数学の三角関数や、線形代数の行列を使っていろいろな動きを表現します。インプット、アウトプットが何なのか、コードを実行し、いろいろいじってみましょう。
昔買った参考書籍:DXライブラリ本「14歳からはじめる リアルに動く!ゲーム物理プログラミング教室 C言語編 Windows 98/2000/Me/XP/Vista対応(大槻有一郎著)」の目次を参考にして、三角関数と行列を使ってpyxelでのサンプルプログラムを作りました。今回はPymunkの出番はありません。
4.1 三角関数の基礎(sin, cosの意味と役割)
半径180の円周上を緑のボール(現在の角度の点半径5)が回ります。
sinの値を縦方向の長さとして表示(赤の線)
cosの値を横方向の長さとして表示(青の線)
画面640x480の中央に単位円(半径200ピクセル)を描画。
角度を0〜359度で毎フレーム変化させ、対応するcos, sinの値を計算。
cosは横方向の成分、sinは縦方向の成分として青線・赤線で表示。
単位円上の点が角度に応じて動き、三角関数の意味を視覚的に理解できる。
import pyxel
import math
class App:
def __init__(self):
pyxel.init(640, 480, title="4.1 三角関数の基礎 (sin, cos)")
self.angle_deg = 0 # 角度(度)
self.center = (320, 240)
self.radius = 180
pyxel.run(self.update, self.draw)
def update(self):
# 角度を毎フレーム少しずつ増やす(0〜359度をループ)
self.angle_deg = (self.angle_deg + 1) % 360
def draw(self):
pyxel.cls(0)
cx, cy = self.center
# 角度をラジアンに変換
angle_rad = math.radians(self.angle_deg)
# cos, sinの値を計算
cos_val = math.cos(angle_rad)
sin_val = math.sin(angle_rad)
# 単位円上の点の座標を計算
x = cx + self.radius * cos_val
y = cy - self.radius * sin_val # y軸は画面では下方向が正なので反転
# 中心点を描画
pyxel.circ(cx, cy, 3, 7)
# 単位円の円周を描画
pyxel.circb(cx, cy, self.radius, 8)
# 現在の角度の点を描画
pyxel.circ(int(x), int(y), 5, 11)
# cosの値を横方向の長さとして表示(青の線)
pyxel.line(cx, cy, int(cx + self.radius * cos_val), cy, 12)
# sinの値を縦方向の長さとして表示(赤の線)
pyxel.line(cx, cy, cx, int(cy - self.radius * sin_val), 8)
# テキスト表示
pyxel.text(5, 5, f"Angle: {self.angle_deg} deg", pyxel.COLOR_WHITE)
pyxel.text(5, 15, f"cos: {cos_val:.3f}", 12) # 青色
pyxel.text(5, 25, f"sin: {sin_val:.3f}", 8) # 赤色
App()
4.1.2 三角関数の基礎(sin, cosの意味と役割)
画面640x480の中央に単位円(半径200ピクセル)を描画。
角度を0〜359度で毎フレーム変化させ、対応するcos, sinの値を計算。
cosは横方向の成分、sinは縦方向の成分として青線・赤線で表示。
単位円上の点が角度に応じて動き、三角関数の意味を視覚的に理解できる。
import pyxel
import math
# 色定数の定義
COLOR_RED = 8
COLOR_BLUE = 5
COLOR_WHITE = 7
class UnitCircleVisualization:
def __init__(self):
# 画面サイズの設定
self.width = 640
self.height = 480
pyxel.init(self.width, self.height, title="Unit Circle Visualization")
# 円の半径と中心座標
self.radius = 200
self.center_x = self.width // 2
self.center_y = self.height // 2
# 回転角度(度数法)の初期値
self.angle = 0
# Pyxelメインループの開始
pyxel.run(self.update, self.draw)
def update(self):
# 角度を1度ずつ増加させ、360度で0に戻る
self.angle = (self.angle + 1) % 360
def draw(self):
# 背景をピーチ(15)でクリア
pyxel.cls(15)
# 単位円の描画(ライトブルー色6)
pyxel.circ(self.center_x, self.center_y, self.radius, 6)
# 角度をラジアンに変換
rad = math.radians(self.angle)
# cosとsinの値を計算
cos_val = math.cos(rad)
sin_val = math.sin(rad)
# 円周上の点の座標を計算
# px = 中心x + cos * 半径(横方向の位置)
# py = 中心y - sin * 半径(縦方向の位置、画面座標系は上が小さいため減算)
px = self.center_x + cos_val * self.radius
py = self.center_y - sin_val * self.radius
# cos成分を表す水平線(青)
pyxel.line(self.center_x, self.center_y, int(px), self.center_y, COLOR_BLUE)
# sin成分を表す垂直線(赤)
pyxel.line(int(px), self.center_y, int(px), int(py), COLOR_RED)
# 中心から円周上の点への半径線(黒)
pyxel.line(self.center_x, self.center_y, int(px), int(py), 0)
# 円周上の点にマーカーを描画(赤色8の円)
pyxel.circ(int(px), int(py), 7, 8)
# テキスト表示位置の設定
text_x = 5
text_y = 5
# 現在の角度と三角関数の値を表示
pyxel.text(text_x, text_y, f"Angle: {self.angle} deg", 0)
pyxel.text(text_x, text_y+10, f"cos: {cos_val:.3f}", COLOR_BLUE)
pyxel.text(text_x, text_y+20, f"sin: {sin_val:.3f}", COLOR_RED)
# プログラムの実行
UnitCircleVisualization()
- 単位円の可視化: 数学の三角関数(cos, sin)を視覚的に理解するための教材
- アニメーション: 角度が0度から359度まで自動的に回転
視覚要素:
- グレーの円: 単位円
- 青い横線: cos値(x軸方向の成分)
- 赤い縦線: sin値(y軸方向の成分)
- 黒い斜線: 半径(原点から円周上の点への線)
- 赤い点: 現在の角度における円周上の位置
pymunk未使用: このコードは物理シミュレーションではなく、純粋な数学的可視化のためpymunkは使用していません
4.2 ベクトルの回転:三角関数を使った角度変更
画面640x480
2Dベクトル (x, y) の定義
回転角度 θ(ラジアン)を決める
回転後のベクトルの計算式:
実際にベクトルを回転させる関数を作成
pyxelで元のベクトルと回転後のベクトルを視覚的に表示
左右矢印キー入力で回転角度を変更し動的に回転させる
import pyxel
import math
# 色定数の定義
COLOR_WHITE = 7
COLOR_RED = 8
COLOR_GREEN = 11
class VectorRotationApp:
def __init__(self):
# 画面サイズの設定
self.width = 640
self.height = 480
pyxel.init(self.width, self.height, title="Vector Rotation")
# 原点の座標(画面中央)
self.origin = (self.width // 2, self.height // 2)
# 元のベクトル(右向き、長さ240)
self.vector = (240, 0)
# 現在の回転角度(ラジアン)
self.angle = 0.0
# 1回の操作での回転角度(5度をラジアンに変換)
self.angle_step = math.radians(5)
# Pyxelメインループの開始
pyxel.run(self.update, self.draw)
def rotate_vector(self, v, theta):
"""
2次元ベクトルを回転行列で回転させる関数
回転行列:
| cos(θ) -sin(θ) |
| sin(θ) cos(θ) |
新しいx = x*cos(θ) - y*sin(θ)
新しいy = x*sin(θ) + y*cos(θ)
"""
x, y = v
cos_t = math.cos(theta)
sin_t = math.sin(theta)
return (x * cos_t - y * sin_t, x * sin_t + y * cos_t)
def update(self):
# 右矢印キー: 反時計回りに回転(角度を増加)
if pyxel.btn(pyxel.KEY_RIGHT):
self.angle += self.angle_step
# 左矢印キー: 時計回りに回転(角度を減少)
if pyxel.btn(pyxel.KEY_LEFT):
self.angle -= self.angle_step
def draw(self):
# 画面を黒でクリア
pyxel.cls(0)
# 原点座標を取得
ox, oy = self.origin
# 元のベクトルの成分を取得
vx, vy = self.vector
# 回転後のベクトルを計算
rx, ry = self.rotate_vector(self.vector, self.angle)
# 元のベクトル(赤色)を描画
# 画面座標系は上が小さいため、y成分は減算
pyxel.line(ox, oy, ox + int(vx), oy - int(vy), COLOR_RED)
# 回転後のベクトル(緑色)を描画
pyxel.line(ox, oy, ox + int(rx), oy - int(ry), COLOR_GREEN)
# 元のベクトルの終点にマーカー(赤)
pyxel.circ(ox + int(vx), oy - int(vy), 6, COLOR_RED)
# 回転後のベクトルの終点にマーカー(緑)
pyxel.circ(ox + int(rx), oy - int(ry), 6, COLOR_GREEN)
# 現在の回転角度を度数法で表示
pyxel.text(5, 5, f"Angle: {math.degrees(self.angle):.1f} deg", COLOR_WHITE)
# 操作説明の表示
pyxel.text(5, 15, "Left/Right keys to rotate", COLOR_WHITE)
# プログラムの実行
if __name__ == "__main__":
VectorRotationApp()
1.ベクトル回転の可視化: 2次元ベクトルの回転を視覚的に理解するための教材
2.インタラクティブ操作:
- 右矢印キー: 反時計回りに5度ずつ回転
- 左矢印キー: 時計回りに5度ずつ回転
3.視覚要素:
- 赤いベクトル: 元のベクトル(常に右向き)
- 緑のベクトル: 回転後のベクトル
- 赤/緑の点: 各ベクトルの終点
4.数学的概念:
- 回転行列: 2次元平面上でベクトルを回転させる標準的な方法
- x成分 = x×cos(θ) - y×sin(θ)
- y成分 = x×sin(θ) + y×cos(θ)
5.pymunk未使用: 物理シミュレーションではなく、数学的な幾何変換の可視化プログラム
4.3 2D回転行列の導入と実装
画面640x480
rotate_vector 関数で2D回転行列によるベクトル回転を実装。
update で角度を1度ずつ増やし、ベクトルを毎フレーム回転させる。
元のベクトル(緑)と回転後のベクトル(赤)を画面に表示。
import pyxel
import math
# 色定数の定義
COLOR_GREEN = 3
COLOR_RED = 8
class RotateVectorSimulation:
def __init__(self):
# 画面サイズの設定
self.width = 640
self.height = 480
pyxel.init(self.width, self.height, title="2D Vector Rotation")
# 回転角度の初期値(度数法)
self.angle = 0
# 基準ベクトル(右向き、長さ230)
self.base_vector = (230, 0)
# Pyxelメインループの開始
pyxel.run(self.update, self.draw)
def rotate_vector(self, vector, angle_deg):
"""
2次元ベクトルを指定角度(度数法)で回転させる関数
回転行列を使用:
新しいx = x*cos(θ) - y*sin(θ)
新しいy = x*sin(θ) + y*cos(θ)
"""
# 度数法からラジアンに変換
rad = math.radians(angle_deg)
# 三角関数の値を事前計算
cos_a = math.cos(rad)
sin_a = math.sin(rad)
# ベクトルの成分を取得
x, y = vector
# 回転行列を適用して新しいベクトルを返す
return (x * cos_a - y * sin_a, x * sin_a + y * cos_a)
def update(self):
# 角度を1度ずつ増加させ、360度で0に戻る(自動アニメーション)
self.angle = (self.angle + 1) % 360
def draw(self):
# 画面を黒でクリア
pyxel.cls(0)
# 原点(画面中央)の座標
origin_x = self.width // 2
origin_y = self.height // 2
# 基準ベクトルの成分を取得
vx, vy = self.base_vector
# 回転後のベクトルを計算
rx, ry = self.rotate_vector(self.base_vector, self.angle)
# 基準ベクトル(緑色)を描画
# 画面座標系は上が小さいため、y成分は減算
pyxel.line(origin_x, origin_y, origin_x + int(vx), origin_y - int(vy), COLOR_GREEN)
# 回転後のベクトル(赤色)を描画
pyxel.line(origin_x, origin_y, origin_x + int(rx), origin_y - int(ry), COLOR_RED)
# 軌跡を示す円(半径230、白の輪郭のみ)
pyxel.circb(origin_x, origin_y, 230, 1)
# 現在の回転角度を表示
pyxel.text(5, 5, f"Angle: {self.angle} deg", 7)
# プログラムの実行
if __name__ == "__main__":
RotateVectorSimulation()
1.自動回転アニメーション: 前のプログラムとの違いは、キー操作ではなく自動的に回転する点
2.視覚要素:
- 緑のベクトル: 基準ベクトル(常に右向き、固定)
- 赤のベクトル: 回転後のベクトル(1フレームごとに1度ずつ回転)
- 白い円: ベクトルの終点が描く軌跡(半径230の円周)
3.アニメーションの仕組み:
- update()で角度が毎フレーム1度ずつ増加
- 360度に達すると0度に戻り、無限ループ
4.数学的概念:
- ベクトルの回転は回転行列で実現
- 回転角度が変化することで、ベクトルが円周上を移動
5.用途: ベクトル回転の基礎を視覚的に理解するための教材(三角関数と回転行列の関係を示す)
4.4 行列を使った座標変換(平行移動、回転、拡大縮小)
画面640x480
平行移動はベクトルの足し算で実装することを説明
拡大縮小行列の定義:
回転行列、拡大縮小行列を組み合わせる方法を説明
同次座標を使った3x3行列で平行移動も含めた変換を紹介(応用)
実装としては、複数の行列を掛け合わせて合成変換行列を作成
同次座標(x, y, 1)と3x3行列を使い、平行移動、回転、拡大縮小を一つの合成行列として扱う。
Mat3 クラスで行列の生成、掛け算を実装。
拡大縮小→回転→平行移動の順に変換を合成。
変換前の点を変換して画面に描画。
変換パラメータは時間経過で変化し、動的に変形がわかる。
pyxelで図形の座標に対して変換を適用し描画
import pyxel
import math
class Mat3:
"""3×3の行列クラス(2D変換用の同次座標系)"""
def __init__(self, m=None):
"""
行列の初期化
m が None の場合は単位行列を生成
"""
if m is None:
# 単位行列(何も変換しない行列)
self.m = [
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]
]
else:
# 指定された行列をコピー
self.m = m
def __mul__(self, other):
"""
行列の乗算演算子のオーバーロード
self * other を計算
行列の乗算:
C[i][j] = Σ(k=0 to 2) A[i][k] * B[k][j]
"""
# 結果を格納する3×3行列を初期化
result = [[0.0]*3 for _ in range(3)]
# 行列の乗算を実行
for i in range(3):
for j in range(3):
s = 0.0
for k in range(3):
s += self.m[i][k] * other.m[k][j]
result[i][j] = s
return Mat3(result)
def transform_point(self, x, y):
"""
点(x, y)を行列で変換
同次座標系での変換:
[x'] [m00 m01 m02] [x]
[y'] = [m10 m11 m12] * [y]
[1 ] [m20 m21 m22] [1]
"""
tx = self.m[0][0]*x + self.m[0][1]*y + self.m[0][2]
ty = self.m[1][0]*x + self.m[1][1]*y + self.m[1][2]
return tx, ty
class TransformDemo:
def __init__(self):
# 画面サイズの設定
self.width = 640
self.height = 480
pyxel.init(self.width, self.height, title="Matrix Transform Demo")
# アニメーション用の時間変数
self.t = 0.0
# 変換対象の四角形の頂点(原点基準)
# 100×100の正方形
self.base_points = [(0, 0), (100, 0), (100, 100), (0, 100)]
# Pyxelメインループの開始
pyxel.run(self.update, self.draw)
def update(self):
# 時間を進める(アニメーション速度の調整)
self.t += 0.02
# Qキーで終了
if pyxel.btnp(pyxel.KEY_Q):
pyxel.quit()
def draw(self):
# 画面を黒でクリア
pyxel.cls(0)
# スケール値(1.0を中心に±0.5で振動)
s = 1.0 + 0.5 * math.sin(self.t * 2.0)
# 回転角度(時間に応じて一定速度で回転)
angle = self.t * 2.0 * math.pi
# 移動先の座標(円軌道上を移動)
tx = 320 + 100 * math.cos(self.t)
ty = 240 + 100 * math.sin(self.t)
# スケール変換行列(拡大縮小)
# [s 0 0]
# [0 s 0]
# [0 0 1]
scale_mat = Mat3([
[s, 0, 0],
[0, s, 0],
[0, 0, 1]
])
# 三角関数の値を事前計算
cos_a = math.cos(angle)
sin_a = math.sin(angle)
# 回転変換行列
# [cos(θ) -sin(θ) 0]
# [sin(θ) cos(θ) 0]
# [0 0 1]
rot_mat = Mat3([
[cos_a, -sin_a, 0],
[sin_a, cos_a, 0],
[0, 0, 1]
])
# 平行移動変換行列
# [1 0 tx]
# [0 1 ty]
# [0 0 1 ]
trans_mat = Mat3([
[1, 0, tx],
[0, 1, ty],
[0, 0, 1]
])
# 変換行列の合成(右から順に適用される)
# 1. スケール変換
# 2. 回転変換
# 3. 平行移動変換
transform = trans_mat * rot_mat * scale_mat
# 各頂点を変換
points = []
for x, y in self.base_points:
# 変換行列を適用
px, py = transform.transform_point(x, y)
# 整数座標に変換
px_int = int(px)
# 画面座標系に変換(上下反転)
py_int = int(self.height - py)
points.append((px_int, py_int))
# 変換後の四角形を描画(各辺を線で結ぶ)
for i in range(len(points)):
x0, y0 = points[i]
# 次の点(最後の点は最初の点に戻る)
x1, y1 = points[(i+1) % len(points)]
pyxel.line(x0, y0, x1, y1, 7)
# プログラムの実行
if __name__ == "__main__":
TransformDemo()
1.3×3行列クラス (Mat3):
- 2D変換を表現するための同次座標系の行列
- 行列の乗算演算子をオーバーロード
- 点の変換メソッドを提供
2.複合変換のアニメーション:
- スケール: sin波で拡大縮小(0.5〜1.5倍)
- 回転: 一定速度で回転
- 平行移動: 円軌道上を移動
3.変換の順序:
- transform = trans_mat * rot_mat * scale_mat
- 右から順に適用: スケール → 回転 → 平行移動
- この順序が重要(順序が変わると結果が変わる)
4.数学的概念:
- 同次座標系: 2D変換を3×3行列で統一的に表現
- 行列の合成: 複数の変換を1つの行列にまとめられる
5.用途: 線形代数と幾何変換の理解を深める教材
pyxel.tri()を使った四角形に適応してください。塗りは赤。
import pyxel
import math
class Mat3:
def __init__(self, m=None):
if m is None:
self.m = [
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]
]
else:
self.m = m
def __mul__(self, other):
result = [[0.0]*3 for _ in range(3)]
for i in range(3):
for j in range(3):
s = 0.0
for k in range(3):
s += self.m[i][k] * other.m[k][j]
result[i][j] = s
return Mat3(result)
def transform_point(self, x, y):
tx = self.m[0][0]*x + self.m[0][1]*y + self.m[0][2]
ty = self.m[1][0]*x + self.m[1][1]*y + self.m[1][2]
return tx, ty
class TransformDemo:
def __init__(self):
self.width = 640
self.height = 480
pyxel.init(self.width, self.height, title="Matrix Transform Demo")
self.t = 0.0
self.base_points = [(0, 0), (100, 0), (100, 100), (0, 100)]
self.COLOR_RED = 8
pyxel.run(self.update, self.draw)
def update(self):
self.t += 0.02
if pyxel.btnp(pyxel.KEY_Q):
pyxel.quit()
def draw(self):
pyxel.cls(0)
s = 1.0 + 0.5 * math.sin(self.t * 2.0)
angle = self.t * 2.0 * math.pi
tx = 320 + 100 * math.cos(self.t)
ty = 240 + 100 * math.sin(self.t)
scale_mat = Mat3([
[s, 0, 0],
[0, s, 0],
[0, 0, 1]
])
cos_a = math.cos(angle)
sin_a = math.sin(angle)
rot_mat = Mat3([
[cos_a, -sin_a, 0],
[sin_a, cos_a, 0],
[0, 0, 1]
])
trans_mat = Mat3([
[1, 0, tx],
[0, 1, ty],
[0, 0, 1]
])
transform = trans_mat * rot_mat * scale_mat
verts = []
for x, y in self.base_points:
px, py = transform.transform_point(x, y)
px_int = int(px)
py_int = int(self.height - py)
verts.append((px_int, py_int))
v0, v1, v2, v3 = verts
pyxel.tri(v0[0], v0[1], v1[0], v1[1], v2[0], v2[1], self.COLOR_RED)
pyxel.tri(v2[0], v2[1], v3[0], v3[1], v0[0], v0[1], self.COLOR_RED)
if __name__ == "__main__":
TransformDemo()
4.5 行列とベクトルの掛け算で形を変える
画面640x480
複数の頂点を持つ図形(多角形)の定義
各頂点に行列変換を適用して座標を更新
拡大縮小や回転を組み合わせて形状の変形を実装
pyxelで変形前と変形後の図形を描画し比較
実時間で変換パラメータを変えてアニメーション化
正方形の4頂点を同次座標で定義。
拡大縮小、回転、平行移動を合成した変換行列を作成。
各頂点に変換行列を掛けて形状を変形。
pyxelで変形前の形ではなく変形後の多角形を描画。
時間経過で変換パラメータが変わり、形が動的に変化する。
import pyxel
import math
class Mat3:
"""3x3行列クラス(同次座標変換用)"""
def __init__(self, m=None):
if m:
self.m = m
else:
self.m = [
[1,0,0],
[0,1,0],
[0,0,1]
]
def __mul__(self, other):
"""行列×行列 または 行列×ベクトル(同次座標)の掛け算"""
if isinstance(other, Mat3):
result = [[0]*3 for _ in range(3)]
for i in range(3):
for j in range(3):
for k in range(3):
result[i][j] += self.m[i][k] * other.m[k][j]
return Mat3(result)
elif isinstance(other, (list, tuple)) and len(other) == 3:
res = [0,0,0]
for i in range(3):
for j in range(3):
res[i] += self.m[i][j] * other[j]
return res
else:
raise TypeError("Unsupported multiplication")
@staticmethod
def translation(tx, ty):
return Mat3([
[1,0,tx],
[0,1,ty],
[0,0,1]
])
@staticmethod
def rotation(angle_rad):
c = math.cos(angle_rad)
s = math.sin(angle_rad)
return Mat3([
[c,-s,0],
[s, c,0],
[0, 0,1]
])
@staticmethod
def scaling(sx, sy):
return Mat3([
[sx,0, 0],
[0, sy,0],
[0, 0, 1]
])
class App:
def __init__(self):
pyxel.init(256, 256, title="4.5 行列×ベクトルで形を変える")
self.center = (128, 128)
# 正方形の4頂点(同次座標)
self.shape = [
[-30, -30, 1],
[30, -30, 1],
[30, 30, 1],
[-30, 30, 1]
]
self.angle_deg = 0
self.scale_x = 1.0
self.scale_y = 1.0
self.tx = 0
self.ty = 0
pyxel.run(self.update, self.draw)
def update(self):
self.angle_deg = (self.angle_deg + 1) % 360
self.scale_x = 1.0 + 0.5 * math.sin(math.radians(self.angle_deg))
self.scale_y = 1.0 + 0.5 * math.cos(math.radians(self.angle_deg))
self.tx = 30 * math.cos(math.radians(self.angle_deg))
self.ty = 30 * math.sin(math.radians(self.angle_deg))
def draw(self):
pyxel.cls(0)
cx, cy = self.center
# 合成変換行列
S = Mat3.scaling(self.scale_x, self.scale_y)
R = Mat3.rotation(math.radians(self.angle_deg))
T = Mat3.translation(self.tx, self.ty)
M = T * R * S
# 変換後の頂点を計算
transformed = [M * v for v in self.shape]
# 頂点を画面座標に変換(中心基準)
points = [(int(cx + v[0]), int(cy - v[1])) for v in transformed]
# 多角形を描画
for i in range(len(points)):
x1, y1 = points[i]
x2, y2 = points[(i+1) % len(points)]
pyxel.line(x1, y1, x2, y2, 11)
# 中心点描画
pyxel.circ(cx, cy, 3, 7)
# テキスト表示
pyxel.text(5, 5, f"Angle: {self.angle_deg} deg", pyxel.COLOR_WHITE)
pyxel.text(5, 15, f"Scale X: {self.scale_x:.2f}", pyxel.COLOR_WHITE)
pyxel.text(5, 25, f"Scale Y: {self.scale_y:.2f}", pyxel.COLOR_WHITE)
pyxel.text(5, 35, f"Translate: ({self.tx:.1f}, {self.ty:.1f})", pyxel.COLOR_WHITE)
App()
import pyxel
import math
class MatrixVectorTransform:
def __init__(self):
# 画面サイズの設定
self.width = 640
self.height = 480
pyxel.init(self.width, self.height, title="Matrix Vector Transform")
# 正方形のサイズ
self.size = 100
# 画面中央の座標
self.center = (self.width // 2, self.height // 2)
# 正方形の4つの頂点(同次座標系: [x, y, 1])
# 原点を中心とした100×100の正方形
self.vertices = [
[ -self.size / 2, self.size / 2, 1], # 左上
[ self.size / 2, self.size / 2, 1], # 右上
[ self.size / 2, -self.size / 2, 1], # 右下
[ -self.size / 2, -self.size / 2, 1] # 左下
]
# アニメーション用の時間変数
self.time = 0.0
# Pyxelメインループの開始
pyxel.run(self.update, self.draw)
def mat_mult(self, m, v):
"""
3×3行列と3次元ベクトル(同次座標)の乗算
[x'] [m00 m01 m02] [x]
[y'] = [m10 m11 m12] * [y]
[w'] [m20 m21 m22] [w]
結果を同次座標として正規化(w で除算)
"""
# 行列とベクトルの積を計算
x = m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]*v[2]
y = m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]*v[2]
w = m[2][0]*v[0] + m[2][1]*v[1] + m[2][2]*v[2]
# w が 0 でない場合は同次座標を正規化
if w != 0:
return [x / w, y / w, 1]
else:
return [x, y, 1]
def update(self):
# 時間を進める(60FPS想定で1/60秒ずつ)
self.time += 1 / 60.0
def draw(self):
# 画面を黒でクリア
pyxel.cls(0)
# 回転角度(時間に応じて増加)
angle = self.time
# X軸方向のスケール(1.0を中心に±0.5で振動)
scale_x = 1 + 0.5 * math.sin(self.time * 2)
# Y軸方向のスケール(X軸とは異なる周期で振動)
scale_y = 1 + 0.5 * math.cos(self.time * 2)
# X方向の平行移動量(円軌道のX成分)
tx = 100 * math.cos(self.time)
# Y方向の平行移動量(楕円軌道のY成分)
ty = 50 * math.sin(self.time * 1.5)
# 三角関数の値を事前計算
cos_a = math.cos(angle)
sin_a = math.sin(angle)
# スケール変換行列(X軸とY軸で異なるスケール)
# [sx 0 0]
# [0 sy 0]
# [0 0 1]
scale_matrix = [
[scale_x, 0, 0],
[0, scale_y, 0],
[0, 0, 1]
]
# 回転変換行列
# [cos(θ) -sin(θ) 0]
# [sin(θ) cos(θ) 0]
# [0 0 1]
rotation_matrix = [
[cos_a, -sin_a, 0],
[sin_a, cos_a, 0],
[0, 0, 1]
]
# 平行移動変換行列
# [1 0 tx]
# [0 1 ty]
# [0 0 1 ]
translation_matrix = [
[1, 0, tx],
[0, 1, ty],
[0, 0, 1]
]
def mat_mult3(a, b):
"""
3×3行列同士の乗算
C[i][j] = Σ(k=0 to 2) A[i][k] * B[k][j]
"""
# 結果を格納する3×3行列を初期化
r = [[0,0,0],[0,0,0],[0,0,0]]
# 行列の乗算を実行
for i in range(3):
for j in range(3):
s = 0
for k in range(3):
s += a[i][k]*b[k][j]
r[i][j] = s
return r
# 変換行列の合成(右から順に適用される)
# 1. スケール変換
# 2. 回転変換
# 3. 平行移動変換
transform = mat_mult3(translation_matrix, mat_mult3(rotation_matrix, scale_matrix))
# 各頂点に変換行列を適用
transformed = [self.mat_mult(transform, v) for v in self.vertices]
# 変換後の座標を画面座標系に変換
# - 画面中央を原点とする
# - Y軸を反転(数学座標系→画面座標系)
verts = [(int(self.center[0] + v[0]), int(self.center[1] - v[1])) for v in transformed]
# 画面中央に原点のマーカーを描画(白い点)
pyxel.circ(self.center[0], self.center[1], 3, 7)
# 変換後の正方形を2つの三角形で描画(赤色)
# 三角形1: 頂点0-1-2
pyxel.tri(verts[0][0], verts[0][1], verts[1][0], verts[1][1], verts[2][0], verts[2][1], 8)
# 三角形2: 頂点2-3-0
pyxel.tri(verts[2][0], verts[2][1], verts[3][0], verts[3][1], verts[0][0], verts[0][1], 8)
# プログラムの実行
if __name__ == "__main__":
MatrixVectorTransform()
1.同次座標系による変換:
- 頂点を [x, y, 1] の3次元ベクトルとして表現
- すべての変換(スケール、回転、平行移動)を3×3行列で統一的に扱う
2.複合アニメーション:
- 非均等スケール: X軸とY軸で異なる速度で拡大縮小(正方形が長方形に変形)
- 回転: 一定速度で回転
- 楕円軌道移動: 円ではなく楕円軌道上を移動
3.行列演算の実装:
- mat_mult: 行列×ベクトルの乗算(同次座標の正規化を含む)
- mat_mult3: 行列×行列の乗算(ローカル関数として定義)
4.描画の工夫:
- 正方形を2つの三角形に分割して描画
- 画面中央に原点マーカーを表示
5.前のプログラムとの違い:
4.6 実例:図形の回転アニメーション
画面640x480
正六角形の頂点を同次座標で定義。
毎フレーム回転角度を増加させ、回転行列で頂点を回転変換。
回転後の頂点を画面座標に変換し、多角形を描画。
中心点と回転角度も表示し、回転の様子を視覚的に確認できる。
import pyxel
import math
# 色定数の定義
COLOR_WHITE = 7
COLOR_RED = 8
# 画面サイズの定数
WIDTH = 640
HEIGHT = 480
class RotateHexagon:
def __init__(self):
# Pyxelの初期化
pyxel.init(WIDTH, HEIGHT, title="Rotate Hexagon")
# 六角形の中心座標(画面中央)
self.center_x = WIDTH // 2
self.center_y = HEIGHT // 2
# 回転角度の初期値(ラジアン)
self.angle = 0.0
# 六角形の半径(中心から頂点までの距離)
self.radius = 100
# 六角形の頂点リスト(同次座標系: [x, y, 1])
self.vertices = []
# 正六角形の6つの頂点を生成
for i in range(6):
# 各頂点の角度(60度 = π/3 ラジアンずつ)
theta = math.pi / 3 * i
# 極座標から直交座標への変換
x = self.radius * math.cos(theta)
y = self.radius * math.sin(theta)
# 同次座標として頂点を追加
self.vertices.append((x, y, 1))
# Pyxelメインループの開始
pyxel.run(self.update, self.draw)
def update(self):
# 回転角度を0.05ラジアンずつ増加
self.angle += 0.05
# 角度が2πを超えたら0に戻す(角度の正規化)
if self.angle > 2 * math.pi:
self.angle -= 2 * math.pi
def draw(self):
# 画面を黒でクリア
pyxel.cls(0)
# 現在の回転角度に対応する三角関数の値を計算
cos_a = math.cos(self.angle)
sin_a = math.sin(self.angle)
# 回転後の頂点座標を格納するリスト
rotated_points = []
# 各頂点を回転変換
for x, y, w in self.vertices:
# 回転行列を適用
# rx = cos(θ) * x - sin(θ) * y
# ry = sin(θ) * x + cos(θ) * y
rx = cos_a * x - sin_a * y
ry = sin_a * x + cos_a * y
# 回転後の座標を画面座標系に変換
# 画面中央を原点とするため、center_x と center_y を加算
screen_x = int(self.center_x + rx)
screen_y = int(self.center_y + ry)
# 画面座標を保存
rotated_points.append((screen_x, screen_y))
# 六角形の各辺を描画
for i in range(6):
# 現在の頂点
x0, y0 = rotated_points[i]
# 次の頂点(最後の頂点の次は最初の頂点)
x1, y1 = rotated_points[(i + 1) % 6]
# 頂点間を赤い線で結ぶ
pyxel.line(x0, y0, x1, y1, COLOR_RED)
# 六角形の中心座標を表示
pyxel.text(10, 10, f"Center: ({self.center_x}, {self.center_y})", COLOR_WHITE)
# 現在の回転角度を表示
pyxel.text(10, 20, f"Angle: {self.angle:.2f} rad", COLOR_WHITE)
# プログラムの実行
RotateHexagon()
1.正六角形の生成:
- 正六角形の頂点は、中心から等距離(半径100)に配置
- 各頂点の角度は60度(π/3ラジアン)ずつ増加
- 極座標 (r, θ) から直交座標 (x, y) への変換を使用
2.回転アニメーション:
- 毎フレーム0.05ラジアンずつ回転
- 2π(360度)を超えたら0に戻す(オーバーフロー防止)
3.回転変換の実装:
- 回転行列を各頂点に適用:
- rx = cos(θ) * x - sin(θ) * y
- ry = sin(θ) * x + cos(θ) * y
4.回転後の座標を画面中央を基準に配置
- 描画方法:
- 6つの頂点を順番に線で結ぶことで六角形を描画
- 最後の頂点と最初の頂点も結ぶ((i + 1) % 6で実現)
5.特徴:
- 同次座標 (x, y, 1) を使用しているが、実際には2D回転のみ
- Y軸の反転がないため、数学的な座標系と画面座標系が一致
- 数学的背景: 正n角形の頂点は θ = 2π/n * i(i = 0, 1, ..., n-1)で配置される
以下、pyxel.tri()を使って塗りを青で表示するコードです:
import pyxel
import math
# 色定数の定義
COLOR_WHITE = 7
COLOR_RED = 8
COLOR_BLUE = 5 # 青色を追加
# 画面サイズの定数
WIDTH = 640
HEIGHT = 480
class RotateHexagon:
def __init__(self):
# Pyxelの初期化
pyxel.init(WIDTH, HEIGHT, title="Rotate Hexagon")
# 六角形の中心座標(画面中央)
self.center_x = WIDTH // 2
self.center_y = HEIGHT // 2
# 回転角度の初期値(ラジアン)
self.angle = 0.0
# 六角形の半径(中心から頂点までの距離)
self.radius = 100
# 六角形の頂点リスト(同次座標系: [x, y, 1])
self.vertices = []
# 正六角形の6つの頂点を生成
for i in range(6):
# 各頂点の角度(60度 = π/3 ラジアンずつ)
theta = math.pi / 3 * i
# 極座標から直交座標への変換
x = self.radius * math.cos(theta)
y = self.radius * math.sin(theta)
# 同次座標として頂点を追加
self.vertices.append((x, y, 1))
# Pyxelメインループの開始
pyxel.run(self.update, self.draw)
def update(self):
# 回転角度を0.05ラジアンずつ増加
self.angle += 0.05
# 角度が2πを超えたら0に戻す(角度の正規化)
if self.angle > 2 * math.pi:
self.angle -= 2 * math.pi
def draw(self):
# 画面を黒でクリア
pyxel.cls(0)
# 現在の回転角度に対応する三角関数の値を計算
cos_a = math.cos(self.angle)
sin_a = math.sin(self.angle)
# 回転後の頂点座標を格納するリスト
rotated_points = []
# 各頂点を回転変換
for x, y, w in self.vertices:
# 回転行列を適用
# rx = cos(θ) * x - sin(θ) * y
# ry = sin(θ) * x + cos(θ) * y
rx = cos_a * x - sin_a * y
ry = sin_a * x + cos_a * y
# 回転後の座標を画面座標系に変換
# 画面中央を原点とするため、center_x と center_y を加算
screen_x = int(self.center_x + rx)
screen_y = int(self.center_y + ry)
# 画面座標を保存
rotated_points.append((screen_x, screen_y))
# 六角形を中心点から各辺への三角形で塗りつぶす
# 中心点の座標
center = (self.center_x, self.center_y)
# 6つの三角形を描画して六角形を塗りつぶす
for i in range(6):
# 現在の頂点
x0, y0 = rotated_points[i]
# 次の頂点(最後の頂点の次は最初の頂点)
x1, y1 = rotated_points[(i + 1) % 6]
# 中心点と2つの隣接する頂点で三角形を描画(青色で塗りつぶし)
pyxel.tri(center[0], center[1], x0, y0, x1, y1, COLOR_BLUE)
# 六角形の輪郭を赤い線で描画(塗りつぶしの上に重ねる)
for i in range(6):
# 現在の頂点
x0, y0 = rotated_points[i]
# 次の頂点(最後の頂点の次は最初の頂点)
x1, y1 = rotated_points[(i + 1) % 6]
# 頂点間を赤い線で結ぶ
pyxel.line(x0, y0, x1, y1, COLOR_RED)
# 六角形の中心座標を表示
pyxel.text(10, 10, f"Center: ({self.center_x}, {self.center_y})", COLOR_WHITE)
# 現在の回転角度を表示
pyxel.text(10, 20, f"Angle: {self.angle:.2f} rad", COLOR_WHITE)
# プログラムの実行
RotateHexagon()
1.色定数の追加: COLOR_BLUE = 5 を追加
2.塗りつぶしの実装:
- 六角形を6つの三角形に分割
- 各三角形は「中心点」と「隣接する2つの頂点」で構成
- pyxel.tri()で青色(COLOR_BLUE)で塗りつぶし
3.描画順序:
- 先に青色の塗りつぶし(三角形)を描画
- その後、赤色の輪郭線を描画(見やすくするため)
4.六角形の三角形分割:
- 三角形0: 中心 - 頂点0 - 頂点1
- 三角形1: 中心 - 頂点1 - 頂点2
- 三角形2: 中心 - 頂点2 - 頂点3
- ...
- 三角形5: 中心 - 頂点5 - 頂点0
以下の六角形は塗りに空きがあります。中心点を基準に、扇形の三角形6個で六角形を構成するように、青い塗りを入れてください。
import pyxel
import math
COLOR_ORANGE = 9
COLOR_WHITE = 7
COLOR_BLUE = 5
WIDTH = 640
HEIGHT = 480
class RotateHexagon:
def __init__(self):
pyxel.init(WIDTH, HEIGHT, title="Rotate Hexagon")
self.center_x = WIDTH // 2
self.center_y = HEIGHT // 2
self.angle = 0.0
self.radius = 100
self.vertices = []
for i in range(6):
theta = math.pi / 3 * i
x = self.radius * math.cos(theta)
y = self.radius * math.sin(theta)
self.vertices.append((x, y, 1))
pyxel.run(self.update, self.draw)
def update(self):
self.angle += 0.05
if self.angle > 2 * math.pi:
self.angle -= 2 * math.pi
def draw(self):
pyxel.cls(0)
cos_a = math.cos(self.angle)
sin_a = math.sin(self.angle)
rotated_points = []
for x, y, w in self.vertices:
rx = cos_a * x - sin_a * y
ry = sin_a * x + cos_a * y
screen_x = int(self.center_x + rx)
screen_y = int(self.center_y + ry)
rotated_points.append((screen_x, screen_y))
cx = self.center_x
cy = self.center_y
# ステップ1: 青色の六角形を描画(中心から放射状の6つの三角形)
for i in range(6):
x0, y0 = rotated_points[i]
x1, y1 = rotated_points[(i + 1) % 6]
pyxel.tri(cx, cy, x0, y0, x1, y1, COLOR_BLUE)
# ステップ2: オレンジ色の三角形を上に重ねて描画(連続3頂点)
for i in range(4):
x0, y0 = rotated_points[i]
x1, y1 = rotated_points[i + 1]
x2, y2 = rotated_points[i + 2]
pyxel.tri(x0, y0, x1, y1, x2, y2, COLOR_ORANGE)
# ステップ3: 最後のオレンジ三角形
pyxel.tri(rotated_points[4][0], rotated_points[4][1], rotated_points[5][0], rotated_points[5][1], rotated_points[0][0], rotated_points[0][1], COLOR_ORANGE)
pyxel.text(10, 10, f"Center: ({self.center_x}, {self.center_y})", COLOR_WHITE)
pyxel.text(10, 20, f"Angle: {self.angle:.2f} rad", COLOR_WHITE)
RotateHexagon()
重ね描きをしてしまった。
描画される三角形の詳細:
青色(6個の三角形):
中心-頂点0-頂点1
中心-頂点1-頂点2
中心-頂点2-頂点3
中心-頂点3-頂点4
中心-頂点4-頂点5
中心-頂点5-頂点0
オレンジ色(5個の三角形、青の上に重ねる):
頂点0-頂点1-頂点2
頂点1-頂点2-頂点3
頂点2-頂点3-頂点4
頂点3-頂点4-頂点5
頂点4-頂点5-頂点0
結果
完全に重ね描きしています
先に青色で六角形全体を塗る
その後、オレンジ色の三角形が部分的に青を覆い隠す
最終的に見えるのは「青とオレンジの組み合わせ」
Pyxelは後から描いたものが上に表示される(ペインターアルゴリズム)
このコードは無駄な描画をしているため、青色のみまたはオレンジ色のみにするか、意図的に2色のパターンを作りたい場合以外は、どちらか一方を削除すべきです。
「コードを書くという面倒な部分はAIに手伝ってもらうが、そのコードがなぜそう動くのかを理解するのは、人の役割である」
一歩一歩です。ありがとうございます。





