Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Illustrator:ポアンカレ円板上での反転関連
#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();
#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();
#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();
// 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