星形多角形
このワークシートはMath by Codeの一部です。
アート、背景、実装、バリエーションの順に見ていきましょう。
<星形多角形>
円をかいて、等分点をうちます。
等分点を同じずつ進んで結びます。
もし、M等分してNずつ進むと、星形多角形MNができます。
これは分数 に対応しますね。
星形多角形と分数
1.背景(basics & backgrounds)
星形多角形は
単純なしくみでキレイな図形なので、
geogebraのアプリで「星形多角形」で検索すると、
とても多くヒットしますね。
多くの人の心をひきつけるということですね。
<数論の視点では>
中学入試の算数の問題でも取り上げられることがあります。
星形多角形は倍数と約数の関係がからむからです。
質問:N等分点をMずつ進むとどんな規則性があるでしょうか?
1「MとNが互いに素だとN角形になる。」
(例)
12点を1点ずつ進むと正12角形。
(例)
12点を5点ずつ進むと星形12角形。
(理由)頂点に0から11まで番号をつけると、
0から0に戻るのは5と12の最小公倍数の60点進んだときだね。
60÷5=12個めの点が0だから、12個の頂点をすべてかぶらず進み12個めに0にかぶる。
だから、12点すべてを結ぶ星形12角形ができる。
2「MとNの最大公約数がgのとき、N/g角形をずらしてg個かける。」
(例)
12点を2点ずつ進むと正12/2=6角形。
(理由)頂点に0から11まで番号をつけると、
0から0に戻るのは2と12の最小公倍数の12点進んだときだね。
12÷2=6点めの点が0だから、6個の頂点をかぶらずに進んで6個めに0にかぶる。
だから、6点を結ぶ6角形ができる。
(例)
12点を3点ずつ進むと正12/3=4角形。4角形が3組できる。
(例)
12点を8点ずつ進むとg(12,8)=4から、正12/4=3角形。3角形が4組できる。
3「L+M=Nなら、できるL角形とM角形は合同になる。」
(例)
12点を1点ずつ進んでも、12-1=11点ずつ進んでも、同じ正12角形ができる。
(理由)頂点に0から11まで番号をつけると、
0,1,2,3,....の順に進むのに対して0,11,10,9,...の順に進むことは、進む向きが逆になっただけ。
11点進むことは、逆向きに1進むことになる。できる多角形は進む数で決まるので、裏返しただけで
合同な多角形になるね。対称性の感覚につながり、面白いね。
(例)
12点を3点ずつ進んでも、12-3=9点ずつ進んでも、同じ正12/3=4角形ができる。
・こうして考察すると、中学入試の算数の問題でも取り上げられる理由がわかるね。
星形多角形は公倍数、公約数の考え方、互いに素の理解を
図形を使って試すことができる題材だから出題されてきたということだね。
<星形多角形の歴史>
星形多角形は、ルドルフ・シュタイナーという教育者が体の動き・視覚的な動きの体験を動きの想像によって抽象概念の形成を促す素材として考案したものという説があります。
シュタイナー教育は、リズム体操、手拍子と発声、床に描いた星形5角形の辺上の移動など5感を使って、倍数、公倍数、公約数などの数の性質の概念理解を促す進歩的な側面と、血液型とは別ですが人間を4つの気質に分類する気質決定論の側面が見られます。
<図形の視点では>
星形多角形は図形なので、内角の数や内角の総和とも関係します。
中学入試問題でも、星形5角形の内角の和をはじめとする変則多角形が出題されることがあります。
次の2つの図を利用して、一般化につながる考察をしましょう。
さっきの3番の事実から、対称性を利用してMはN/2未満とします。
たとえば、円の5等分点を進むとき、スタートを1として、進む順に2,3とつけます。
三角形123は二等辺三角形になるね。
5角形の中心Oをとると、角1O2,2O3は等しい。
角の大きさはM=5等分点をN進む場合は、360÷N×Mになるね。
二等辺三角形だから角O12=角O21=(180-360÷N×M)/2=90-180M/N
だから、1つの内角、角123はこの2倍になり、180-360M/N
1.MとNが互いに素の場合の星形M角形の内角の和は
(180-360M/N)×N=180N-360M=180(N-2M)
(例)
5等分点を2点ずつ進む星形5角形の内角の和は180(5-2×2)=180。
(別解)5つの内角を延長して外角をつけたした、5角の和は180×5でこれから、外角は2回転するので和360×2をひくと、180×5-180×2×2=180(5-2×2)
(例)
星形M角形の内角和が180になる条件を探ってみる。
Nが奇数つまり、N=2n+1とするとき、N-2M=2n+1-2M=1となるのは、
M=nのときだ。M=(N-1)/2と求められるね。
だから、5点ならN=(5-1)/2=2。7点ならN=(7-1)/2=3。
2.MとNの最大公約数がgの場合の星形N/g角形の内角の和は180(N-2M)/g
ずらしてかくと、N/g角形がg個かけるから、和の合計は180(N-2M)
(例)
12等分点を2点ずつ進む星形6角形の内角の和は180(12-2*2)/2=720で、ずらすともう一組できる。
結局、2組の和の合計は1番と同じ180(N-2M)。
2.実装( math & codes)
星形多角形をかくための「数学」とそれを実現するための「道具」を調べましょう。
<数学>
数学としては高校程度の基礎知識が必要です。
(3平方の定理)
直角三角形の直角をはさむ辺a,bと残りの辺cの間にa2+b2=c2。
(単位円の方程式)
原点O(0,0)と点A(x、y)の距離の2乗がx2+y2=1なら点Aは半径1の円(単位円)周上の点。
(三角比から三角関数へ)
Bが直角の直角三角形AOBのAO=1、角AOB=◎度とするとき、OB=cos◎、AB=sin◎とかける。
(単位円周の点の座標)
x軸に対する角AOxを◎度とすると、単位円周上の点Aの座標は(cos◎, sin◎)。
以上の基礎知識を前提にすると、星形多角形を数式化できますね。
(円のN等分点の座標)
x軸をスタートにすると次の点は360度を1周とすると(cos(360/N), sin(360/N)),
この角度を0倍からN-1倍にするか、1倍からN倍にする点列を作ればよいね。
だから、点の座標の数列PListはたとえば、次のようになるね。
PList=Sequence(point(cos((360°/n) (i-1)+90°),sin((360°/n) (i-1)+90°)),i,1,n)
(円のM等分点をN進む点の座標)
x軸から1回で進んだ点の座標は、
N等分点の座標のM倍をするから、(cos((360/N) M), sin((360/N) M))
また、スタートをx軸の(1,0)ではなく、y軸の(0,1)にしたいならば、
回転角を90度先にまわしておくことが必要になります。
だから、角度に90を加えればよいですね。
進むことをk回したあとの点は(cos((360/N) k M+90), sin((360/N) k M+90))
となり、k=Nにしたときに、スタートの点(0,1)にかぶるね。
通常の環境では、角度360度を弧度2πに直せばよいね。
<道具>
質問:geogebraで星形多角形をかくにはどういう数式にしたらよいでしょうか。
分母のスライダーn (たとえば、4から40で増分1)
分子のスライダーm(たとえば、1からn-1の増分1)たとえば、アニメーションをOnにする。
スタートボタンは「設定」の「スクリプト記述」の「クリックして」に以下のコマンドを入力します。
StartAnimation[m]
停止ボタンは「設定」の「スクリプト記述」の「クリックして」に以下のコマンドを入力します。
StartAnimation[false]
次は点列と辺列を「数式」にしましょう。
・単位円のn等分点の点列を「数式」に入力します。
PList=Sequence(point(cos((360°/n) (i-1)+90°),sin((360°/n) (i-1)+90°)),i,1,n)
・多角形の辺列を入力します。
点列PListから、始点よりmすすんだ点を終点にし、始点をmずつ増やします。
だから、辺の設定は、Segment(Element(PList,Mod(i-1,n)+1),Element(PList,Mod(i-1+m,n)+1))
となりますね。
一本目はi=1にしたとき、Segment(Element(PList,1),Element(PList,Mod(m,n)+1))
二本目はi=1+mから、Segment(Element(PList,Mod(m,n)+1),Element(PList,Mod(m+m,n)+1))
三本目はi=1+2mとして、Segment(Element(PList,Mod(2m,n)+1),Element(PList,Mod(3m,n)+1))
...........
j+1本目はi=1+jmとなり、Segment(Element(PList,Mod(jm,n)+1),Element(PList,Mod((j+1)m,n)+1))
...........
最大で
N本目はi=1+(n-1)mとなり、
Segment(Element(PList,n-m+1),Element(PList,1))
つまり、iを1以上nm以下で公差mで動かせばいいですね。
Sequence(Segment(Element(PList,Mod(i-1,n)+1),Element(PList,Mod(i-1+m,n)+1)),i,1,n m,m)
この通りにして作り、点の結び方がわかるように、1,2,3番めの点を表示したものが最初の例でした。説明の必要がないのなら、
進む個数、分子のスピードをあげて、スラーダーも非表示にする。
そして、等分数、分母の数を大きくして、辺の色をm、nによって変化するようにすると、
さらにわくわくする図形になるかもしれませんね。
たとえば、
辺列の「設定」で「上級」タブの「動的な色」を以下のように設定することができます。
R,G,Bの値は0以上1以下だと予想し、色がかぶらないように20と素な3,7倍して色要素を
変動させました。
赤 Mod(m + n, 20) / 20
緑 Mod(7m + 7n, 20) / 20
黄 Mod(3m + 3n, 20) / 20
RGB
この設定で次のようなアートができました。
質問:色の設定数式を変えると、どんな色変化になりますか。
時間があるときに遊んでみよう。
STAR POLYGON
3。バリエーション
<よりアートらしく>
色の変化は楽しいが、等分点は手動だし、進む数は単調に増減する。
質問:どんな改良ができますか。これを改良してみよう。
たとえば、NとMの乱数の系列pn, pmを作っておく。
スラーダーiをアニメーションにして、N=pn(i), M=pm(i)にすると、
分母も分子もランダムに変化するね。
また、1系統の多角形だけでなく、分母分子もまったく別系統の多角形を重ねて表示してみる。
すると、いっそう複雑で予測しにくい展開になるね。
STAR POLYGON2
<他の環境でも>
geogebraは、数式が1行ずつしか入力できないという難点はあるけれど、
sequence, Element,zipなどになれると、点列、線分列など、
図形をリストとして簡単にかけるという利点がある。
しかし、これに慣れすぎると、便利すぎて他の環境への移植が難しくなることもあるでしょう。
そこで、今後のことを考えて、他の言語や環境での実装にも取り組んみよう。
<Python>
pythonのGUIといえばTkinterがある。
ほかにも色々あるけれど、
基本的なウィンドウ、キャンバス、座標のロジックを知るために取り組んでみよう。
jupyter notebookの環境を用意しましょう。
まず、普通のGUIでは座標の原点は画面中央ではなく、画面の左上角にくることが多い。
そして、いきなり画面に図はかくことができない。画面の表示能力もあるが、インチ単位で画面の大きさを決める。それから、画面左上角の原点から、たてよこ半分ずつ進んだ点が画面の中心になる。
そこで、画面を1辺SIDE=500の正方形にして
ウィンドウの原点から画面の中心までの距離をCENTER_xyに入れます。
円の半径r=CENTER_xy-20にして、隙間をあけます。
次にウィンドウにキャンバスを貼り付けてパックします。
キャンバスに星形多角形を1秒ごとに重ねがきしてます。
drawStar関数は、かくキャンバスとm,nで動きます。
m,nは外で乱数を作ってから読み取るので、大小が逆転してるかもしれないのでm=m%nで
mは剰余にします。しかし、割り切れると困るので、m==0のときはm=4とセットします。
PListはgeogebraと同じロジックですが、線を結ぶようにiを0かnまで動かしてout of rangeになるのを
予防しています。
線をかくために、PListから連続する2点のxy座標を与えますが、色はfill = "ff0000"のように8けたの
16進数で設定します。だから、赤2けた、緑2けた、青2けたで16進数に変換した2文字ずつ連結する
という手間がかかっています。
canvas.create_line(pt1x,pt1y, pt2x,pt2y, fill = "#" + rcolor + gcolor + bcolor, tag="star")
これが、1本の線をひくメソッドです。
そして、更新のための関数がupdate()です。
この中で毎秒m,nを乱数で4から100の整数にして
drawStarを呼び出すことを、root.afterというウィンドウのメソッドで実行してます。
geogebraと同様に2つの多角形を重ね書きしては消すというロジックで作ろうとしたのですが、
消すロジックを調べる手間がもったいなく、消さずに重ねると
曼荼羅のような鮮やかだか重厚感のあるアートができそうなので、
まあ、これもバリエーションということでそのままにしました。
[IN]Python
#================================================
import tkinter as tk
from math import cos as cos
from math import sin as sin
from math import radians as rad
import time
import random
# 初期値
n = 8
m = 3
SIDE = 500 # 正方形窓の一辺
CENTER_xy = SIDE / 2 # 左上角からはかった中心座標(x,y)のxとy
r = CENTER_xy-20 #単位円を半径r円に拡大する
# ウィンドウにキャンバスを貼る。
root = tk.Tk()
root.title("STAR POLYGONS") # タイトル
root.geometry(f"{SIDE}x{SIDE}") # 大きさ
canvas = tk.Canvas(root, width = SIDE, height = SIDE)
canvas.pack()
#キャンバスに星形多角形を1秒ごとに重ね書きする。
def drawStar(canvas, m,n):
m = m % n
if m==0:
m=4
PList=[(r*cos(rad(360/n * i + 90)) + CENTER_xy, r*sin(rad(360/n * i + 90)) + CENTER_xy) for i in range(n+1)]
rcolor = f"{5*(m+n) % 256 :02X}"
gcolor = f"{7*(m+n) % 256 :02X}"
bcolor = f"{3*(m+n) % 256 :02X}"
for i in range(0,n*m,m):
pt1x,pt1y = PList[i % n]
pt2x,pt2y = PList[(i+m) % n]
canvas.create_line(pt1x,pt1y, pt2x,pt2y, fill = "#" + rcolor + gcolor + bcolor, tag="star")
def update():
m = random.randint(4, 100)
n = random.randint(20, 100)
drawStar(canvas, m, n)
root.after(1000, update) # 1秒後に自身を再呼び出し
start_time = time.time()
update()
# ウィンドウを表示し続ける。
root.mainloop()
#=====================================================
[OUT]

<Javascript>
javascriptにもキャンバスがある。
これはHTML5のときにかなり強化されているらしいが、pythonのtkinterと比べてどうだろうか。
Qtという言語でもそうだけれど、javascriptでは描画コンテキストというオブジェクトに描画します。
VScodeなどで、HTMLファイルをかき、その中の<canvas></>と<script></>という要素を使いましょう。
・canvasは識別子としてのidとサイズとしてのwidth, heightを設定しますが、idはscriptで読み込みます。
・scriptでは、canvasで設定したidとコンテキストを2Dとして読み取り、ctxなどの名前をつけて取得しましょう。ctxというオブジェクトの属性やメソッドの設定によって、Webbrowserで開いたときに動作するアプリが作れるのです。
HTMLのbody→init()→tick()→drawStar()のように、関数をバケツリレーします。
ただし、描画コンテキストctxは他の関数でも使うので、scriptの先頭でグローバル変数にして宣言します。
init関数の中で、setInterval(tick, 1000); として、
1秒ごとに描画をします。
javascriptでタプルが扱いにくいので、点の座標をPListのようにxyをセットにして作らずに、
PListx,PListyと別に作りました。
また、飛び越して進むためのfor分の増分式が難しいので、変数iを1ずつ増やして、
点のインデックスをmの倍数をnで割った余り i*m%n で指定しています。
通常のLineの描画は
ctx.moveTo(x0,y0);
ctx.lineTo( x1, y1);
ctx.stroke();
を繰り返します。
折れ線Polylineをかくには
for文の中に
ctx.lineTo( xi, yi);
だけをかいて、最後に1回だけ、ctx.stroke();
とかけばいですね。
たいていのWebbrowserでstar.htmlなどという名前で保存したものを実行できるでしょう。
[IN]==============================================
<!DOCTYPE html>
<html>
<body onload="init()">
<canvas id="star" width="500" height="500" style="border:1px solid grey"></canvas>
<head>
<script>
//グルーバル変数
const canvas = document.getElementById("star");
const ctx = canvas.getContext("2d");
let m, n;
let SIDE= 500;
let CENTER_xy = SIDE/2;
let r = CENTER_xy-20;
function rad(kakudo){
return kakudo * 2 * Math.PI / 360;
}
function init() {
setInterval(tick, 1000); //1秒ごとに描画する
}
// 描画の設定
function tick() {
let now = new Date(); // 現在時刻から時h、分m、秒sを取得
m = Math.floor(Math.random()*(100 - 4) + 4);
n = Math.floor(Math.random()*(100 - 20) + 20);
drawStar();
}
// 描画の実行
function drawStar() {
m = m % n;
let PListx = [];
let PListy = [];
if (m==0){ m= 4;}
for (let i = 0; i < n+1; i++){
PListx[i]=r * Math.cos(rad(360/n * i + 90)) + CENTER_xy;
PListy[i]=r * Math.sin(rad(360/n * i + 90)) + CENTER_xy;
}
ctx.lineWidth = 1;
let rgbcolor = Math.random().toString(16).slice(-8)
ctx.strokeStyle = "#"+rgbcolor;
ctx.beginPath();
for (let i = 0; i < n+1 ; i++)
{
let idx = i*m % n
ctx.lineTo(PListx[idx], PListy[idx]);
}
ctx.stroke();
}
</script>
</head>
</body>
</html>
Webbrowserでもstar polygons
