Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Illustratorで3円に接する円を描く
// 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