Created
April 19, 2017 11:49
-
-
Save shspage/5031e173f8edcbced2ae8a284ae408e7 to your computer and use it in GitHub Desktop.
Illustrator:ポアンカレ円板上での反転関連
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
#include "PoincareDiskInversion.jsxinc" | |
function main(){ | |
var sel = activeDocument.selection; | |
var p = sel[0]; | |
var p1 = sel[1]; | |
var p2 = sel[2]; | |
var circle = sel[3]; | |
// ポアンカレ円板上の点を双曲直線に関して反転させた点を描く | |
// p1 : PageItem. この中心の点を反転する | |
// p2, p3 : PageItem. これらの中心を2点とする | |
// circle : PageItem. 理想円の中心と半径を取得する対象 | |
PoincareDiskInversion.drawInversionOfPoint(p, p1, p2, circle); | |
} | |
main(); |
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
#include "PoincareDiskInversion.jsxinc" | |
function main(){ | |
var sel = activeDocument.selection; | |
var p1 = sel[0]; | |
var p2 = sel[1]; | |
var circle = sel[2]; | |
// ポアンカレ円板上の2点を結ぶ双曲線分を描く。 | |
// p1, p2 : PageItem. これらの中心を2点とする | |
// circle : PageItem. 理想円の中心と半径を取得する対象 | |
PoincareDiskInversion.drawHyperbolicSegment(p1, p2, circle); | |
} | |
main(); |
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
#include "PoincareDiskInversion.jsxinc" | |
function main(){ | |
var sel = activeDocument.selection; | |
var path = sel[0]; | |
var p1 = sel[1]; | |
var p2 = sel[2]; | |
var circle = sel[3]; | |
// ポアンカレ円板上の形状を双曲直線に関して反転させた点を描く | |
// path : PathItem. 反転させる形状(ハンドルは考慮されない) | |
// p1, p2 : PageItem. これらの中心を2点とする | |
// circle : PageItem. 理想円の中心と半径を取得する対象 | |
PoincareDiskInversion.drawInversionOfPath(path, p1, p2, circle); | |
} | |
main(); |
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
// PoincareDiskInversion.jsxinc | |
// Gist上でシンタックスハイライトするためにファイル名を~jsxにしてます | |
var PoincareDiskInversion; | |
(function(){ | |
// ------------------------ | |
var STROKE_WIDTH = 0.5; | |
var STROKE_GRAY_VALUE = 100; | |
var MARK_RADIUS = 2; | |
var HPI = Math.PI / 2; | |
var WPI = Math.PI * 2; | |
var EPSILON = 1e-7; | |
// -------------------------------------- | |
// 半径 r, 中心 o の円に関して点 p を反転変換する。 | |
// r2: float 半径 r の2乗 | |
// o, p: Point | |
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; | |
} | |
// -------------------------------------- | |
/// p1, p2 を通る直線に関して点 p を反転変換する。 | |
// p, p1, p2 : Point | |
function inversionByLine(p, p1, p2){ | |
if(p1.equals(p2)){ | |
return p.equals(p1) ? Infinity : p; | |
} | |
var v1 = p.sub(p1); | |
var v2 = p2.sub(p1); | |
var p3 = p1.add(v2.mul(v1.dot(v2) / v2.dot(v2))); | |
return p.add(p3.sub(p).mul(2)); | |
} | |
// ---------------------------------------------- | |
// 点 | |
// x, y : float | |
var Point = function(x, y){ | |
this.x = x; | |
this.y = y; | |
} | |
Point.prototype = { | |
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); | |
}, | |
dot : function(p){ // 内積. p : Point | |
return this.x * p.x + this.y * p.y; | |
}, | |
cross : function(p){ // 外積. p : Point | |
return this.x * p.y - this.y * p.x; | |
}, | |
// o から this へ引いた線の角度を返す | |
getAngle : function(o){ // o : Point | |
return Math.atan2(this.y - o.y, this.x - o.x); | |
}, | |
byAngle : function(t){ // t : float (radian) | |
return new Point(Math.cos(t), Math.sin(t)); | |
}, | |
norm : function(){ // ノルム | |
return Math.sqrt(this.x * this.x + this.y * this.y); | |
}, | |
normalize : function(){ // 正規化 | |
var d = this.norm(); | |
if(d == 0) return new Point(0, 0); | |
return new Point(this.x / d, this.y / d); | |
}, | |
// o を中心に反時計回りに回転する | |
rot : function(t, o){ // t : float (radian), o : Point | |
var c = Math.cos(t); | |
var s = Math.sin(t); | |
var x = this.x - o.x; | |
var y = this.y - o.y; | |
return new Point(x * c - y * s, | |
x * s + y * c).add(o); | |
}, | |
equals : function(p){ // p と位置が等しいとき true | |
return this.x == p.x && this.y == p.y; | |
}, | |
toArray : function(){ | |
return [this.x, this.y]; | |
} | |
} | |
// ------------------------ | |
// 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; | |
} | |
// ------------------------ | |
// Point p1, p2 の中点を返す | |
function getMidPoint(p1, p2){ | |
return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); | |
} | |
// ------------------------ | |
// 弧の中心角に対して半径が1の場合のハンドル長さを返す | |
// t : float, radian | |
function getHandleLengthBase(t){ | |
if(t > Math.PI){ // 4点で描画 | |
t /= 3; | |
} else if(t > HPI){ // 3点で描画 | |
t /= 2; | |
} // else : 2点で描画 | |
return 4 * Math.tan(t / 4) / 3; | |
} | |
// ------------------------ | |
// PathItem を生成して返す | |
function createAPath(){ | |
var path = app.activeDocument.activeLayer.pathItems.add(); | |
path.closed = false; | |
path.filled = false; | |
path.stroked = true; | |
path.strokeWidth = STROKE_WIDTH; | |
var gray = new GrayColor(); | |
gray.gray = STROKE_GRAY_VALUE; | |
path.strokeColor = gray; | |
return path; | |
} | |
// ------------------------ | |
// PathPoint の位置を設定する | |
// p : PathPoint | |
// anchor, rdir, ldir : Point | |
function setPathPoint(p, anchor, rdir, ldir){ | |
p.anchor = anchor.toArray(); | |
p.rightDirection = rdir.toArray(); | |
p.leftDirection = ldir.toArray(); | |
} | |
// ------------------------ | |
// o を中心として p1 から p2 へ反時計回りに円弧を描く | |
// drawSmallerSideがtrueの場合は回転方向は考慮せず | |
// 円弧の中心角の小さい側に描く | |
// p1, p2, o : Point | |
// drawSmallerSide: bool | |
function drawArc(p1, p2, o, drawSmallerSide){ | |
if(p1.equals(p2)) return; | |
// 1. 弧の中心角を求める | |
var v1 = p1.sub(o).normalize(); | |
var v2 = p2.sub(o).normalize(); | |
var t = Math.acos(v1.dot(v2)); | |
if(v1.cross(v2) < 0){ | |
if(drawSmallerSide){ | |
var tmp = p1; | |
p1 = p2; | |
p2 = tmp; | |
} else { // 反時計回り | |
t = WPI - t; | |
} | |
} | |
// 2. 始点・終点のハンドルの位置を求める | |
var t1 = p1.getAngle(o); | |
var t2 = p2.getAngle(o); | |
var radius = dist(p1, o); | |
var han = radius * getHandleLengthBase(t); // ハンドル長 | |
// ハンドルの位置 | |
var h1 = new Point().byAngle(t1 + HPI).mul(han).add(p1); | |
var h2 = new Point().byAngle(t2 - HPI).mul(han).add(p2); | |
var p = createAPath().pathPoints; | |
// 3. 中間点のアンカーの位置を決める | |
if(t > Math.PI){ | |
t /= 3; | |
setPathPoint(p.add(), p1, h1, p1); | |
setPathPoint(p.add(), p1.rot(t, o), | |
h1.rot(t, o), | |
h2.rot(-t * 2, o)); | |
setPathPoint(p.add(), p2.rot(-t, o), | |
h1.rot(t * 2, o), | |
h2.rot(-t, o)); | |
setPathPoint(p.add(), p2, p2, h2); | |
} else if(t > HPI){ | |
t /= 2; | |
setPathPoint(p.add(), p1, h1, p1); | |
setPathPoint(p.add(), p1.rot(t, o), | |
h1.rot(t, o), | |
h2.rot(-t, o)); | |
setPathPoint(p.add(), p2, p2, h2); | |
} else { | |
setPathPoint(p.add(), p1, h1, p1); | |
setPathPoint(p.add(), p2, p2, h2); | |
} | |
//p.parent.locked = true; | |
} | |
// -------------------------------------- | |
// 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 を通る直線を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); | |
} | |
// ---------------------------------------------- | |
// 2点の間に線分を描く | |
// p1, p2 : Point | |
function drawLine(p1, p2){ | |
var p = createAPath(); | |
p.setEntirePath([p1.toArray(), p2.toArray()]); | |
} | |
// ---------------------------------------------- | |
// n が0と見なせる場合 true を返す | |
// n : float | |
function isZero(n){ | |
return Math.abs(n) < EPSILON; | |
} | |
// ---------------------------------------------- | |
// 点の位置に円を描く | |
function mark(pt){ // pt: Point | |
var radius = MARK_RADIUS; | |
activeDocument.activeLayer.pathItems.ellipse( | |
pt.y + radius, pt.x - radius, radius*2, radius*2); | |
} | |
// ---------------------------------------------- | |
// ポアンカレ円板上の2点を結ぶ双曲線分を描く。 | |
// p1, p2 : Point | |
// o : Point. 理想円とする円の中心 | |
// r2 : float. 理想円とする円の半径の2乗 | |
// specOnly : bool. trueの場合、描画せず属性を返す | |
function drawHyperbolicSegment(p1, p2, o, r2, specOnly){ | |
var px = p1; | |
var d1 = dist2(p1, o); | |
var d2 = dist2(p2, o); | |
if(isZero(d1) || isZero(d2)){ | |
if(specOnly) return {o:Infinity, r:Infinity}; | |
drawLine(p1, p2); | |
return; | |
} | |
if(d1 > d2) px = p2; | |
var p3 = inverse(o, r2, px); | |
//if(p3 == Infinity) return; | |
if(isZero(p1.sub(p3).cross(p2.sub(p3)))){ | |
if(specOnly) return {o:Infinity, r:Infinity}; | |
drawLine(p1, p2); | |
return; | |
} | |
var pb1 = perpendicularBisector(p1, p2); | |
var pb2 = perpendicularBisector(px, p3); | |
var center = intersection(pb1, pb2); | |
if(specOnly) return {o:center, r:dist(center, p1)}; | |
drawArc(p1, p2, center, true); | |
} | |
// ---------------------------------------------- | |
// ポアンカレ円板上の点を双曲直線に関して反転させた点を描く | |
// p1 : Point. 反転させる点 | |
// p2, p3 : Point. 双曲直線が通る点 | |
// o : Point. 理想円とする円の中心 | |
// r2 : float. 理想円とする円の半径の2乗 | |
function drawInversionOfPoint(p1, p2, p3, o, r2){ | |
var spec = drawHyperbolicSegment(p2, p3, o, r2, true); | |
if(spec){ | |
var px; | |
if(spec.o == Infinity){ | |
px = inversionByLine(p1, p2, p3); | |
} else { | |
px = inverse(spec.o, spec.r * spec.r, p1); | |
} | |
if(px != Infinity) mark(px); | |
} | |
} | |
// ---------------------------------------------- | |
// ポアンカレ円板上の形状を双曲直線に関して反転させた点を描く | |
// path : PathItem. 反転させる形状(ハンドルは考慮されない) | |
// p1, p2 : Point. 双曲直線が通る点 | |
// o : Point. 理想円とする円の中心 | |
// r2 : float. 理想円とする円の半径の2乗 | |
function drawInversionOfPath(path, p1, p2, o, r2){ | |
var dup = path.duplicate(); | |
var pp = dup.pathPoints; | |
for(var i = 0; i < pp.length; i++){ | |
var a = pp[i].anchor; | |
var p = new Point(a[0], a[1]); | |
var spec = drawHyperbolicSegment(p1, p2, o, r2, true); | |
if(spec){ | |
var px; | |
if(spec.o == Infinity){ | |
px = inversionByLine(p, p1, p2); | |
} else { | |
px = inverse(spec.o, spec.r * spec.r, p); | |
} | |
if(px == Infinity) continue; | |
a = px.toArray(); | |
pp[i].anchor = a | |
pp[i].rightDirection = a; | |
pp[i].leftDirection = a; | |
} | |
} | |
} | |
// ---------------------------------------------- | |
PoincareDiskInversion = { | |
// ポアンカレ円板上の2点を結ぶ双曲線分を描く。 | |
// obj1, obj2 : PageItem. これらの中心を2点とする | |
// circle : PageItem. 理想円の中心と半径を取得する対象 | |
drawHyperbolicSegment : function(obj1, obj2, circle){ | |
var p1 = getCenter(obj1); | |
var p2 = getCenter(obj2); | |
var o = getCenter(circle); | |
var r = circle.width / 2; | |
drawHyperbolicSegment(p1, p2, o, r*r); | |
}, | |
// ポアンカレ円板上の点を双曲直線に関して反転させた点を描く | |
// obj1 : PageItem. この中心の点を反転する | |
// obj2, obj3 : PageItem. これらの中心を2点とする | |
// circle : PageItem. 理想円の中心と半径を取得する対象 | |
drawInversionOfPoint : function(obj1, obj2, obj3, circle){ | |
var p1 = getCenter(obj1); | |
var p2 = getCenter(obj2); | |
var p3 = getCenter(obj3); | |
var o = getCenter(circle); | |
var r = circle.width / 2; | |
drawInversionOfPoint(p1, p2, p3, o, r*r); | |
}, | |
// ポアンカレ円板上の形状を双曲直線に関して反転させた点を描く | |
// path : PathItem. 反転させる形状(ハンドルは考慮されない) | |
// obj1, obj2 : PageItem. これらの中心を2点とする | |
// circle : PageItem. 理想円の中心と半径を取得する対象 | |
drawInversionOfPath : function(path, obj1, obj2, circle){ | |
var p1 = getCenter(obj1); | |
var p2 = getCenter(obj2); | |
var o = getCenter(circle); | |
var r = circle.width / 2; | |
drawInversionOfPath(path, p1, p2, o, r*r); | |
} | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment