ZawaWorks’s diary

プログラミング技術メモ

Processing: 円と円の交点を求めよう

目次

はじめに

目標

今回は円と円の交点を求めてみます

サンプルコード

動画のコードは以下のようになります 記事の後半では、この中のgetCirclesCrossPoints()という関数について解説していきます

gist.github.com

解説

数式で考えてみる

2つの円の方程式を用いて考えます

円の中心座標が(x_1, y_1)で半径がr_1の円の方程式


(x-x_1)^2+(y-y_1)^2=r_1^2\cdots(1)

中心座標が(x_2,  y_2)で半径がr_2の円の方程式


(x-x_2)^2+(y-y_2)^2=r_2^2\cdots(2)

このとき(2)-(1)をすると

2(x_2-x_1)x + 2(y_2-y_1)y + x_1^2-x_2^2+y_1^2-y_2^2+r_2^2-r_1^2=0\cdots(3)

この(3)は円の交点を通る直線の方程式になっています f:id:ZawaWorks:20200106014311j:plain あとは(3)の直線と(1)または(2)の円との交点を求めれば、円と円の交点を求めることができます 直線と円の交点の求め方は以下の記事を参考にしてください

zawaworks.hatenablog.com

コードにしてみよう

a=2(x_2-x_1)\\b = 2(y_2-y_1)\\c=x_1^2-x_2^2+y_1^2-y_2^2+r_2^2-r_1^2

とおきます。それを円と直線の交点を求める関数に代入すれば円と円の交点が返ってくるようになっています。

//円と円の交点を取得する関数
PVector[] getCirclesCrossPoints(float x1, float y1, float r1, float x2, float y2, float r2) {

  float a = 2*(x2 - x1);
  float b = 2*(y2 - y1);
  float c = sq(x1)-sq(x2)+sq(y1)-sq(y2)+sq(r2)-sq(r1);
  return getLineCircleCrossPoints(a, b, c, x1, y1, r1);
}

//直線(ax + by + c = 0)と円(中心座標(circleX, circleY), 半径 r)の交点を取得する関数
PVector[] getLineCircleCrossPoints(float a, float b, float c, float circleX, float circleY, float r) {
    //中身はこちらの記事を読んでください
    //http://zawaworks.hatenablog.com/entry/2019/12/04/012717?_ga=2.20645616.952110635.1578237427-1521756013.1576935957
}

Processing: 2番目の画面でクラスを使うときの注意

Processingは通常画面が一つしか作成されませんが,PAppletクラスを継承するクラスを作れば画面を複数作成することができます. f:id:ZawaWorks:20191207161923j:plain

問題

新しくTestクラスを作りました.これは画面の背景を黒くするクラスです.

このクラスをSecondWindowdraw()に書きましたが,背景が黒くなりません.

f:id:ZawaWorks:20191207162005j:plain

解決方法

これは以下のようにSecondWindow内にPApplet型の変数を用意して,applet.background(0)とすれば2番目の画面の背景色を変更することができました. f:id:ZawaWorks:20191207162716j:plain

今回のコードは,すべてこちらにあります. 2番目の画面内でクラスを使うときの注意 · GitHub

解説

f:id:ZawaWorks:20191207165911j:plain PAppletクラスは画面を作成し,setup()draw()background()を管理しているクラスです. Processingでは起動するとPAppletクラスが呼ばれて画面を作成し,その中身はsize()background()などで変更されます. SecondWindowクラスはそれを継承して,新しい画面を作成し元のPAppletクラスと同じ機能(setup()background()など)を備えています.

background()ellipse()などの命令を自分の作ったクラス(今回はTestクラス)で呼ぶと,それは1番目のPAppletの命令を呼んでしまうので,2番目のPAppletSecondWindow)の中身には何も変化が起きません.

これは,クラスの内で呼ばれるbackground()などの命令が,どのPAppletの命令なのかを指定することで解決することができるのです.

ちなみに

クラス内でmouseXmouseYとしても,1番目の画面のマウス座標を取得してしまうので,これもapplet.mouseXのように指定する必要があります.

class Test {
  PApplet applet;

  Test(PApplet _applet) {
    applet = _applet;
  }

  void display() {
    applet.background(0);
    applet.fill(255);
    applet.ellipse(applet.mouseX, applet.mouseY, 20, 20);
  }
}

参考文献

3846masa.hatenablog.jp

Processing: 2点を通る直線と円の交点を求めよう

目標


Processing: 2点を通る直線と円の交点を求めよう

今回は以下のようにある2点を通る直線と円の交点を求めるgetCrossPoints()という関数を作ります.

//交点を取得する関数
PVector[] getCrossPoints(float x1, float y1, float x2, float y2, float circleX, float circleY, float r) {

  //ax + by + c = 0 の定数項
  float a = y2-y1;
  float b = x1-x2;
  float c = -a*x1-b*y1;

  //円の中心から直線までの距離
  //mag(a, b) = √a^2+b^2
  float d = abs((a*circleX+b*circleY+c)/mag(a, b));

  //直線の垂線とX軸と平行な線がなす角度θ
  float theta = atan2(b, a);

  if (d > r) {
    return null;
  } else if (d == r) {
    PVector[] point = new PVector[1];

    //場合わけ
    if (a*circleX+b*circleY+c > 0)theta += PI;

    float crossX = r*cos(theta)+circleX;
    float crossY = r*sin(theta)+circleY;

    point[0] = new PVector(crossX, crossY);
    return point;
  } else {

    PVector[] crossPoint = new PVector[2];
    float[]crossX = new float[2];
    float[]crossY = new float[2];

    //alphaとbetaの角度を求める
    float alpha, beta, phi;
    phi = acos(d/r);
    alpha = theta - phi;
    beta = theta + phi;

    //場合わけ
    if (a*circleX+b*circleY+c > 0) {
      alpha += PI;
      beta += PI;
    }

    //交点の座標を求める
    crossX[0] = r*cos(alpha) + circleX;
    crossY[0] = r*sin(alpha) + circleY;

    crossX[1] = r*cos(beta) + circleX;
    crossY[1] = r*sin(beta) + circleY;


    for (int i = 0; i < crossPoint.length; i++)
      crossPoint[i] = new PVector(crossX[i], crossY[i]);

    return crossPoint;
  }
}

動画のプログラムは,こちらを実行すれば体験できます.

円と線の交点を求めたプログラム · GitHub

2点を通る直線と円の交点の求め方

ここからは直線と円の交点をどうやって求めているのか解説したいと思います

直線と円の交点があるかを確認

f:id:ZawaWorks:20191203233704j:plain まず「直線と円が交わっているのか?」を確認しないといけません. 確認の方法は至って簡単で,直線と円の中心の距離をdとしたときに,r < dだったら直線が円から離れているので交わっていません. r = dのときはちょうど円と直線が接しています.そして,r > dだったら直線と円は2つの交点を持ちます.

dの求め方

d点と直線の距離の公式を使うことで,求めることが可能です.

点(x_0, y_0)と直線(ax_0+by_0+c = 0)の距離の公式


d=\dfrac{|ax_0+by_0+c|}{\sqrt{a^2+b^2}}

コードにすると……

 //円の中心から直線までの距離
 //mag(a, b) = √a^2+b^2
 float d = abs((a*circleX+b*circleY+c)/mag(a, b));

if  (d > r) {
 //交点が0のとき
}else if (d == r) {
 //交点が1つのとき
}else{
 //交点が2つのとき
}

直線と円が接しているとき

まず直線と円が接しているときに交点の座標をどのように求めるのかを解説していきます.

交点の座標の求め方

f:id:ZawaWorks:20191203234227j:plain 例えば円の中心が原点だったときのことを考えます. 円と直線の距離をdとし,直線の垂線がX軸と平行な線となす角をθとすると交点の座標は

float x = r*cos(theta);
float y = r*sin(theta);

となります.

円の中心が(centerX, centerY)だった場合は平行移動させればいいので

float x = r*cos(theta) + centerX;
float y = r*sin(theta) + centerY;

とすれば,交点の座標を求めることができます.

θの求め方

f:id:ZawaWorks:20191203234921j:plain 直線と円との距離は,円の半径と同じであるため求めるのが簡単です.一方,θの値はどうやって求めればいいのでしょうか.

円と接する直線の方程式をax + by + c = 0とすると,その垂線の傾きはb/aとなることが知られています. そして,傾きはtan()を使って表すこともでき,tan(θ) = b/aとなります. これを満たすθを求める命令がatan2()です.

float theta = atan2(b, a);

これでθの値を求めることができました.

atan2()の罠

f:id:ZawaWorks:20191203235520j:plain 先ほど紹介したθの求め方だけだと,正しい交点の座標を求めることはできません.それは.θの値を直線の方程式のaとbを使うことで角度を求めているので,その値に円と直線の位置関係が反映されていないからです. これは,a*centerX + b*centerY + cが0より大きいか小さいかで場合わけすることで対処することができます(理由は後日記述します).

float theta = atan2(b, a);
 if (a*circleX+b*circleY+c > 0) theta += PI;

このようにa*circleX+b*circleY+c > 0のときにθに180°足すと,円と直線の位置関係が反映されて,正しい交点の座標を求めることができます.

コードにすると……

これまで説明したことをコードにすると以下のようになります.

float theta = atan2(b, a);

//場合わけ
if (a*circleX+b*circleY+c > 0)theta += PI;

float crossX = r*cos(theta)+circleX;
float crossY = r*sin(theta)+circleY;

直線と円の交点が2つのとき

次に直線と円の交点が2つのときに交点の座標をどのように求めるのかを解説していきます.

交点の座標の求め方

f:id:ZawaWorks:20191204000947j:plain 直線と円が接しているときと同じ考え方で求めます.円の中心と交点を通る直線X軸と平行な線のなす角をそれぞれα, βとすると

float crossX1 = r*cos(alpha) + centerX;
float crossY1 = r*sin(alpha) + centerY;

float crossX2 = r*cos(beta) + centerX;
float crossY2 = r*sin(beta) + centerY;

として求めることができます.

αとβの求め方

f:id:ZawaWorks:20191204001750j:plain 直線の垂線AがX軸と平行な線となす角をθとし,「円の中心を通る垂線A」が「円の中心と交点を通る直線B, C」となす角をそれぞれγδとすると

float alpha = theta - gamma;
float beta = theta + delta;

として求めることができます.

γ,δの求め方

f:id:ZawaWorks:20191204002409j:plain 「円の中心と交点の直線」と「円の中心から直線の垂線」を引きます. そしてできた直角三角形を見てみると,それぞれ合同であることが分かります. つまり,φ = γ = δとなるφの値を求めるだけで済みます.

直角三角形であることを利用すればφを求めることは簡単です. r, d, φは,cos(φ) = d/rという関係があります.

これはatan()のときと同じようにacos()という命令を使えば,求めることが可能です.

float phi = acos(d/r);

これでphiの値を求めることができました.

コードにすると……

これまで説明したことをコードにすると以下のようになります. (atan2()の場合わけも忘れないように!)

phi = acos(d/r);
alpha = theta - phi;
beta = theta + phi;

//場合わけ
if (a*circleX+b*circleY+c > 0) {
   alpha += PI;
   beta += PI;
}

//交点の座標を求める
crossX[0] = r*cos(alpha) + circleX;
crossY[0] = r*sin(alpha) + circleY;

crossX[1] = r*cos(beta) + circleX;
crossY[1] = r*sin(beta) + circleY;

おわりに

今回は,2点を通る直線と円の交点の求め方について解説しました.この記事の知識をプログラム作品に応用してもらえたら幸いです.

Python : pandasでdf['hoge']が使えないときの対処法

はじめに

自分の研究でデータ分析をするためにpandasを使い始めました.pandasはcsv(tsv)ファイルをdf = pd.read_csv('hoge.csv')(またはread_table)で読み込んで,df['hoge'] という形で指定したカラム(列)を配列として抽出できます.しかし,あるときなぜかそれができませんでした.そこで,そのときの対処法をここでまとめました.

今日使うデータの説明

今回使うデータは以下の通りです.

hoge.csv

id sex name
0 M Yuta
1 F Rikka
2 M Sho

ちなみに表の一番上にあるid, sex, nameのタイトルが書かれているカラムのことをヘッダーといいます.

コード

df pd.read_csv('hoge.csv')

print(df['id'])
print(df['sex'])
print(df['name'])

これがうまくいけば以下のような結果が返ってきます.

実行結果

[0, 1, 2]
[`M`, `F`, `M`]
[`Yuta`, `Rikka`, `Sho`]

read_csvかread_tableかを確認

pandasには 'read_csv' と read_table という二つの命令があります.これらは前者がcsvファイルを読み込むときに使い,後者が read_table ファイルを読み込むときに使います.

read_csv read_table
csv専用 tsv専用

そのため,read_csv('hoge.tsv')read_table('hoge.csv') とすると,ヘッダーが読み込めなくなり,df['id']と呼んでも[0, 1, 2] の配列は返ってきません. ちゃんとread_csv('hoge.csv')read_table('hoge.tsv')になっているか確認しましょう.

index_col=0 またはheader=0を入れる

たとえばread_csv('hoge.csv')をやってもdf['id']が出ない場合,hoge.csvが「ヘッダーがないcsvファイル」として読み込まれている可能性があります.csvファイルには「ヘッダーあり」の場合とありますと「ヘッダーなし」の場合があります.df['id']でカラムを抽出できるのは,ヘッダーありcsvの場合なので,ヘッダーがないcsvだとできません.このとき,ヘッダーありのcsvとして読み込むためには,以下のように書きます.

df = pd.read_csv('hoge.csv', index_col=0)
df = pd.read_csv('hoge.csv', header=0)

index_col = 0は「0行目をindexとして使いますよ」という変数で,header=0は「0行名をヘッダーにするよ」という命令です.これを指定あげることで,「0行目がヘッダーのcsvファイル」として読み込むことができます.

Processing : PGraphicsでジェネラティブアート

はじめに

私はProcessingを使ってジェネラティブアートを作るのが趣味です.作っていくうちに「 PGraphics 使うといろんな作品が作れる!」と気づいたので紹介します.

PGraphicsの簡単な紹介

PGraphicsは簡単に言うとProcessing画面を画像として作り出すものです.

実行画面

youtu.be

コード

gist.github.com

このように beginDraw() と, endDraw() の間にProcessingでおなじみの background()ellipse() , rect() などの命令を書き込むことでPGraphics内を描画します.そして,描画されたPGraphicsは image() を使って,PImageと同じように使うことができます.

我流:PGraphicsの使い方

パネルの中だけ色違いにする

画面中心に白い円があります.この円をマウスで動かすPGraphics上だけ背景と円の色が違うようにしたいと思います.

実行画面

youtu.be

コード

gist.github.com

仕組み

次の図の赤い点はPGraphicsの左上の座標,緑の点は ellipse() の中心を表しています.

Processing画面の目線

f:id:ZawaWorks:20181207003950p:plain
PGraphicsの左上の座標を (pgX, pgY) ,円の中心を (250, 250) とします.このとき (pgX, pgY)(250, 250) の距離は (250-pgX, 250-pgY) あります.

PGraphicsの目線

f:id:ZawaWorks:20181207004248p:plain 先ほどの図をPGraphicsの目線で見ます.PGraphicsの左上の座標を (0, 0) とすると,円の中心は (250-pgX, 250-pgY) になります.このようにPGraphicsの目線で見ると,オブジェクトの座標はすべて (-pgX, -pgY) を足して表すことができるのです.

作品例

では,これを使ってジェネラティブアートしていきます.

作品1:市松模様

左上から交互に背景と円が白黒逆転したPGraphicsを並べます.円の位置はランダムで決めて,それぞれのPGraphics上でも円を描いています.だから,同じ円なのに部分ごとに色が違っていきます.

作品2:揺れる文字

今までのような PGraphicsの使い方をすると,Processing画面上の図形とPGraphics上の図形が一致します.PGraphics上の図形の位置を少しずらすとまるで図形が歪んだように見えます.これを文字でやってみたら結構エモくなりました.

おわりに

今回は私が発見した「PGraphicsアート」の紹介をしました.この手法を使った作品をいろいろ考案しているところです.よろしければこの手法を使ってみてください.これを読んだ皆さんがこの手法でどんな作品を作るか楽しみにしています!

Adobe Premiere Proでオブジェクトを回転運動させる方法

はじめに

動画編集のために Adobe Premiere Pro を使ったのですが,そこで「オブジェクトの回転運動」で手こずったので,そのときの対策を書き記しておきます.

今回の目標

下の動画のようにカーソルをその向きのまま回転させます. カーソルは1秒に360度回転させています.

www.youtube.com

普通に回転運動をさせようとすると

オブジェクトを回転するときの操作はこうです この操作でカーソルの画像を回転させてみましょう

www.youtube.com

なんと向きまで変わってしまいました.

対策

このときの対策は,オブジェクト自体を動かしたい方向と逆回転させ,そのシーケンスを動かしたい方向に回転させればいいのです

www.youtube.com

やったことは

  1. カーソル自体は1秒間で-360度で回転させる
  2. そのカーソルをネストしたシーケンスにする
  3. そのシーケンスを1秒間で360度に回転させる

このようにn秒間でm度回転させたいときは,そのオブジェクト自体はn秒間に-m秒間すれば大丈夫になります!

おわりに

自分はこの方法で対策しましたが,他にもっと良い方法があったら教えてくれるとありがたいです.

Processing: 正弦波を使ったおもしろ図形(おにぎり、花、金平糖、その他)

はじめに

Processingで正弦波を使って様々な図形を作ってみました。すべて同じ関数の変数をいじるだけで作成できます。

使う関数

今回使う関数はwave_circle()という自作の関数です。この関数は元々ジグザグした円を作るために作成しました。

wave_circle()

void setup() {
  size(600, 600);
  fill(-1);
  wave_circle(width/2, height/2, 100, 10, 10);
}

//posX,posY: 座標, r: 半径, wavelength: 波長(波の幅), amplitude: 振幅(波の高さ)
void wave_circle(float posX, float posY, float r, float wavelength, float  amplitude) {
  pushMatrix();
  translate(posX, posY);

  beginShape();
  for (float i=0; i <= 2*PI; i += PI/180) {
    float x = cos(i)*( r + amplitude*sin(i*wavelength) );
    float y = sin(i)*( r + amplitude*sin(i*wavelength) );

    vertex(x, y);
  }
  endShape();

  popMatrix();
}

出力結果

f:id:ZawaWorks:20180509233417p:plain 上の図のように金平糖のようなジグザグした図形を作ることができます。これで目的は達成したのですが、変数をいじってみたら様々な形に変わることが分かりました。

図形一覧

ここからはどのような変数を入れるといかなる図形になるの一覧にしました。

金平糖

wavelength = 10 , amplitude = r/10 f:id:ZawaWorks:20180509233417p:plain

ミートボール

wavelength = 20, amplitude = r/100 f:id:ZawaWorks:20180509234412p:plain

ウニ

wavelength = 100, amplitude = r/10 f:id:ZawaWorks:20180509235117p:plain

おにぎり(角丸多角形)

wavelength = 3, amplitude = r/10wavelength = n のとき角丸のn角形になります) f:id:ZawaWorks:20180509235657p:plain

花びら

wavelength > 2, amplitude = r (図はwavelength = 5のとき) f:id:ZawaWorks:20180509235912p:plain

ヒトデ、星

wavelength = 5, amplitude = (3*r) / 10 f:id:ZawaWorks:20180510000204p:plain

作品事例

この関数を使って作ってみた作品です。

おわりに

なんとなく作ってみた関数でいろいろな図形を描くことができたことに驚きました。なんとなく入れた数字で想像してなかった図形に巡り合えることが興味深かったです。もしかしたら僕も発見してない図形があるかもしれないので試してみると面白いかもしれません。