指文字3次元ビューワ(ソースコードと説明と利用ガイド)

概要

日本語指文字(手指で五十音を表す方法)46文字の手形を、21点の3次元ランドマークとして保持し、3次元表示するプログラムである。点の並びは MediaPipe Hand Landmarker の規約(0:手首、1-4:親指、5-8:人差し指、9-12:中指、13-16:薬指、17-20:小指)に従う。マウスのドラッグで視点を回転し、ホイールで拡大縮小できる。指ごとに色を変え、奥にあるものほど色を白に近づけて奥行きを表す。Python標準ライブラリのtkinterだけで動作し、追加パッケージのインストールは不要である。

目次

第1章 事前準備

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.html で説明しているので、必要に応じて参照する。

本プログラムは、Python標準ライブラリのtkinterとmathだけを使用するため、追加パッケージのインストールは不要である。

第2章 Python 3.12 のインストール

Pythonのインストールを行い、Pythonのプログラムを実行する環境を整える。扱う環境は、Windows搭載パソコンである。金子研究室では、Python 3.12.10を推奨する。

[Windows での Python 3.12 のインストール手順を見るには、ここをクリック]

Windows での Python 3.12 のインストール

以下のいずれかの方法でPython 3.12をインストールする。Pythonがインストール済みの場合、この手順は不要である。

方法 1:winget によるインストール

インストールコマンドの実行方法

管理者権限コマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして、コマンド全体をコマンドプロンプトにコピー&ペーストする。

--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動するとPATHが反映される。

REM Python 3.12 をシステム領域にインストール
winget install --id Python.Python.3.12 -e --scope machine --silent --accept-source-agreements --accept-package-agreements --override "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_pip=1 Include_launcher=1 InstallLauncherAllUsers=1 TargetDir=\"C:\Program Files\Python312\""
if not "%ERRORLEVEL%"=="0" ( color 0c & echo Python 3.12 のインストールに失敗しました & ping 127.0.0.1 -n 6 >nul & color )

REM Python と Scripts を PATH 先頭に追加
powershell -NoProfile -Command "$p='C:\Program Files\Python312'; $s=\"$p\Scripts\"; if(Test-Path $p){$k=[Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\CurrentControlSet\Control\Session Manager\Environment',$true); $c=$k.GetValue('Path','',[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames); $t=$k.GetValueKind('Path'); $new=$c; if((';'+$new+';') -notlike \"*;$p;*\"){$new=$p+';'+$new}; if((';'+$new+';') -notlike \"*;$s;*\"){$new=$s+';'+$new}; if($new -ne $c){$k.SetValue('Path',$new,$t)}; $k.Close()}"

REM 現在のセッションにも反映(システムPATHを再取得して連結)
for /f "usebackq tokens=2,*" %A in (`reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path`) do set "PATH=%B"

REM pip / wheel の更新
python -m pip install --no-user -U pip wheel
if not "%ERRORLEVEL%"=="0" ( color 0c & echo pip / wheel の更新に失敗しました & ping 127.0.0.1 -n 6 >nul & color )

方法 2:インストーラーによるインストール

  1. Python公式サイト(https://www.python.org/downloads/)にアクセスし、「Download Python 3.x.x」ボタンからWindows用インストーラーをダウンロードする。
  2. ダウンロードしたインストーラーを実行する。
  3. 初期画面の下部に表示される「Add python.exe to PATH」にチェックを入れてから「Customize installation」を選択する。このチェックを入れ忘れると、コマンドプロンプトから python コマンドを実行できない。
  4. 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。

インストールの確認

コマンドプロンプトで以下を実行する。

python --version

バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。「'python' は、内部コマンドまたは外部コマンドとして認識されていません。」と表示される場合は、インストールが正常に完了していない。

第3章 Python の開発環境 Visual Studio Code のインストールと Python 用の設定

Python の開発環境Visual Studio Code(プログラムを編集するソフトウェア。以下、VS Code)を整える。

[Windows での Visual Studio Code のインストールと Python 用の設定手順を見るには、ここをクリック]

Windows での Visual Studio Code のインストールと Python 用の設定手順

1. VS Code と拡張機能のインストール

以下のコマンドにより,既存の VS Code を削除し,全ユーザー共有の設定で再インストールしたうえで,拡張機能(VS Code に機能を追加するソフトウェア)をまとめて導入する.

インストールコマンドの実行方法

管理者権限コマンドプロンプトを起動する(手順:Windows キーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして,コマンド全体をコマンドプロンプトにコピー&ペーストする。

インストールコマンド


REM ============================================================
REM Microsoft Visual Studio Code
REM ============================================================
REM Build Tools + Desktop development with C++(VCTools)+ 追加コンポーネント(一括)
REM 未インストール時: winget で新規インストール
REM インストール済み時: setup.exe modify でコンポーネント追加(バージョンは変更しない)
winget list --id Microsoft.VisualStudio.BuildTools 2>nul | findstr /i "BuildTools" >nul 2>&1
if %ERRORLEVEL% EQU 0 (
    for /f "usebackq delims=" %P in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -products Microsoft.VisualStudio.Product.BuildTools -property installationPath`) do start /wait "" "C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe" modify --installPath "%P" --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --add Microsoft.VisualStudio.Component.VC.v143.x86.x64 --includeRecommended --quiet --norestart --nocache
    if not "%ERRORLEVEL%"=="0" ( color 0c & echo Build Tools のコンポーネント追加に失敗しました & ping 127.0.0.1 -n 6 >nul & color )
) else (
    winget install --scope machine --id Microsoft.VisualStudio.BuildTools -e --silent --disable-interactivity --force --accept-source-agreements --accept-package-agreements --override "--quiet --wait --norestart --nocache --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --add Microsoft.VisualStudio.Component.VC.v143.x86.x64"
    if not "%ERRORLEVEL%"=="0" ( color 0c & echo Build Tools のインストールに失敗しました & ping 127.0.0.1 -n 6 >nul & color )
)

REM 全ユーザー共有の拡張機能フォルダ
if not exist "C:\ProgramData\vscode-extensions" mkdir "C:\ProgramData\vscode-extensions"
icacls "C:\ProgramData\vscode-extensions" /grant "Everyone:(OI)(CI)M" /T

REM スタートメニューのショートカットを --extensions-dir 付きで再作成
if exist "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code" rmdir /s /q "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code"
if exist "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk" del "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk"
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); $lnk.TargetPath='C:\Program Files\Microsoft VS Code\Code.exe'; $lnk.Arguments='--extensions-dir \"C:\ProgramData\vscode-extensions\"'; $lnk.Save()"
REM ショートカットの検証
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); Write-Host 'TargetPath:' $lnk.TargetPath; Write-Host 'Arguments:' $lnk.Arguments"

REM ファイル / フォルダ右クリックの「Code で開く」を登録
reg add "HKLM\SOFTWARE\Classes\*\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\Background\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%V\"" /f

REM --extensions-dir 付きで起動する code.cmd ラッパを作成
REM (%* を echo で書くと対話的 cmd で失われるため、PowerShell で [char]37+'*' を書き出す)
powershell -NoProfile -Command "$pct=[char]37; $q=[char]34; $c='@echo off'+[char]13+[char]10+$q+'C:\Program Files\Microsoft VS Code\bin\code.cmd'+$q+' --extensions-dir '+$q+'C:\ProgramData\vscode-extensions'+$q+' '+$pct+'*'+[char]13+[char]10; [IO.File]::WriteAllText('C:\ProgramData\vscode-extensions\vscode.cmd',$c,[Text.Encoding]::ASCII)"

REM 拡張機能のインストール
set "CODE=C:\Program Files\Microsoft VS Code\bin\code.cmd"
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot-chat
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.python
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.vscode-pylance
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.debugpy
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension MS-CEINTL.vscode-language-pack-ja
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension saoudrizwan.claude-dev
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension rust-lang.rust-analyzer
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension tamasfe.even-better-toml
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension anthropic.claude-code
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension almenon.arepl
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --list-extensions --show-versions

REM settings.json を作成(自動更新オフ、Python、Claude Code 設定)
if not exist "%APPDATA%\Code\User" mkdir "%APPDATA%\Code\User"
python -c "import json,os;data={'update.mode':'none','update.enableWindowsBackgroundUpdates':False,'extensions.autoUpdate':False,'python.defaultInterpreterPath':r'C:\Program Files\Python312\python.exe','claudeCode.environmentVariables':[{'name':'ANTHROPIC_API_KEY','value':'not-needed'},{'name':'ANTHROPIC_AUTH_TOKEN','value':'ollama'},{'name':'ANTHROPIC_BASE_URL','value':'http://localhost:11434'},{'name':'ANTHROPIC_MODEL','value':'glm-4.7-flash'},{'name':'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC','value':'1'}]};p=os.path.join(os.environ['APPDATA'],'Code','User','settings.json');open(p,'w',encoding='utf-8').write(json.dumps(data,indent=4));print('Done:',p)"

REM 自動更新の抑止ポリシー(settings.json に加えて、レジストリ側でも明示的にオフ)
reg add "HKLM\SOFTWARE\Policies\Microsoft\VSCode" /v "UpdateMode" /t REG_SZ /d "none" /f
echo === セットアップ完了 ===

2. Python インタプリタの選択

同一マシンに複数の Python がインストールされている場合,VS Code で使用する Python 本体(インタプリタ:Python プログラムを解釈・実行するソフトウェア)を選択する必要がある.

  1. コマンドパレット(コマンド名で機能を呼び出す VS Code の入力欄)を開く(Ctrl+Shift+P
  2. Python: Select Interpreter と入力する
  3. 表示される一覧から,使用する Python(例:C:\Program Files\Python312\python.exe)を選択する.

第4章 指文字3次元ビューワのプログラム

概要

本プログラムは、日本語指文字46文字それぞれの手形を3次元で表示する。画面左のボタンで文字を選ぶと、その文字の手形が右側のキャンバスに描画され、文字の由来と指の状態(伸ばす/曲げる)が表示される。手形のデータはプログラム内に定数として持つため、カメラや学習済みモデルを使わずに動作する。

主要技術

21点ハンドランドマーク

MediaPipe Hand Landmarker は、手を21個の点で表す[1]。点の番号は、0が手首、1から4が親指、5から8が人差し指、9から12が中指、13から16が薬指、17から20が小指であり、各指は付け根から指先へ向かって番号が増える。本プログラムの手形データと骨格の接続は、この規約に従う。

透視投影

3次元座標を2次元の画面に描くために、奥にあるものほど小さく描く変換を行う。本プログラムでは、視点からの距離に応じた倍率を座標に掛けることで実現する。

画家のアルゴリズム

奥にあるものから順に描き、手前のもので上書きすることで前後関係を表す描画方法である。本プログラムは、線と点をまとめて奥行き順に並べ替えてから描画する。

技術的特徴

操作方法

参考文献

ソースコード

"""
プログラム名: 日本語指文字 3次元ビューワ
特徴技術名: MediaPipe Hand Landmarker の21点ハンドランドマーク規約
出典: Google. Hand landmarks detection guide. https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker
特徴機能: 手を21点(0:手首、1-4:親指、5-8:人差し指、9-12:中指、13-16:薬指、17-20:小指)で表す規約に従い、
          日本語指文字46文字の手形を3次元座標として保持し、透視投影で描画する
学習済みモデル: 使用しない(手形データはプログラム内に定数として保持)
方式設計:
  関連利用技術:
    - tkinter: ウィンドウとCanvasによる描画、マウス操作の受け取り(Python標準ライブラリ)
    - math: 回転行列の計算に用いる三角関数(Python標準ライブラリ)
  入力と出力: 入力: 画面左の文字選択ボタン、キャンバス上のマウス操作(ドラッグで回転、ホイールで拡大縮小)、
              出力: キャンバスへの3次元手形の描画と、文字の説明・指の状態の表示
  処理手順: 1.文字選択→2.手形の21点座標を取得→3.重心を原点へ移動→4.X軸・Y軸まわりに回転→
            5.透視投影で2次元座標へ変換→6.奥のものから順に線と点を描画
  前処理、後処理: 前処理:重心を原点へ移動、後処理:奥行きに応じて色を白に近づけ、奥のものから描画
  追加処理: 指ごとの色分け表示
  調整を必要とする設定値: zoom(表示倍率、初期値1500)、rotation_x/rotation_y(初期の視点角度)
将来方策: 手形データの精度向上 - 実際の手をMediaPipe Hand Landmarkerで計測した座標に置き換える
その他の重要事項: 追加パッケージのインストールは不要(Python標準ライブラリのみで動作)
前準備: なし
"""

import tkinter as tk
import math

# ----------------------------------------------------------------------
# 全文字のランドマークデータ
# ----------------------------------------------------------------------
# 21点の [X, Y, Z] (単位はメートル相当)。Zは値が大きいほど奥(カメラから遠い)。
LANDMARKS_DATA = {
    # ランドマークの構造: [X, Y, Z]
    'あ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.08,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'い': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.08,0.0],[0.0,0.085,0.01],[0.0,0.09,0.01]],
    'う': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'え': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.02],[0.025,0.08,0.025],[0.025,0.09,0.03],[0.0,0.05,0.0],[0.0,0.07,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03],[-0.025,0.05,0.0],[-0.025,0.07,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03],[-0.045,0.045,0.0],[-0.045,0.065,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03]],
    'お': [[0.0,0.0,0.0],[0.02,0.015,-0.015],[0.04,0.03,-0.005],[0.04,0.045,0.0],[0.03,0.055,0.015],[0.01,0.05,0.0],[0.01,0.07,0.005],[0.01,0.08,0.01],[0.01,0.09,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.08,0.01],[0.0,0.09,0.015],[-0.01,0.05,0.0],[-0.01,0.07,0.005],[-0.01,0.08,0.01],[-0.01,0.09,0.015],[-0.025,0.045,0.0],[-0.025,0.065,0.005],[-0.025,0.08,0.01],[-0.025,0.095,0.015]],
    'か': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'き': [[0.0,0.0,0.0],[0.05,0.01,-0.01],[0.08,0.025,0.0],[0.1,0.04,0.02],[0.11,0.05,0.04],[0.04,0.05,0.0],[0.04,0.09,0.0],[0.04,0.13,0.0],[0.04,0.16,0.0],[0.0,0.05,0.0],[0.0,0.07,0.02],[0.0,0.09,0.03],[0.0,0.1,0.04],[-0.04,0.05,0.0],[-0.04,0.07,0.02],[-0.04,0.09,0.03],[-0.04,0.1,0.04],[-0.07,0.045,0.0],[-0.07,0.08,0.0],[0.0,0.095,0.0],[0.0,0.1,0.0]],
    'く': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.07,0.095,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'け': [[0.0,0.0,0.0],[0.02,0.015,-0.015],[0.035,0.035,-0.01],[0.05,0.05,0.0],[0.06,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.09,0.0],[-0.025,0.13,0.0],[-0.025,0.16,0.0],[-0.045,0.045,0.0],[-0.045,0.085,0.0],[0.0,0.11,0.0],[0.0,0.12,0.0]],
    'こ': [[0.0,0.0,0.0],[0.04,0.015,-0.015],[0.06,0.035,-0.01],[0.07,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.05,0.0],[0.025,0.05,0.0],[0.025,0.05,0.0],[0.0,0.05,0.0],[0.0,0.05,0.0],[0.0,0.05,0.0],[0.0,0.05,0.0],[-0.025,0.05,0.0],[-0.025,0.05,0.0],[-0.025,0.05,0.0],[-0.025,0.05,0.0],[-0.045,0.045,0.0],[-0.045,0.045,0.0],[0.0,0.045,0.0],[0.0,0.045,0.0]],
    'さ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'し': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'す': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.02],[0.025,0.08,0.025],[0.025,0.09,0.03],[0.0,0.05,0.0],[0.0,0.07,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03],[-0.025,0.05,0.0],[-0.025,0.07,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03],[-0.045,0.045,0.0],[-0.045,0.065,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03]],
    'せ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'そ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.02],[0.025,0.08,0.025],[0.025,0.09,0.03],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.09,0.0],[-0.025,0.13,0.0],[-0.025,0.16,0.0],[-0.045,0.045,0.0],[-0.045,0.085,0.0],[0.0,0.11,0.0],[0.0,0.12,0.0]],
    'た': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'ち': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'つ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'て': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.09,0.0],[-0.025,0.13,0.0],[-0.025,0.16,0.0],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'と': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'な': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'に': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'ぬ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'ね': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'の': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'は': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'ひ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'ふ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'へ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'ほ': [[0.0,0.0,0.0],[0.02,0.015,-0.015],[0.035,0.035,-0.01],[0.05,0.05,0.0],[0.06,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.09,0.0],[-0.025,0.13,0.0],[-0.025,0.16,0.0],[-0.045,0.045,0.0],[-0.045,0.085,0.0],[0.0,0.11,0.0],[0.0,0.12,0.0]],
    'ま': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'み': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'む': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'め': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'も': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'や': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0]],
    'ゆ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'よ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'ら': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'り': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'る': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'れ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'ろ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'わ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'を': [[0.0,0.0,0.0],[0.02,0.015,-0.015],[0.035,0.035,-0.01],[0.05,0.05,0.0],[0.06,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
    'ん': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]]
}

# 各文字の説明
DESCRIPTIONS = {
    'あ': 'アルファベット「a」', 'い': 'アルファベット「i」', 'う': 'アルファベット「u」',
    'え': 'アルファベット「e」', 'お': 'アルファベット「o」', 'か': 'アルファベット「k」',
    'き': '影絵の「きつね」', 'く': '数詞手話「9」と同じ', 'け': '親指だけを曲げる',
    'こ': 'カタカナ「コ」', 'さ': 'アルファベット「s」', 'し': '数詞手話「7」と同じ',
    'す': 'カタカナ「ス」', 'せ': '中指を背に例えて立てる', 'そ': '手話「それ」と同じ',
    'た': 'アルファベット「t」', 'ち': 'カタカナ「チ」', 'つ': 'カタカナ「ツ」',
    'て': '手そのものを表示', 'と': 'あなた「と」2人で', 'な': 'アルファベット「n」',
    'に': 'カタカナ「ニ」', 'ぬ': '手話「盗む」と同じ', 'ね': '手話「根」と同じ',
    'の': '手首を動かして、カタカナ「ノ」を空書き', 'は': 'アルファベット「h」',
    'ひ': '数詞手話「1」と同じ', 'ふ': 'カタカナ「フ」', 'へ': 'カタカナ「ヘ」',
    'ほ': '船の「帆」', 'ま': 'アルファベット「m」', 'み': 'カタカナ「ミ」',
    'む': '数詞手話「6」と同じ', 'め': '「目」',
    'も': '親指と人さし指を開いた状態から、お互いに合わせる', 'や': 'アルファベット「y」',
    'ゆ': '温泉マークの「湯」', 'よ': '「4」本の指を横に出す', 'ら': 'アルファベット「r」',
    'り': '人さし指と中指の2本で手首を動かして、カタカナ「リ」を空書き',
    'る': 'カタカナ「ル」', 'れ': 'カタカナ「レ」', 'ろ': 'カタカナ「ロ」',
    'わ': 'アルファベット「w」', 'を': '指文字「お」を、そのまま自分の方に少し引き寄せる',
    'ん': '手首を動かして、カタカナ「ン」を空書き'
}

# 簡略化された指の状態データ(親, 人, 中, 薬, 小 の状態と、手形の説明)
FINGER_SHAPES_SIMPLE = {
    'あ': ['伸', '曲', '曲', '曲', '曲', '親指を立てる'],
    'い': ['曲', '曲', '曲', '曲', '伸', '小指を立てる'],
    'う': ['曲', '伸', '伸', '曲', '曲', '人差指と中指を立てる'],
    'え': ['曲', '曲', '曲', '曲', '曲', '指先を軽く曲げて広げる'],
    'お': ['他', '曲', '曲', '曲', '曲', '親指と他の指で輪を作る'],
    'か': ['曲', '伸', '曲', '曲', '曲', '人差指を立てて、中指を前に出す'],
    'き': ['曲', '伸', '曲', '曲', '伸', '人差指と小指を立てて、狐の影絵'],
    'く': ['伸', '曲', '曲', '曲', '曲', '親指を立て、手の側面を横に向ける'],
    'け': ['曲', '曲', '曲', '曲', '曲', '親指を曲げて、他の指を伸ばす'],
    'こ': ['曲', '伸', '伸', '伸', '伸', '指を揃え、手のひらを横(相手側)に向ける'],
    'さ': ['曲', '伸', '伸', '伸', '伸', '指を揃えて伸ばす'],
    'し': ['曲', '伸', '曲', '曲', '曲', '人差指を立てて、手のひらを横(相手側)に向ける'],
    'す': ['曲', '曲', '曲', '曲', '曲', '指を揃えて軽く曲げる'],
    'せ': ['曲', '曲', '伸', '曲', '曲', '中指を背に例えて立てる'],
    'そ': ['曲', '曲', '伸', '伸', '伸', '手を広げ、手のひらを下に向ける (手話の「それ」)'],
    'た': ['曲', '曲', '曲', '曲', '曲', '親指と人差指を曲げ、直角に近い形にする'],
    'ち': ['曲', '曲', '曲', '曲', '曲', '手のひらを下にして、指を軽く曲げる'],
    'つ': ['曲', '曲', '曲', '曲', '曲', '指を軽く曲げて、手を少し前に出す'],
    'て': ['伸', '伸', '伸', '伸', '伸', '手のひらを横(相手側)に向け、手全体を表す'],
    'と': ['他', '他', '曲', '曲', '曲', '人差指と中指で輪を作る'],
    'な': ['曲', '伸', '伸', '伸', '伸', '指を揃えて伸ばす(「さ」と同じ形)'],
    'に': ['曲', '伸', '伸', '曲', '曲', '人差指と中指を立てる(「に」の形に似る)'],
    'ぬ': ['伸', '曲', '曲', '曲', '曲', '親指と人差指でつまむ形(手話の「盗む」)'],
    'ね': ['曲', '曲', '曲', '曲', '曲', '手を軽く握り、親指を立てる(手話の「根」)'],
    'の': ['伸', '伸', '伸', '伸', '伸', '手首を動かして「ノ」の字を空書き'],
    'は': ['伸', '伸', '伸', '伸', '伸', '手のひらを相手側に向ける'],
    'ひ': ['伸', '曲', '曲', '曲', '曲', '人差指を立てる'],
    'ふ': ['伸', '伸', '伸', '伸', '伸', '親指と人差指を少し離し、他は揃える'],
    'へ': ['伸', '伸', '伸', '伸', '伸', '手のひらを下に向けて、指を揃える'],
    'ほ': ['曲', '伸', '伸', '伸', '伸', '親指を曲げ、他の指を伸ばす(船の帆)'],
    'ま': ['伸', '伸', '伸', '伸', '曲', '親指、人差指、中指を伸ばし、薬指、小指を曲げる'],
    'み': ['曲', '伸', '伸', '曲', '曲', '人差指と中指を立てる'],
    'む': ['伸', '曲', '曲', '曲', '曲', '人差指から薬指までを曲げ、親指と小指を立てる'],
    'め': ['曲', '曲', '曲', '曲', '曲', '人差指と中指を曲げて目の形を作る'],
    'も': ['他', '他', '曲', '曲', '曲', '親指と人差指をくっつけてから開く'],
    'や': ['曲', '曲', '曲', '伸', '伸', '小指と薬指を立てる(アルファベットの「Y」)'],
    'ゆ': ['伸', '伸', '伸', '伸', '伸', '指先を揃えて、手のひらを上に向ける(温泉マーク)'],
    'よ': ['伸', '伸', '伸', '伸', '曲', '4本の指を横に出す'],
    'ら': ['曲', '伸', '伸', '曲', '曲', '人差指と中指を交差させる'],
    'り': ['曲', '伸', '伸', '曲', '曲', '人差指と中指を立てて「リ」の字を空書き'],
    'る': ['曲', '曲', '曲', '曲', '曲', '人差指と中指を曲げて、小指と薬指を伸ばす'],
    'れ': ['伸', '伸', '伸', '伸', '伸', '人差指を少し曲げ、他の指は揃える'],
    'ろ': ['曲', '曲', '曲', '曲', '曲', '指を軽く曲げ、手のひらを横(相手側)に向ける'],
    'わ': ['伸', '伸', '伸', '伸', '伸', '指を揃えて、手のひらを上に向ける'],
    'を': ['他', '曲', '曲', '曲', '曲', '指文字「お」を作り、自分の方に少し引き寄せる'],
    'ん': ['伸', '伸', '伸', '伸', '伸', '手首を動かして「ン」の字を空書き']
}

# 手の骨格接続(MediaPipe Hand Landmarker の接続規約に基づく)
HAND_CONNECTIONS = [
    (0,1),(1,2),(2,3),(3,4),    # 親指
    (0,5),(5,6),(6,7),(7,8),    # 人差し指
    (5,9), # 人差し指の付け根(5)から中指の付け根(9)
    (9,10),(10,11),(11,12),   # 中指
    (9,13), # 中指の付け根(9)から薬指の付け根(13)
    (13,14),(14,15),(15,16),  # 薬指
    (13,17), # 薬指の付け根(13)から小指の付け根(17)
    (0,17),(17,18),(18,19),(19,20), # 小指と手首(0)への接続
]

# 手のひら接続の判定用セット
PALM_CONNECTIONS_SET = {
    (5, 9), (9, 5), (9, 13), (13, 9), (13, 17), (17, 13), (5,0), (9,0), (13,0), (17,0)
}

# 指のグループ定義 (0=手首)
FINGER_GROUPS = {
    'thumb':  {0, 1, 2, 3, 4},
    'index':  {0, 5, 6, 7, 8},
    'middle': {0, 9, 10, 11, 12},
    'ring':   {0, 13, 14, 15, 16},
    'pinky':  {0, 17, 18, 19, 20},
}

# 指ごとの色設定
FINGER_COLORS = {
    'thumb': '#FF5555',     # 親指:赤
    'index': '#55FF55',     # 人差し指:緑
    'middle': '#5555FF',    # 中指:青
    'ring': '#FFFF55',      # 薬指:黄
    'pinky': '#FF55FF',     # 小指:マゼンタ
    'palm': '#AAAAAA'       # 手のひら:グレー
}

def get_connection_color(conn):
    """接続線の色と太さを取得する"""
    start, end = conn

    # 手のひらを構成する接続 (付け根同士)
    # 太さ: 2 (細い)
    if conn in PALM_CONNECTIONS_SET:
        return FINGER_COLORS['palm'], 2

    # 指ごとの色判定 (太さ: 5 (太い))
    # 親指
    if start >= 0 and end <= 4:
        return FINGER_COLORS['thumb'], 5
    # 人差し指
    if start >= 5 and end <= 8:
        return FINGER_COLORS['index'], 5
    # 中指
    if start >= 9 and end <= 12:
        return FINGER_COLORS['middle'], 5
    # 薬指
    if start >= 13 and end <= 16:
        return FINGER_COLORS['ring'], 5
    # 小指
    if start >= 17 and end <= 20:
        return FINGER_COLORS['pinky'], 5

    return FINGER_COLORS['palm'], 2 # デフォルトは手のひらとして細く

def _get_color_with_depth(hex_color, depth, min_depth=-0.1, max_depth=0.1):
    """深度に応じて色を薄くする(白に近づける)"""
    # 深度を 0 (手前) 〜 1 (奥) に正規化
    depth_ratio = (depth - min_depth) / (max_depth - min_depth)
    depth_ratio = max(0.0, min(1.0, depth_ratio))

    # 奥にあるものほど強く白を混ぜる
    fade_factor = depth_ratio * 0.7

    # 16進カラーコード (#RRGGBB) を RGBタプルに変換
    r = int(hex_color[1:3], 16)
    g = int(hex_color[3:5], 16)
    b = int(hex_color[5:7], 16)

    # 白 (255, 255, 255) に近づける
    r_new = int(r + (255 - r) * fade_factor)
    g_new = int(g + (255 - g) * fade_factor)
    b_new = int(b + (255 - b) * fade_factor)

    return f"#{r_new:02x}{g_new:02x}{b_new:02x}"

class Hand3DViewer:
    def __init__(self, root):
        self.root = root
        self.root.title("日本語指文字 3D Viewer")
        self.root.geometry("1000x600")

        # 変数
        self.current_char = ''
        self.rotation_x = 30
        self.rotation_y = 45
        self.zoom = 1500
        self.last_mouse_x = 0
        self.last_mouse_y = 0
        self.center_offset = (0, 0, 0)
        self.buttons = {}

        # UI構築
        self.create_ui()

        # 初期表示
        self.select_char('あ')

    def create_ui(self):
        """UI構築"""
        # タイトル
        title = tk.Label(self.root, text="日本語指文字 3D Viewer",
                         font=('Arial', 16, 'bold'))
        title.pack(pady=10)

        # メインフレーム
        main_frame = tk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # 左側:文字選択ボタン
        left_frame = tk.Frame(main_frame, width=200)
        left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))

        tk.Label(left_frame, text="文字選択", font=('Arial', 12, 'bold')).pack(pady=5)

        # スクロール可能なフレーム
        canvas = tk.Canvas(left_frame, width=180)
        scrollbar = tk.Scrollbar(left_frame, orient="vertical", command=canvas.yview)
        scrollable_frame = tk.Frame(canvas)

        scrollable_frame.bind(
            "",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )

        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)

        # あ行〜ん行のボタン配置
        hiragana_rows = [
            ('あ行', ['あ', 'い', 'う', 'え', 'お']),
            ('か行', ['か', 'き', 'く', 'け', 'こ']),
            ('さ行', ['さ', 'し', 'す', 'せ', 'そ']),
            ('た行', ['た', 'ち', 'つ', 'て', 'と']),
            ('な行', ['な', 'に', 'ぬ', 'ね', 'の']),
            ('は行', ['は', 'ひ', 'ふ', 'へ', 'ほ']),
            ('ま行', ['ま', 'み', 'む', 'め', 'も']),
            ('や行', ['や', 'ゆ', 'よ']),
            ('ら行', ['ら', 'り', 'る', 'れ', 'ろ']),
            ('わ行', ['わ', 'を', 'ん'])
        ]

        for row_name, chars in hiragana_rows:
            tk.Label(scrollable_frame, text=row_name, font=('Arial', 10, 'bold')).pack(anchor='w', pady=(10,2))
            char_frame = tk.Frame(scrollable_frame)
            char_frame.pack(fill=tk.X)

            for char in chars:
                btn = tk.Button(char_frame, text=char, width=3,
                                command=lambda c=char: self.select_char(c))
                btn.pack(side=tk.LEFT, padx=2, pady=2)
                self.buttons[char] = btn

        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 右側:3D表示
        right_frame = tk.Frame(main_frame)
        right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 情報表示
        info_frame = tk.Frame(right_frame)
        info_frame.pack(fill=tk.X, pady=5)

        self.char_label = tk.Label(info_frame, text="現在の文字: あ",
                                    font=('Arial', 14, 'bold'))
        self.char_label.pack()

        # 自動改行対応
        self.desc_label = tk.Label(info_frame, text="",
                                    font=('Arial', 11),
                                    justify=tk.LEFT)
        self.desc_label.pack(fill=tk.X, padx=10)

        # 自動改行対応
        self.simple_shape_label = tk.Label(info_frame, text="",
                                            font=('Arial', 10, 'italic'),
                                            fg="#333333",
                                            justify=tk.LEFT)
        self.simple_shape_label.pack(fill=tk.X, padx=10)

        # info_frame のサイズ変更時にラベルの wraplength を更新
        def update_wraplength(event):
            margin = 25
            self.desc_label.config(wraplength=event.width - margin)
            self.simple_shape_label.config(wraplength=event.width - margin)

        info_frame.bind('', update_wraplength)

        # 3Dキャンバス
        self.canvas = tk.Canvas(right_frame, bg='white', width=600, height=400)
        self.canvas.pack(fill=tk.BOTH, expand=True, pady=10)

        # マウスイベント
        self.canvas.bind("", self.on_mouse_down)
        self.canvas.bind("", self.on_mouse_drag)
        self.canvas.bind("", self.on_mouse_wheel) # Windows/macOS
        self.canvas.bind("", self.on_mouse_wheel)   # Linux up
        self.canvas.bind("", self.on_mouse_wheel)   # Linux down

        # 操作説明
        help_text = "マウスドラッグ: 回転 | マウスホイール: ズーム"
        tk.Label(right_frame, text=help_text, font=('Arial', 9)).pack()

    def select_char(self, char):
        """文字選択時にボタンのスタイルを変更"""

        # 古いボタンのスタイルをリセット
        if self.current_char in self.buttons:
            self.buttons[self.current_char].config(relief=tk.RAISED, bg=self.root.cget('bg'))

        self.current_char = char

        # 新しいボタンのスタイルを適用
        if self.current_char in self.buttons:
            self.buttons[self.current_char].config(relief=tk.SUNKEN, bg='#E0E0E0')

        self.update_display()

    def update_display(self):
        """表示更新でラベル内容を詳細化"""
        self.char_label.config(text=f"現在の文字: {self.current_char}")
        self.desc_label.config(text=DESCRIPTIONS[self.current_char])

        # FINGER_SHAPES_SIMPLE の情報を更新
        simple_shape_data = FINGER_SHAPES_SIMPLE[self.current_char]
        fingers = simple_shape_data[:5]
        desc = simple_shape_data[5]
        finger_labels = ['親', '人', '中', '薬', '小']
        # 指の状態を文字列化
        shape_str = ", ".join(f"{l}:{s}" for l, s in zip(finger_labels, fingers))
        self.simple_shape_label.config(text=f"形状: [{shape_str}] \n{desc}")

        # 描画の前に重心を計算
        landmarks = LANDMARKS_DATA[self.current_char]
        center_x = sum(p[0] for p in landmarks) / len(landmarks)
        center_y = sum(p[1] for p in landmarks) / len(landmarks)
        center_z = sum(p[2] for p in landmarks) / len(landmarks)
        self.center_offset = (center_x, center_y, center_z)

        self.draw_3d_hand()

    def rotate_point(self, x, y, z):
        """3D回転"""
        # X軸回転
        rad_x = math.radians(self.rotation_x)
        y_new = y * math.cos(rad_x) - z * math.sin(rad_x)
        z_new = y * math.sin(rad_x) + z * math.cos(rad_x)
        y, z = y_new, z_new

        # Y軸回転
        rad_y = math.radians(self.rotation_y)
        x_new = x * math.cos(rad_y) + z * math.sin(rad_y)
        z_new = -x * math.sin(rad_y) + z * math.cos(rad_y)
        x, z = x_new, z_new

        return x, y, z

    def project_point(self, x, y, z):
        """2D投影と深度による遠近法"""

        # 重心からのオフセットを適用
        x -= self.center_offset[0]
        y -= self.center_offset[1]
        z -= self.center_offset[2]

        # 回転
        x, y, z = self.rotate_point(x, y, z)

        # 投影 (遠近法)
        depth_factor = self.zoom / (self.zoom + z)

        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()

        # 座標をキャンバスの中心に合わせる
        proj_x = canvas_width / 2 + x * depth_factor * self.zoom
        proj_y = canvas_height / 2 - y * depth_factor * self.zoom # Y軸は反転

        return proj_x, proj_y, z # Z (奥行き) も返す

    def draw_3d_hand(self):
        """3D手の骨格を描画"""
        self.canvas.delete("all")

        landmarks = LANDMARKS_DATA[self.current_char]

        # 投影された座標を保存 (ソート用)。ランドマーク番号も保持する
        projected_points = []
        for index, point in enumerate(landmarks):
            proj_x, proj_y, z = self.project_point(point[0], point[1], point[2])
            projected_points.append(((proj_x, proj_y, z), index))

        # 描画要素をリストに保存 (奥から手前の順にソートして描画するため)
        draw_elements = []

        # 1. 接続線 (骨格)
        for conn in HAND_CONNECTIONS:
            start_index, end_index = conn

            # 投影された始点と終点の情報
            (x1, y1, z1), _ = projected_points[start_index]
            (x2, y2, z2), _ = projected_points[end_index]

            # 接続線の色と太さを取得
            color, width = get_connection_color(conn)

            # 線の平均深度
            avg_z = (z1 + z2) / 2

            # 深度に基づく色の調整
            shaded_color = _get_color_with_depth(color, avg_z)

            # 描画順リストに追加
            # (avg_z, 'line', coords, options)
            draw_elements.append((avg_z, 'line', (x1, y1, x2, y2), {'fill': shaded_color, 'width': width}))

        # 2. ランドマーク (関節点)
        dot_size = 5
        for (x, y, z), index in projected_points:

            # 指ごとに色を変える
            if index in FINGER_GROUPS['thumb']:
                fill_color = FINGER_COLORS['thumb']
            elif index in FINGER_GROUPS['index']:
                fill_color = FINGER_COLORS['index']
            elif index in FINGER_GROUPS['middle']:
                fill_color = FINGER_COLORS['middle']
            elif index in FINGER_GROUPS['ring']:
                fill_color = FINGER_COLORS['ring']
            elif index in FINGER_GROUPS['pinky']:
                fill_color = FINGER_COLORS['pinky']
            else: # 手首や手のひら
                fill_color = FINGER_COLORS['palm']

            # 深度に基づく色の調整
            shaded_color = _get_color_with_depth(fill_color, z)

            # 描画順リストに追加
            # (z, 'oval', coords, options)
            draw_elements.append((z, 'oval', (x-dot_size, y-dot_size, x+dot_size, y+dot_size), {'fill': shaded_color, 'outline': '#333333', 'width': 1}))

        # 3. 描画順のソートと実行 (Zが大きい(奥)ものから先に描く)
        draw_elements.sort(key=lambda item: item[0], reverse=True)

        for z_depth, element_type, coords, options in draw_elements:
            if element_type == 'line':
                self.canvas.create_line(*coords, **options)
            elif element_type == 'oval':
                self.canvas.create_oval(*coords, **options)

    # --- マウス操作 ---
    def on_mouse_down(self, event):
        self.last_mouse_x = event.x
        self.last_mouse_y = event.y

    def on_mouse_drag(self, event):
        dx = event.x - self.last_mouse_x
        dy = event.y - self.last_mouse_y

        # Y軸ドラッグでX軸回転 (上下のドラッグ)
        self.rotation_x = (self.rotation_x - dy * 0.5) % 360
        # X軸ドラッグでY軸回転 (左右のドラッグ)
        self.rotation_y = (self.rotation_y + dx * 0.5) % 360

        self.last_mouse_x = event.x
        self.last_mouse_y = event.y
        self.draw_3d_hand()

    def on_mouse_wheel(self, event):
        # Windowsの場合: event.delta は 120 または -120
        # Linuxの場合: event.num は 4 (up) または 5 (down)
        if event.num == 4 or event.delta > 0:
            # ズームイン
            self.zoom = min(self.zoom + 100, 5000)
        elif event.num == 5 or event.delta < 0:
            # ズームアウト
            self.zoom = max(self.zoom - 100, 500)

        self.draw_3d_hand()

if __name__ == "__main__":
    root = tk.Tk()
    app = Hand3DViewer(root)
    root.mainloop()