62
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

いのちの輝きくん eyes の作り方

Last updated at Posted at 2020-09-06

#いのちの輝き君 eyes
xeyes のパクリオマージュとして「いのちの輝きくん」がマウスを見つめる物を作ろうと思い立った。
できあがったのは↓

■Windows
InochiShine2.gif

■macOS
ino.gif

形や目はランダム生成。

Delphi なので、この手のデスクトップマスコットを作るのは本当に簡単。
具体的には下記の記事を参照。

■FireMonkey で Window Drag
https://qiita.com/pik/items/907641319e4de4d74805

■Delphi + FireMonkey で TaskTray / StatusBar にアイコンとメニューを実装する
https://qiita.com/pik/items/bd3448e2cf0c4c528027

なので、それだけだったら新たに記事を書く必要は無かった!
つまり問題はそこじゃなく、楕円の内部に収まるように目玉を動かす部分!

幾何学をもう一度

やりたいことは、マウスカーソルの位置と楕円の接点を求め(図の赤丸部分)、そこに接するように目玉を配置する事

eye1.png

ここで、図中の要素に名前を付ける

eye5.png

図中の名前 意味 変数名
c (cx, cy) 中心点 FCenterX, FCenterY
m (mx, my) マウスの座標 AMouseX, AMouseY
ra 長軸の半分 FRA
rb 短軸の半分 FRB
θ 線分 c-m の角度 Theta
er 目玉の半径 FEyeR
x, y 線分 c-m と楕円の接点 X, Y

θを求める

接点 X, Y を求めるのは線分 m の傾きが解ればいいので、まずθを求める。
θは ArcTan を使って dy/dx から求められる

\theta = tan^{-1}(\frac{dy}{dx})

dx, dy は

dx = mx - cx\\
dy = my - cy\\

なので

\theta = tan^{-1}(\frac{my - cy}{mx - cx})

となる。
これをコードに落とすとこう
(ArcTan2 は分子と分母を別々に渡せる ArcTan のバリエーション)

var Theta := ArcTan2(AMouseY - FCenter.Y, AMouseX - FCenter.X);

X, Y を求める

θが求まったので、x, y を求める。

X を求める

この時、中心点 c で半径 ra の真円(下図の赤い線)を考える。

image.png

図中の点 Q は、真円の円周上にある物とすると、線分 OQ の長さは ra となる(真円なので)

次に三角形 OQx の底辺 Ox の長さを求める。
ここでは、三角関数の cos を使う。

cos は三角関数の定義で

image.png

上図の三角形の

\frac{b}{a} = cos(\theta)

と決められている。

ここでは、上図の三角形の底辺 b (=Ox) の長さを求めたい訳だから、両辺に a を掛けてやればよい。

\begin{align}
\frac{b}{a} &= cos(\theta)\\
\\
\frac{b}{a} * a &= cos(\theta) * a\\
\\
b &= a * cos(\theta)
\end{align}

これを使って図の三角形 OQx の底辺 Ox の長さ(x座標)を表すと

  x = ra * cos(\theta)

となる。

Y を求める

y を求める方法は2つ。
x と同じように rb を半径とした真円を考える方法と、楕円の公式を解く方法がある。
せっかくなので、ここでは楕円の公式を使う方法を紹介する。

楕円の公式は

\frac{x^2}{a^2} + \frac{y^2}{b^2} = 1

なので、これを y について解くと

y = \frac{b}{a}\sqrt{a^2 - x^2}

となるので a に ra, b に rb, x に ra * cosθ を代入すると

\begin{align}
y &= \frac{rb}{ra}\sqrt{ra^2 - (ra * cos(θ))^2}\\
\\
& = \frac{rb}{ra}\sqrt{ra^2 - ra^2 * cos(θ)^2}\\
\\
& = \frac{rb}{ra}ra\sqrt{1 - cos(θ)^2}\\
\\
& = rb\sqrt{1 - cos(θ)^2}\\
\end{align}

こうなる。
ここで三角関数の定義1↓より

\sqrt{1 - cos(θ)^2} = sin(θ)

なので、

y = rb * sin(θ)

となる。

総合すると

x = ra * cos(\theta)
y = rb * sin(\theta)

となり、これをコードに落とすと

var X := FRA * Cos(Theta);
var Y := FRB * Sin(Theta);

こうなる。

ただ、このままだと目玉の半径が考慮されておらず、目玉がはみ出てしまうので、楕円のサイズを目玉の半径分だけ小さくして考える。

eye3.png

上記を考慮すると

x = (ra - er) * cos(θ)
y = (rb - er) * sin(θ)
var X := (FRA - FEyeR) * Cos(Theta);
var Y := (FRB - FEyeR) * Sin(Theta);

となる。

座標系に合せる

ここまでで数学上は X, Y が求まっているが、目玉の座標系は Delphi FireMonkey の座標系(左上原点)に合せなければならない。
座標系に合せた座標を (x', y') とすると

eye4.png

図のようになり、x は c 点からの座標であったので座表原点 o に合せるため ra 分を加算し、図形は左上が原点になるので er 分を引く。
つまり、

x' = x + ra - er
y' = y + rb - er

となる。

目玉の座標計算コード

ここまでをコードで書くと

procedure TExpoCell.SetEyePos(const AMouseX, AMouseY: Single);
begin
  var Theta := ArcTan2(AMouseY - FCenter.Y, AMouseX - FCenter.X);

  var X := (FRA - FEyeR) * Cos(Theta);
  var Y := (FRB - FEyeR) * Sin(Theta);

  X := X + FRA - FEyeR;
  Y := Y + FRB - FEyeR;

  FEye.SetBounds(X, Y, FEyeD, FEyeD); // FEyeD は目玉の直径
end;

となり、更に式変形して

procedure TExpoCell.SetEyePos(const AMouseX, AMouseY: Single);
begin
  var Theta := ArcTan2(AMouseY - FCenter.Y, AMouseX - FCenter.X);
  var X := (FRA - FEyeR) * (Cos(Theta) + 1);
  var Y := (FRB - FEyeR) * (Sin(Theta) + 1);

  FEye.SetBounds(X, Y, FEyeD, FEyeD);
end;

となる。
できあがったコードはスゴくシンプル!
こんなに短いのに目玉がちゃんとマウスを追いかける!

ソースコード一式

Delphi 10.4 Sydney Release 1 (Delphi 10.4.1) で制作。

まとめ

最近ゲームを作っていなかったので幾何学計算に手間取った!
これで、xeyes もどきが量産されれば幸い。

  1. sin(θ)^2 + cos(θ)^2 = 1

62
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?