うしブログ

うしブログ

趣味で運営する、GeoGebraの専門ブログ。

(作業メモ)StartPoint要検証(2行の場合;テキスト変更時未定義問題)

(要修復)ToggleButton・RollPolygonWithoutSlipping・貯金時計・直感力トレーニング

多角形を、直線に沿って滑らずに転がす

はじめに

下図のように、GeoGebraで、多角形を、直線に沿って滑らずに転がす(以下単に「転がす」という)方法を紹介する。

f:id:usiblog:20190427042928g:plain

解説 

前提オブジェクト

多角形poly1

直線j

数値α(転がす角度を指定するのに用いる)

※さしあたり、-6π<=α<=6πで、スライダーを作っておく。

環境整備

maxLength = Distance[Corner[1], Corner[3]]

maxLengthは、アプレット画面の対角線の長さである。直線jのうち、画面上で見える部分は、この長さを超えることはない。したがって、poly1を転がす範囲は、maxLengthの2倍あれば十分だろう。 

vertList = {Vertex[poly1]}

poly1の頂点のリスト。

ステップ① poly1を直線まで持ってくる

poly1は、直線jから離れていることもあれば、直線jと交わっていることもあるだろう。都合良く直線jに接している場合ばかりではないということだ。まずは、poly1を、直線jに接するように平行移動させよう。

vectorList = Zip[Vector[β, ClosestPoint[j, β]], β, vertList]

f:id:usiblog:20190427044514p:plainf:id:usiblog:20190427044501p:plainf:id:usiblog:20190427044443p:plain

poly1の各頂点から、直線jに垂線を下ろし、それをベクトルとして取得する(vectorList)。

 

productList = Zip[γ ⊗ UnitVector[j], Vector[γ], vectorList]

外積を用いて、vectorListの各要素であるベクトルの向きを判別する(productList)。

 

negativeVectors = RemoveUndefined[Zip[If[δ < 0, Vector[ε], ?], δ, productList, Vector[ε], vectorList]]

positiveVectors = RemoveUndefined[Zip[If[δ ≥ 0, Vector[ε], ?], δ, productList, Vector[ε], vectorList]]

f:id:usiblog:20190427045338p:plain

向きに応じてnegativeとpositiveに分ける(negativeVectors、positiveVectors)。

 

lengthOfNegativeVectors = Zip[Length[η], Vector[η], negativeVectors]

sortedNegativeVectors = Sort[negativeVectors, lengthOfNegativeVectors]

lengthOfPositiveVectors = Zip[Length[ζ], Vector[ζ], positiveVectors]

sortedPositiveVectors = Sort[positiveVectors, lengthOfPositiveVectors]

negativeVectors、positiveVectorsそれぞれ、ベクトルの長さに応じて昇順に並べ替える(sortedNegativeVectors、sortedPositiveVectors)

 

positiveMaxAndnegativeMax = Flatten[{Last[sortedPositiveVectors], Last[sortedNegativeVectors]}]

negativeVectorsのうち最長のベクトルと、positiveVectorsのうち最長のベクトルを1本ずつ選んで、リストを作る(positiveMaxAndnegativeMax)。

 

shorterElementOfpositiveMaxAndnegativeMax = First[Sort[positiveMaxAndnegativeMax, Zip[Length[μ], Vector[μ], positiveMaxAndnegativeMax]]]

positiveMaxAndnegativeMaxのうち、長さが短い方だけを残す(shorterElementOfpositiveMaxAndnegativeMax)。

 

positiveMinAndNegativeMin = RemoveUndefined[Flatten[{First[sortedPositiveVectors], First[sortedNegativeVectors]}]]

negativeVectorsのうち最短のベクトルと、positiveVectorsのうち最短のベクトルを1本ずつ選んで、リストを作る(positiveMinAndNegativeMin)。

 

vectorToAttach = If[Length[positiveMinAndNegativeMin] ≟ 1, Vector[Element[positiveMinAndNegativeMin, 1]], Vector[Element[shorterElementOfpositiveMaxAndnegativeMax, 1]]]

Length[positiveMinAndNegativeMin]の要素数が1の場合には、poly1が直線jと交わらない。この場合には、positiveMinAndNegativeMinの唯一の要素であるベクトルを返す(vectorToAttach;If条件を満たす場合)。

poly1が直線jと交わる場合には、shorterElementOfpositiveMaxAndnegativeMaxの唯一の要素であるベクトルを返す(vectorToAttach;else)。

 

poly2 = Translate[poly1, vectorToAttach]

f:id:usiblog:20190427051236g:plain

vectorToAttachに従って、poly1を平行移動した多角形を作成する(poly2;上図の赤い多角形)。

 

ステップ② α=0に対応する初期位置を決める

poly2は、poly1を単に平行移動しただけである。これをα=0の場合(初期位置)としてしまうと、「右にどれだけ転がせば次の頂点が直線とぶつかるか」と、「左にどれだけ転がせば次の頂点が直線とぶつかるか」の両方を測定しなければならず、設計が煩雑になる。そこで、「すでに2頂点が直線上にある状態」を初期位置にする。

 

conv = Translate[ConvexPolygon[vertList], vectorToAttach]

f:id:usiblog:20190427052709p:plain

転がす際の挙動は、poly1の凸包で考えた方が分かりやすい。そこで、poly1の凸包ConvexPolygon[vertList]をvectorToAttachで平行移動した多角形を作成する(conv)。

なお、ここで用いている、ConvexPolygon[<点のリスト>]は、オリジナルツールである。ConvexPolygonツールの詳細は、下記記事を参照。

点がパス上にあるか否かを表す真偽値オブジェクト - うしブログ

 

convAngle = Zip[Θ + 0, Θ, {Angle[conv]}]

convの内角の大きさ(ラジアン)をリストで取得する(convAngle)。+0をしているのは、角度オブジェクトではなく、数値オブジェクトとして取得したいからである。 

 

convVert = {Vertex[conv]}

convの頂点をリストで取得する(convVert)。

 

convSides = Sides[conv]

convの辺をリストで取得する(convSides)。なお、Sides[<多角形>]は、オリジナルツールである(下記記事参照)。

多角形オブジェクトを用いて、頂点または辺のリストを返す - うしブログ

 

Contact = Point[vectorToAttach, 1]

f:id:usiblog:20190427053804p:plain

convと直線jとの接点を作成する(Contact)。

 

convAngle, convVert, convSidesの各要素は、ConvexPolygonツールの仕様上、反時計回りに並ぶ。しかし、どこが始点になるかは、場合によって区々である。

そこで、以降の処理を容易にするため、convAngle, convVert, convSidesを、Contactを始点とする反時計回りに並べ替えておこう。

indexOfContact = IndexOf[Contact, convVert]

Contactの、convVertにおけるIndexを取得する(indexOfContact)。

key = Zip[If[Mod[σ, Length[convVert]] ≟ 0, Length[convVert], Mod[σ, Length[convVert]]], σ, Sequence[indexOfContact + ρ, ρ, 0, Length[convVert] - 1]]

並べ替えの基準となる数値リストを作成する(key)。 

sortedAngle = Zip[Element[convAngle, ϕ], ϕ, key]

sortedVert = Zip[Element[convVert, τ], τ, key]

sortedSides = Zip[Element[convSides, φ], φ, key] 

keyで並べ替える。

 

lastVector = Vector[Element[sortedVert, 1], Element[Last[sortedVert], 1]]

f:id:usiblog:20190427063603p:plain

sortedVertの最初と最後を結んだベクトルを作成する(lastVector)。

 

poly3 = Rotate[poly2, If[Angle[lastVector, Vector[UnitVector[j]]] > π, Angle[lastVector, Vector[UnitVector[j]]] - π, Angle[lastVector, Vector[UnitVector[j]]]], Contact]

originalPoint = Rotate[Element[Last[sortedVert], 1], If[Angle[lastVector, Vector[UnitVector[j]]] > π, Angle[lastVector, Vector[UnitVector[j]]] - π, Angle[lastVector, Vector[UnitVector[j]]]], Contact]

originalPositionOfConv = Rotate[conv, If[Angle[lastVector, Vector[UnitVector[j]]] > π, Angle[lastVector, Vector[UnitVector[j]]] - π, Angle[lastVector, Vector[UnitVector[j]]]], Contact]

f:id:usiblog:20190427064205p:plain

lastVectorとUnitVector[j]とのなす角に着目して、lastVectorが直線jと重なるように、poly2を(Contactを中心に)回転させる(poly3)。poly3をもって、α=0の場合の位置(初期位置)とする。

convおよび、sortedVertの最後の要素も、同様に回転させる(originalPositionOfConv、originalPoint)。

 

ステップ③ 転がした場合に各頂点が着地する場所を記録する

α<0の場合を「Rearに転がす場合」、a>0の場合を「Frontに転がす場合」と呼ぶことにしよう。 

originalPositionOfConvをRearに転がす場合に、originalPositionOfConvの各頂点が直線jに着地する場所を、点のリスト(Rear)として記録しよう。

times = floor(maxLength / Sum[sortedSides])

maxLengthに応じて、どこまで記録するかを決める(times)。

 

reverseSortedSidesFull = Sequence[Element[Sort[sortedSides, Zip[-IndexOf[χ, sortedSides], χ, sortedSides]], If[Mod[t, Length[convVert]] ≟ 0, Length[convVert], Mod[t, Length[convVert]]]], t, 1, times Length[convVert]]

sortedSidesを逆順に並べ替え、timesに応じて適切なだけ、要素の列挙を繰り返す(reverseSortedSidesFull)。

 

reverseSortedSidesFullSum = Sequence[Sum[reverseSortedSidesFull, t], t, 1, Length[reverseSortedSidesFull]]

reverseSortedSidesFullを累積的に積み重ねる(reverseSortedSidesFullSum)。

 

Rear = Zip[Dilate[originalPoint, ψ / Element[Last[sortedSides], 1], Contact], ψ, reverseSortedSidesFullSum]

reverseSortedSidesFullSumに応じて、originalPointをDilateする(Rear)。

 

Front = Sort[Translate[Rear, Vector[Element[Last[Rear], 1], Contact]], Zip[-IndexOf[Σ, Translate[Rear, Vector[Element[Last[Rear], 1], Contact]]], Σ, Translate[Rear, Vector[Element[Last[Rear], 1], Contact]]]]

続いて、Frontに転がす場合の着地点を記録する(Front)。Rearと同様に作っても良いのだが、Rearがすでに作られているので、それを流用して作成した。

 

ステップ④ αの値によって、着地点を特定する

originalPositionOfConvを転がした際に、どの頂点が直線jと接するかは、αによって特定できる(ContactSliding)。

Rearに転がす場合

reverseSortedAngle = Sort[sortedAngle, Zip[-IndexOf[Γ, sortedAngle], Γ, sortedAngle]]

outerReverseSortedAngle = Zip[π - Δ, Δ, reverseSortedAngle]

outerReverseSortedAngleFull = Sequence[Element[outerReverseSortedAngle, If[Mod[t, Length[outerReverseSortedAngle]] ≟ 0, Length[outerReverseSortedAngle], Mod[t, Length[outerReverseSortedAngle]]]], t, 1, Length[Rear]]

outerReverseSortedAngleFullSum = Sequence[Sum[outerReverseSortedAngleFull, t], t, 1, Length[outerReverseSortedAngleFull]]

RearIndexOfα = IndexOf[-α, Sort[Join[{outerReverseSortedAngleFullSum, {-α}}]]]

ContactSlidingRear = Element[Rear, RearIndexOfα]

Frontに転がす場合

outerSortedAngle = Zip[π - Π, Π, sortedAngle]

outerSortedAngleFull = Sequence[Element[outerSortedAngle, If[Mod[t, Length[outerSortedAngle]] ≟ 0, Length[outerSortedAngle], Mod[t, Length[outerSortedAngle]]]], t, 1, Length[Front]]

outerSortedAngleFullSum = Sequence[Sum[outerSortedAngleFull, t], t, 1, Length[outerSortedAngleFull]]

FrontIndexOfα = IndexOf[α, Sort[Join[{outerSortedAngleFullSum, {α}}]]]

ContactSlidingFront = Element[Front, FrontIndexOfα]

両者を統合

ContactSliding = Element[RemoveUndefined[{If[α < 0, ContactSlidingRear, ContactSlidingFront], Contact}], 1]

f:id:usiblog:20190427074933g:plain

 

ステップ⑤ αの値によって、直線jと接する頂点を特定する

originalPositionOfConvの頂点のうち、どの頂点が直線jと接することになるのかは、やはりαによって特定できる(translater)。

Rearに転がす場合

sortedVertOriginalPosition = Zip[Rotate[Φ, If[Angle[lastVector, Vector[UnitVector[j]]] > π, Angle[lastVector, Vector[UnitVector[j]]] - π, Angle[lastVector, Vector[UnitVector[j]]]], Contact], Φ, sortedVert]

reverseSortedVertOriginalPosition = Sort[sortedVertOriginalPosition, Zip[-IndexOf[Ω, sortedVertOriginalPosition], Ω, sortedVertOriginalPosition]]

reverseSortedVertOriginalPositionFull = Sequence[Element[reverseSortedVertOriginalPosition, If[Mod[t, Length[reverseSortedVertOriginalPosition]] ≟ 0, Length[reverseSortedVertOriginalPosition], Mod[t, Length[reverseSortedVertOriginalPosition]]]], t, 1, Length[Rear]]

translaterRear = Element[reverseSortedVertOriginalPositionFull, RearIndexOfα]

Frontに転がす場合

sortedVertOriginalPositionFull = Sequence[Element[sortedVertOriginalPosition, If[Mod[t, Length[sortedVertOriginalPosition]] ≟ 0, Length[sortedVertOriginalPosition], Mod[t, Length[sortedVertOriginalPosition]]]], t, 1, Length[Front]]

translaterFront = Element[sortedVertOriginalPositionFull, FrontIndexOfα] 

両者を統合

translater = Element[RemoveUndefined[{If[α < 0, translaterRear, translaterFront], Contact}], 1]

なお、translaterは、αが描画対象外の値をとったときは、Contactを返すようにしている(フェイルセーフ)。

f:id:usiblog:20190427080450g:plain

ステップ⑥ poly3をαに従って回転移動させ、ContactSlidingおよびtranslaterに従って平行移動する

回転移動

poly4 = Rotate[poly3, -α, originalPoint]

f:id:usiblog:20190427081026g:plain

poly3を、αに従って(originalPointを中心に)回転移動した多角形を作成する(poly4)。

 

平行移動

poly5 = Translate[poly4, Vector[originalPoint, ContactSliding]]

f:id:usiblog:20190427081336g:plain

poly4を、ContactSlidingに従って平行移動する(poly5)。

 

poly6 = Translate[poly5, Vector[Translate[Rotate[translater, -α, originalPoint], Vector[originalPoint, ContactSliding]], ContactSliding]]

f:id:usiblog:20190427081632g:plain

poly5を、translaterに従って平行移動する(poly6)。

poly6が、目的の多角形である。

オリジナルツール「RollPolygonWithoutSlipping」

上記知見を利用して、オリジナルツールを作成した。

RollPolygonWithoutSlipping tool - GeoGebra

構文は

RollPolygonWithoutSlipping[<多角形>, <直線>, <数値>]

である。

多角形を、直線に沿って、指定の角度だけ滑らずに転がした様子を描画する。