Last active
April 10, 2017 21:56
-
-
Save shspage/be7ca6542da78da508a2217f96b9000a to your computer and use it in GitHub Desktop.
Illustratorで3円に接する円を描く
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 3円に接する円を描く | |
// 円の内側に接するケースも考えるともっと場合分けする必要がある | |
// 参考:http://www.comp.tmu.ac.jp/knotNRG/texfiles/apollonius.pdf | |
// ---------------------------------------------- | |
// 点 | |
function Point(x, y){ // x, y : float | |
this.x = x; | |
this.y = y; | |
} | |
Point.prototype = { | |
set : function(x, y){ // x, y : float | |
this.x = x; | |
this.y = y; | |
return this; | |
}, | |
add : function(p){ // p : Point | |
return new Point(this.x + p.x, this.y + p.y); | |
}, | |
sub : function(p){ // p : Point | |
return new Point(this.x - p.x, this.y - p.y); | |
}, | |
mul : function(m){ // m : float | |
return new Point(this.x * m, this.y * m); | |
}, | |
toArray : function(){ | |
return [this.x, this.y]; | |
}, | |
toString : function(){ | |
return "[" + this.x + "," + this.y + "]"; | |
} | |
} | |
// ---------------------------------------------- | |
// 円 | |
// o : Point, 中心 | |
// r : float, 半径 | |
function Circle(o, r){ | |
this.center = o; | |
this.radius = r; | |
} | |
Circle.prototype = { | |
byPath : function(path){ // path: PathItem(円とする) | |
this.center = getCenter(path); | |
this.radius = path.width / 2; | |
return this; | |
}, | |
toString : function(){ | |
return "Circle:center=" + this.center + ", radius=" + this.radius; | |
} | |
} | |
// ---------------------------------------------- | |
// 線分 | |
// p1, p2 : Point | |
function Segment(p1, p2){ | |
this.p1 = p1; | |
this.p2 = p2; | |
} | |
// ---------------------------------------------- | |
// Point p1, p2 の間の距離を返す | |
function dist(p1, p2) { | |
return Math.sqrt(dist2(p1, p2)); | |
} | |
// Point p1, p2 の間の距離の2乗を返す | |
function dist2(p1, p2) { | |
var dx = p1.x - p2.x; | |
var dy = p1.y - p2.y; | |
return dx*dx + dy*dy; | |
} | |
// -------------------------------------- | |
// PageItem item の中心の座標を返す | |
function getCenter(item){ | |
var gb = item.geometricBounds; // [left, top, right, bottom] | |
return new Point((gb[0] + gb[2]) / 2, | |
(gb[1] + gb[3]) / 2); | |
} | |
// ---------------------------------------------- | |
// Point p1, p2 の中点を返す | |
function getMidPoint(p1, p2){ | |
return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); | |
} | |
// ---------------------------------------------- | |
// Point p1, p2 を通る直線をax+by+c=0と表したときの | |
// [a, b, c] を返す | |
function defline(p1, p2){ | |
var a = p1.y - p2.y; | |
var b = p1.x - p2.x; | |
return [a, -b, b * p1.y - a * p1.x]; | |
} | |
// ---------------------------------------------- | |
// Point p1, p2 を結ぶ線分の垂直二等分線を | |
// ax+by+c=0 と表したときの [a, b, c] を返す | |
function perpendicularBisector(p1, p2){ | |
var mp = getMidPoint(p1, p2); | |
return defline(new Point(mp.x - (p1.y - mp.y), | |
mp.y + (p1.x - mp.x)), | |
new Point(mp.x - (p2.y - mp.y), | |
mp.y + (p2.x - mp.x))); | |
} | |
// ---------------------------------------------- | |
// 直線 p, q の交点を返す。2直線が平行な場合は undefined を返す。 | |
// p, q: ax+by+c=0 の係数 [a, b, c] | |
function intersection(p, q){ | |
var d = p[0] * q[1] - p[1] * q[0]; | |
if(d==0) return; | |
return new Point((q[2] * p[1] - p[2] * q[1]) / d, | |
(p[2] * q[0] - q[2] * p[0]) / d); | |
} | |
// -------------------------------------- | |
// 半径 r, 中心 o の円に関する 点 p の反転(円に関する鏡像反転)を返す。 | |
// r2: float, 半径の2乗 | |
// o, p: Point | |
// return: Point, p と o の距離が 0 の場合 Infinity | |
function inverse(o, r2, p){ | |
var d2 = dist2(o, p); | |
if(d2 == 0){ | |
return Infinity; | |
} | |
var m = r2 / d2; | |
var q = new Point((p.x - o.x) * m + o.x, | |
(p.y - o.y) * m + o.y); | |
return q; | |
} | |
// ---------------------------------------------- | |
// 半径 r, 中心 o の円に関する 円 circle の反転を返す。 | |
// o: Point | |
// r2: float 半径の2乗 | |
// circle: Circle | |
// return: Circle | |
function inverseCircle(o, r2, circle){ | |
var d = dist(circle.center, o); | |
var v = d == 0 | |
? new Point(circle.radius, 0) | |
: circle.center.sub(o).mul(circle.radius / d); | |
var p1 = circle.center.sub(v); | |
var p2 = circle.center.add(v); | |
var i1 = inverse(o, r2, p1); | |
var i2 = inverse(o, r2, p2); | |
var c = new Circle(getMidPoint(i1, i2), | |
dist(i1, i2) / 2); | |
return c; | |
} | |
// ---------------------------------------------- | |
// 円1(中心 O1, 半径 R1)、円2(中心 O2, 半径 R2) | |
// の共通接線の接点を返す。接線がない場合 undefinedを返す | |
// O1, O2: Point | |
// R1, R2: float | |
// return: Segment[] (var segs のコメント参照) | |
function getTangentPoints(O1, R1, O2, R2){ | |
//2円の中心の距離を d とする。 | |
var dx = O1.x - O2.x; | |
var dy = O1.y - O2.y; | |
var d = Math.sqrt(dx*dx + dy*dy); | |
if(d <= Math.abs(R1 - R2)){ | |
// 片方が内部にある → undefined を返す | |
return; | |
} | |
var exists_inner = true; // 内側の接線がある | |
if(d <= R1 + R2){ | |
// 2円が重なっているか、接している | |
exists_inner = false; | |
} | |
var segs = []; // Segment[] | |
// 外側の接線(index): 0, 1 | |
// 内側の接線(ある場合)(index): 2, 3 | |
//O1を原点とした場合、中心を結ぶ線の角度は | |
var T = Math.atan2(O2.y - O1.y, O2.x - O1.x); | |
// 外側 | |
var t1 = Math.acos((R1 - R2) / d); | |
var seg = new Segment(); | |
var t = T + t1; | |
var cos_t = Math.cos(t); | |
var sin_t = Math.sin(t); | |
seg.p1 = new Point(O1.x + R1 * cos_t, | |
O1.y + R1 * sin_t); | |
seg.p2 = new Point(O2.x + R2 * cos_t, | |
O2.y + R2 * sin_t); | |
segs.push(seg); | |
seg = new Segment(); | |
t = T - t1; | |
cos_t = Math.cos(t); | |
sin_t = Math.sin(t); | |
seg.p1 = new Point(O1.x + R1 * cos_t, | |
O1.y + R1 * sin_t); | |
seg.p2 = new Point(O2.x + R2 * cos_t, | |
O2.y + R2 * sin_t); | |
segs.push(seg); | |
// 内側 | |
if(exists_inner){ | |
var t2 = Math.acos((R1 + R2) / d); | |
seg = new Segment(); | |
t = T + t2; | |
cos_t = Math.cos(t); | |
sin_t = Math.sin(t); | |
seg.p1 = new Point(O1.x + R1 * cos_t, | |
O1.y + R1 * sin_t); | |
seg.p2 = new Point(O2.x - R2 * cos_t, | |
O2.y - R2 * sin_t); | |
segs.push(seg); | |
seg = new Segment(); | |
t = T - t2; | |
cos_t = Math.cos(t); | |
sin_t = Math.sin(t); | |
seg.p1 = new Point(O1.x + R1 * cos_t, | |
O1.y + R1 * sin_t); | |
seg.p2 = new Point(O2.x - R2 * cos_t, | |
O2.y - R2 * sin_t); | |
segs.push(seg); | |
} | |
return segs; | |
} | |
// ---------------------------------------------- | |
// 検証用。円を描く | |
function markCircle(circle){ // circle: Circle | |
var r = circle.radius; | |
activeDocument.activeLayer.pathItems.ellipse( | |
circle.center.y + r, | |
circle.center.x - r, r*2, r*2); | |
} | |
// 検証用。線を描く | |
function markLine(segment){ | |
var p = activeDocument.activeLayer.pathItems.add(); | |
p.setEntirePath([segment.p1.toArray(), segment.p2.toArray()]); | |
} | |
// 検証用。点の位置に円を描く | |
function mark(pt){ // pt: Point | |
var radius = 2; | |
activeDocument.activeLayer.pathItems.ellipse( | |
pt.y + radius, pt.x - radius, radius*2, radius*2); | |
} | |
// ---------------------------------------------- | |
// メイン処理 | |
function main(){ | |
// 3つの円が選択されているとする | |
var sel = activeDocument.selection; | |
var cs = [new Circle().byPath(sel[0]), | |
new Circle().byPath(sel[1]), | |
new Circle().byPath(sel[2])]; | |
// 最も小さい円を min_c とする | |
var min_r = cs[0].radius; | |
var idx = 0; | |
for(var i = 1; i < 3; i++){ | |
if(cs[i].radius < min_r){ | |
min_r = cs[i].radius; | |
idx = i; | |
} | |
} | |
var min_c = cs[idx]; | |
cs.splice(idx, 1); | |
// 他の円の半径を最も小さい円の半径だけ小さくする | |
cs[0].radius -= min_r; | |
cs[1].radius -= min_r; | |
// 2円を、最も小さい円と中心が同じ円について反転する | |
// 誤差を減らすため何となく半径を大きめに | |
var radius = Math.max(dist(min_c.center, cs[0].center), | |
dist(min_c.center, cs[1].center)); | |
var r2 = radius * radius; | |
var c0 = inverseCircle(min_c.center, r2, cs[0]); | |
var c1 = inverseCircle(min_c.center, r2, cs[1]); | |
// c0, c1 の共通接線を取得する(ここでは内側のは要らないが) | |
var segs = getTangentPoints(c0.center, c0.radius, | |
c1.center, c1.radius); | |
if(!segs){ | |
alert("描けません(共通接線なし)"); | |
return; | |
} | |
// 基準円の中心から最も遠い接点を持つ接線を取得 | |
var max_d; | |
idx = 0; | |
for(var i = 0; i < segs.length; i++){ | |
var d = dist2(segs[i].p1, min_c.center); | |
if(i == 0 || d > max_d){ | |
max_d = d; | |
idx = i; | |
} | |
} | |
var seg = segs[idx]; | |
// 接点を反転 | |
var p1 = inverse(min_c.center, r2, seg.p1); | |
var p2 = inverse(min_c.center, r2, seg.p2); | |
// p1, p2, min_c.center を通る円の中心と半径を取得 | |
var pb1 = perpendicularBisector(p1, min_c.center); | |
var pb2 = perpendicularBisector(p2, min_c.center); | |
var center = intersection(pb1, pb2); | |
var radius = dist(center, min_c.center); | |
// 元の3円に接するように半径を変更 | |
radius -= min_c.radius; | |
// 円を描画 | |
sel[0].layer.pathItems.ellipse( | |
center.y + radius, center.x - radius, | |
radius * 2, radius * 2); | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment