ソフトウェアの作成が好きとあって、一度ソフトウェアの作成に手を付けると沼ってしまいます。ww
ということで先回投稿した、「やっぱり、OpenGL上でジャイロセンサーMPU6050を使って3D紙ヒコーキを飛ばしてみた」の続編です。
いろいろ改造してみましたので、紹介します。
先回の投稿時点では、以下の状態でした。基本的には、紙ヒコーキなどのSTLファイルを読み込んで、画面に表示させます。その紙ヒコーキを受信したジャイロセンサー情報(ロール・ピッチ・ヨー)の角度を基にして姿勢制御をしていました。紙ヒコーキなどのモデルの色や背景を変更できるようにしていました。

まずは結果からです。今回、改造したジャイロセンサーモニターです。
グレードアップしたところを紹介したいと思います。

カラーパレットの追加
1つ目は、カラーパレットを追加しました。元々はR(赤)G(緑)B(青)A(透明度)の4つの数値で指定していましたが、直感的でないためカラーパレットで指定できるようにしました。最初にどの色を変更するかを選択します(例ではマテリアルカラーを選択しています)。次にカラーパレット上をマウスで選択すると、選択したポイントの色をスポイト(抽出)します(例では白丸のところ)。スポイトした色の数値をマテリアルカラーのRGBAの欄に0,139,16,255として反映します。また、その色をマテリアルサンプル欄に色(緑ぽい色)として反映しています。同様に「ライティングカラー」、「背景色」にも行うことができます。

カラーパレットで色を選択すると、グラフィック(紙ヒコーキ)にも同時に反映します。少し明るい緑になっていますが、これはライティングカラーに影響しているためです。

環境ギミックの追加
2つ目は、環境ギミックの追加です。グラフィック空間(環境)に対する仕掛けや工夫です。

- 青空ドーム:大きな球面を配置して、内部表面に水平線から頂点に向かった白色から群青色にグラデーションさせて、青空のようにみせるようにしました。球面内部に紙ヒコーキなどモデルを配置することで青空の中にいる雰囲気を演出しています。オン/オフやドーム半径の指定をできるようにしています。

- メッシュグランド:メッシュ状のグランド(床)を配置できるようにしました。これにより空間におけるモデルの位置や奥行きが把握しやすくなります。オン/オフとグランド(床)高さを指定できます。

- 市松模様床:市松模様の床を配置できるようにしました。メッシュグランドの透過タイプに対して市松模様の床は不透明タイプの床になります。こちらもオン/オフと床高さを指定できます。

- 影(床高さゼロ固定):床を配置できるようしたので、モデルの影をつけられるようにしました。これにより現実感(リアリティ)が増します。今回の影付けは超簡易版です。オン/オフを指定できます。制約の都合でオンにした場合は、床面の高さはゼロ固定にしています。
制約としては、以下の通りです。
・光源はY軸上(この描画では上方)に限る。
・影を投影(描画)する床の高さはゼロ固定(つまり、Y=0)で水平面に限る。
・モデルによって影がまだらになる場合がある
・重なった複数のモデルにより生じるモデル表面の影付けは未対応。
などなど他にも制約あると思います。


サンプルモデルによる影付けです。1つは、3つのモデル(球、円柱と直方体の複合体、立方体が床にめり込んでいる状態)に対する影付けです。いい感じの影になっています。もう1つは人工衛星モデルの影です。影の輪郭はよく表現できていますが、まだらに投影されています。
- ビューポート設定:OpenGLライブラリを使用する上で理解が必要なビューポートの設定をビジブル化(見える化)しました。絵でビューポートがわかるようにして、見える範囲を設定するためのパラメータがどこを指しているかを示しています。

具体的にはビューポートは画面(下図)の赤枠範囲のことです。なお、画面下の2行分のエリアはビューポートに対して上書きしています。ビューポート設定でパラメータW、Hに対しては、以下の画面の幅と高さを好みの大きさに入力します。また、マウスでウィンドウ枠をドラッグして拡大縮小してもパラメータW、Hに反映されます。

パラメータFOVとは、Field of viewの略で、視野角を意味します。設定値は角度(°)を指定します。大きくすると広角レンズで見ているような感じになります。
パラメータnear、farとは、奥行きを表します。near(近く)、far(遠く)の奥行きが見える範囲です。モデルがパラメータfarより奥に配置されていると見えなくなります。同様にnearより手前にモデルを配置すると見えなくなります。
パラメータVPとは、View pointの略で視点を表します。視点から見てビューポートの範囲がウィンドウに描画されるということです。つまり、視点を頂点に高さ(= far – near)の四角錐台(しかくすいたい)の範囲がウィンドウに描画されます。
今回は、VPの視点座標値は自動計算しています。モデルの大きさや配置場所はSTLファイルを読み込むまでわかりません。そうするとどこから見ればよいかわかりません。そこでモデルを読み込んでからモデルの重心を計算して、重心を起点にして視点を求めてなるべく的外れにならないようにしています。
モデルの回転方式の改善(オイラー角からクォータニオンに変更)
3つ目は、先回投稿の段階では、3Dグラフィック空間にあるモデルをマウスを使って回転させることができました。このとき、モデルの回転方式はオイラー角を使ったロール・ピッチ・ヨーによる3つの回転角を使ってモデルを回転させる方法を用いました。これは直感的にわかりやすい表現ですが、ある姿勢角の時にジンバルロックという自由度が減ってしまう制約があることを知りました。実際、先回の投稿時点では、マウス操作するとある姿勢になると急に姿勢が反転してしまう挙動になってしまいました。これを調査していたときにジンバルロックに行きつきました。
このジンバルロックの解決手段にクォータニオンによる4つのパラメータを使って姿勢を表現する方法があります。4つのパラメータ q = (x、y、z、w)から姿勢を表します。x、y、zが回転軸を表し、wが回転軸まわりの回転を表します。
これをわかるように説明するのは難しいので、詳細を知りたい方は以下のページ(外部リンク)を参照してみてください。
どのような違いがあるかを残すためにオイラー角方式とクォータニオン方式を選択できるようにしました。
クォータニオン方式では、制約なくモデルを回転させることができますが、オイラー角方式の場合はZ軸回転に対して±89°までの回転に制約しています。

ジャイロセンサー受信の改善
4つ目は、ジャイロセンサー受信の改善です。オフライン、受信待ち、受信中、受信待ちタイムアウト、受信の中断の状態遷移を明確にしました。ジャイロセンサー側はセンサー情報を垂れ流します。そのためジャイロセンサーモニター側が受信を制御する必要があります。前回投稿時点では雑なところがありましたので上記の5つ状態を制御できるようにしました。
今回、新たに受信待ちタイムアウトを指定できるようにしました。初期値は30秒に設定しています。ジャイロモニタ開始ボタンをクリックしてから30秒間にジャイロセンサーからのデータを受信できなかった場合はタイムアウトしてジャイロモニタ開始を中断するようにしました。

細かな改善
5つ目は、ビューワ画面の細かな改善です。
- アイコンの設置
- 受信待ちの経過時間表示
- ビューワ側にも閉じるボタンの設置(×ボタンクリックはチョイダサなので)

多言語化の対応
個人的なソフトウェア作成で初めての試みです。多言語に対応してみました。今回は、日本語の他に英語、スペイン語、韓国語に対応しました。韓国語はさっぱりわからないので、韓国語がわかる妻に協力してもらい確認してもらいました。
ジャイロセンサーモニターを起動すると最初に以下のように言語選択画面が表示されます。そこで言語選択するとその言語で起動します。

以下に各言語を選択したときの画面例を示します。多言語化でやっかいなのが文字列長の違いにより画面レイアウトが大きく影響してしまうことです。文字列長が長いものは文字のはみ出し、文字切れや文字の重なりがたくさん発生して、画面のレイアウトを見直す必要がでてきます。
4言語のうちスペイン語が最も長く苦労しました。スペイン語に合わせてしまうと日本語では間延びした画面になってしまいますので、中庸あたりを探りながら調整しました。
画面だけでなく、動作確認も必要です。言語依存するような処理(文字列で判定する処理)があると動かなくなってしまいます。ひと通り確認する必要があるので対応言語数が増えれば手間が増えてしまいます。




Visual Studioのリソース管理の活用
Visual Studioには、リソース管理の仕組みがあります。これを使うと言語ごとのリソースを準備することで多言語化を比較的容易に対応することができます。以下は、4言語のリソース(文字列)が一覧で表示できて追加、削除、編集が行えます。

以下のコードは、画面に配置した部品(例えばボタンとか)に表示する文字列を選択した言語の文字列に設定するプログラムです。朱書きしたところを見てどのように指定の言語の文字列に設定されるか見てみます。
private void ApplyLocalization()
{
button_LoadModel.Text = Resources.Resource.button_LoadModel;
button_DisplayModel.Text = Resources.Resource.button_DisplayModel;
button_SaveImage.Text = Resources.Resource.button_SaveImage;
button_Close.Text = Resources.Resource.button_Close;
radioButton_MaterialColor.Text = Resources.Resource.radioButton_MaterialColor;
radioButton_LightingColor.Text = Resources.Resource.radioButton_LightingColor;
button_MonitorOnOff.Text = Resources.Resource.button_MonitorOnOff;
label_Port.Text = Resources.Resource.label_Port;
button_SelectBitmap.Text = Resources.Resource.button_SelectBitmap;
label_ColorPalette.Text = Resources.Resource.label_ColorPalette;
label_MaterialColor.Text = Resources.Resource.label_MaterialColor;
label_LightingColor.Text = Resources.Resource.label_LightingColor;
label_BackgroundColor.Text = Resources.Resource.label_BackgroundColor;
radioButton_RGBColor.Text = Resources.Resource.radioButton_RGBColor;
radioButton_Texture.Text = Resources.Resource.radioButton_Texture;
radioButton_Light.Text = Resources.Resource.radioButton_Light;
radioButton_Dark.Text = Resources.Resource.radioButton_Dark;
label_RotationMethod.Text = Resources.Resource.label_RotationMethod;
checkBox_SkyDome.Text = Resources.Resource.checkBox_SkyDome;
checkBox_MeshGround.Text = Resources.Resource.checkBox_MeshGround;
checkBox_CheckGround.Text = Resources.Resource.checkBox_CheckGround;
label_Hight.Text = Resources.Resource.label_Hight;
label_Radius.Text = Resources.Resource.label_Radius;
label_WH.Text = Resources.Resource.label_WH;
label_near.Text = Resources.Resource.label_near;
label_far.Text = Resources.Resource.label_far;
label_FOV.Text = Resources.Resource.label_FOV;
label_VP.Text = Resources.Resource.label_VP;
label_ViewportSettings.Text = Resources.Resource.label_ViewportSettings;
checkBox_shadowEnable.Text = Resources.Resource.checkBox_shadowEnable;
checkBox_CoordinateAxes.Text = Resources.Resource.checkBox_CoordinateAxes;
label_Timeout.Text = Resources.Resource.label_Timeout;
label_GyroOperation.Text = Resources.Resource.label_GyroOperation;
label_ViewerOperation.Text = Resources.Resource.label_ViewerOperation;
// コンボボックスの要素
string[] items = Resources.Resource.comboBox_RotationMethod_Items.Split(';');
comboBox_RotationMethod.Items.AddRange(items);
}
朱書きのコードを抜き出しました。
button_Close.Text = Resources.Resource.button_Close;
これは「閉じる」ボタンの部分です。ボタンに「閉じる」という文字列を表示させるには、以下のように記述します。
button_Close.Text = “閉じる”;
ただ、この場合、ボタンには日本語で「閉じる」となり固定してしまいます。これを言語により変更できるようにする必要があります。そこで、上述のリソース管理のデータを使います。以下はリソース管理から「閉じる」の文字列部分を抜粋したものです。

ジャイロセンサーモニターを起動したときに言語選択画面が表示されて、言語を選択します。例えば「英語(English)」を選択すると、内部ではカルチャーとして「En」を設定します。「En」というのは言語種別の英語を意味します。日本語を選択すると「Jp」を設定します。
次にジャイロセンサーモニターが起動処理の中で以下のことが行われます。
リソース管理から「En」の列、つまり「ニュートラル値」の列が選択されます。
なぜ、「En」ではなく「ニュートラル値」なの?という疑問があります。「ニュートラル値」というのはデフォルト言語とみなされます。もし、対応言語のリソースが見つからなかったときは「ニュートラル値」を使います。つまり「英語」が使われるという救済の仕組みがあります。「ニュートラル値」を日本語にすることもできます。ここでは、国際標準言語が英語ということなので、とりあえず英語に設定しています。
さて、話をもどして「En」の列、「ニュートラル値」の列が選択されます。
button_Close.Text = Resources.Resource.button_Close;
このプログラムの「Resources.Resource.button_Close」の「Resources.Resource」はリソース管理の「ニュートラル値」の列を参照することを意味します。
そして、後半の「.button_Close」はリソース管理の[名前]列の「button_Close」という行を参照することを意味します。
結果として、上述の黄色線枠の「Close」が参照されることになり、ボタンには「Close」と表示されます。このようなメカニズムにより言語選択することで表示を切り替えられるようになります。
Google翻訳やMicrosoft Copilotによる一括翻訳
Google翻訳やMicrosoft Copilotを使って文字列を一括翻訳することで、効率よく多言語化できます。
Visual Studioのリソースをエクセルに転記して、対訳表を作成しました。対訳表をコピーしてCopilotに「英語とスペイン語と韓国語に翻訳して、表形式に出力して」とプロンプトで指示するとあっという間に翻訳して一覧表にしてくれます。これをエクセルにコピーペーストして、最終チェックして完成です。
手間なのは、リソース管理には一行一行コピーペーストしないといけないことです。もっとリソースが多い場合は、リソース管理に対してエクスポートやインポートできるツールを作成した方がよさそうです。
以上がジャイロセンサーモニターをバージョンアップした内容になります。
ダウンロード
今回、作成したジャイロセンサーモニターをダウンロードできるようにしました。使ってみたい方はダウンロードしてみてください。
【使うまでの手順】
1.ダウンロードしたZIPファイルを解凍します。
2.フォルダ内の「Gyro3dMonitor.exe」を実行します。(インストール不要)
3.ご使用前に後述の【ご利用にあたっての注意事項】を必ず一読ください。
4.サンプルのSTLモデルも同フォルダに格納していますのでお試しください。
【ご利用にあたっての注意事項】
ジャイロセンサーモニターを実行して、ジャイロモニタ開始ボタンをクリックすると、Windows OSの「ファイヤーウォールの警告」画面が表示されます。表示されるのは、本アプリではTCP/IP通信を行うので、Windows Defender Firewallが通信の安全性を確認しているためです。警告画面の[許可]ボタンをクリックすると通信が可能になります。間違って[キャンセル]ボタンをクリックすると通信できなくなります。

誤って[キャンセル]ボタンをクリックしてしまった場合、本アプリの通信が遮断されます。また、この設定は自動的にファイアウォールのルールとして記録され、以後も遮断され続けてしまいます。
【遮断を解除する方法】
1.Windowsの検索バーにて「ファイアウォール」と入力
「Windows Defender ファイアウォール」を開く。
2.左メニューから「Windows Defender ファイヤウォールを介したアプリまたは機能を許可」を選択

3.「設定の変更」をクリック(管理者権限が必要)(下図①)
4.リストの中から該当のアプリ「gyro3dmonitor.exe」を探す
・存在していてチェックが入っていない場合 → チェックを入れる(下図②)
・プライベート/パブリックの両方にチェックを入れる(下図②)
・存在していない場合 → 「別のアプリを許可する」ボタンから手動で追加
5.OKを押して設定を保存(下図③)

ブログをご覧いただきありがとうございます。ご利用の際は、上記の注意事項をご確認ください。
ジャイロセンサーモニター(Gyro3dMonitor)は、3Dモデルの表示やRaspberry Pi上のジャイロセンサーMPU6050の情報をネットワーク経由で受信し、モデルの姿勢としてモニターすることが可能です。
なお、Raspberry Pi からのセンサー情報の送信方法もブログに投稿していますので参考にしてください。
まとめ
今回は、ジャイロセンサーモニターのバージョンアップ内容について紹介しました。ジャイロセンサーモニターのバージョンアップは一旦終わり、引き続きRaspberry Pi を使ったDIYに戻りたいと思います。
準備が整い次第ソースコードも公開したいと考えています。ソースコードの公開に際してはGitHub上で公開したいと考えています。
今後改善したいこととしては、以下があり、ソースコードを公開した折には皆さんからコードの提案を頂けると嬉しいと思っています。
・モデルを選択、移動、回転できるようにする
・光の表現は環境光(Ambient)のみです。拡散光(Diffuse)、鏡面光(Specular)も扱えるようにする
・影(シャドーマッピング)の強化
コメント