プチコンプログラム

マルチタッチ検出ルーチン

【mkII専用】 2013年2月5日 公開

 ハードの制約上、1点タッチしか行えないプチコンにおいて擬似的にマルチタッチによる座標検出を実現するルーチンです。このルーチンを組み込むことでタッチパネルをタッチした状態で別の場所をタッチしてもその座標を検出することが可能になります。仕様面の問題も大きいため実用になるかは微妙ですが、良かったら皆さんのプログラムに自由に組み込んで使ってください。
 なお、リストは最適化を施していません。さらなる高速化やリスト短縮など組み込む際に自由に改造してください。

《 プログラムリスト 》

◎マルチタッチ検出ルーチン(基本部分)
@MTOUCH
RM=20:RL=1
PX=TX:PY=TY
TC=TCHST:TX=TCHX*TC:TY=TCHY*TC
TPO=TPO*TC:TP=TP*TC
TM=(POW(TX-TCX(0),2)+POW(TY-TCY(0),2)<RM*RM/4)+(POW(TX-TCX(1),2)+POW(TY-TCY(1),2)<RM*RM/4)
TL=POW(TX-PX,2)+POW(TY-PY,2)
TLM=TLM+(TL-TLM)*(TL>TLM)
MT=(TL<RL*RL/4)*(TLM>RM*RM/4)*TC*(!TM+!TP)
IF MT THEN TPO=TP+1:TCX(TP)=TX*TPO-TCX(!TP)*TP:TCY(TP)=TY*TPO-TCY(!TP)*TP:TLM=0:TP=!TP
RETURN

◎座標補正ルーチン
@MTCH2
IF !TP THEN RETURN
MTX(0)=-10:MTY(0)=-30
MTX(1)= 30:MTY(1)= 20
MTX(2)= 30:MTY(2)= 45
MTX(3)=-70:MTY(3)=-25
MTX(4)=-30:MTY(4)= 10
MTX(5)= -5:MTY(5)=-50
MTX(6)=-40:MTY(6)=-10
MTX(7)= 35:MTY(7)= 20
FOR I=0TO 1
TFX=(TCX(!I)-128)/128
TFY=(TCY(!I)-96)/96
TK=(TFX>0)+(TFY>0)*2
TFX(I)=MTX(TK+I*4)*ABS(TFX)
TFY(I)=MTY(TK+I*4)*ABS(TFY)
NEXT
TWX=ABS(TCX(0)-TCX(1))/256
TWY=ABS(TCY(0)-TCY(1))/192
TCX(TP)=0OR TCX(TP)+(TFX(0)-TFX(1)*2)*TWX
TCY(TP)=0OR TCY(TP)+(TFY(0)-TFY(1)*2)*TWY
IF TCX(TP)<0THEN TCX(TP)=0
IF TCX(TP)>255THEN TCX(TP)=255
IF TCY(TP)<0THEN TCY(TP)=0
IF TCY(TP)>191THEN TCY(TP)=191
RETURN
QRコード(ファイル名:OCHAMTCH)

《 使用方法 》

 最小限必要なのは基本部分のみです。これを自分のプログラムに組み込んで使用する際にはGOSUB @MTOUCHとしてください。変数TPOに現在タッチしているポイント数(値は0〜2)、変数TCX(0)TCY(0)に最初にタッチしたX、Y座標、変数TCX(1)TCY(1)に後からタッチしたX、Y座標が入ります。
 ただし、基本部分だけでは隅に近い部分をタッチするとかなり大きな誤差が出てしまうためそれを補正するのがもう1つの座標補正ルーチンです。この座標補正ルーチンを使うには基本部分にあるIF文の末尾にあるTP=!TPの前にGOSUB @MTCH2を入れてください。上記のQRコードでは最初からこの座標補正ルーチンが使える状態で入っています。

《 サンプルプログラム 》

 このマルチタッチ検出ルーチンを使ったサンプルプログラムを用意しました。

ACLS:CLEAR:PNLTYPE "OFF"
GPAGE 1
BGMPLAY 28

@MAIN
GCLS
FOR I=0TO 1
 GFILL 9,9+I*171,11,11+I*171,15
 GFILL 244,9+I*171,246,11+I*171,15
NEXT
GFILL 127,95,129,97,15
PNLSTR 0,18,"タッチ"+STR$(TPO),TPO+2
PNLSTR 0,19,"("+STR$(TCX(0))+","+STR$(TCY(0))+") "
PNLSTR 0,20,"("+STR$(TCX(1))+","+STR$(TCY(1))+") "
P=TPO
GOSUB @MTOUCH
FOR I=0TO TPO-1
 GCIRCLE TCX(I),TCY(I),7,2
 GPAINT TCX(I),TCY(I),2
NEXT
GFILL TX-1,TY-1,TX+1,TY+1,11
BEEP ,,-(P<TPO)
WAIT 1
GOTO @MAIN

@MTOUCH
RM=20:RL=1
PX=TX:PY=TY
TC=TCHST:TX=TCHX*TC:TY=TCHY*TC
TPO=TPO*TC:TP=TP*TC
TM=(POW(TX-TCX(0),2)+POW(TY-TCY(0),2)<RM*RM/4)+(POW(TX-TCX(1),2)+POW(TY-TCY(1),2)<RM*RM/4)
TL=POW(TX-PX,2)+POW(TY-PY,2)
TLM=TLM+(TL-TLM)*(TL>TLM)
MT=(TL<RL*RL/4)*(TLM>RM*RM/4)*TC*(!TM+!TP)
IF MT THEN TPO=TP+1:TCX(TP)=TX*TPO-TCX(!TP)*TP:TCY(TP)=TY*TPO-TCY(!TP)*TP:TLM=0:GOSUB@MTCH2:TP=!TP
RETURN

@MTCH2
IF !TP THEN RETURN
MTX(0)=-10:MTY(0)=-30
MTX(1)= 30:MTY(1)= 20
MTX(2)= 30:MTY(2)= 45
MTX(3)=-70:MTY(3)=-25
MTX(4)=-30:MTY(4)= 10
MTX(5)= -5:MTY(5)=-50
MTX(6)=-40:MTY(6)=-10
MTX(7)= 35:MTY(7)= 20
FOR I=0TO 1
 TFX=(TCX(!I)-128)/128
 TFY=(TCY(!I)-96)/96
 TK=(TFX>0)+(TFY>0)*2
 TFX(I)=MTX(TK+I*4)*ABS(TFX)
 TFY(I)=MTY(TK+I*4)*ABS(TFY)
NEXT
TWX=ABS(TCX(0)-TCX(1))/256
TWY=ABS(TCY(0)-TCY(1))/192
TCX(TP)=0OR TCX(TP)+(TFX(0)-TFX(1)*2)*TWX
TCY(TP)=0OR TCY(TP)+(TFY(0)-TFY(1)*2)*TWY
IF TCX(TP)<0THEN TCX(TP)=0
IF TCX(TP)>255THEN TCX(TP)=255
IF TCY(TP)<0THEN TCY(TP)=0
IF TCY(TP)>191THEN TCY(TP)=191
RETURN
QRコード(ファイル名:OCHAMSPL)

 タッチしている点数とその座標を表示し、タッチしている場所には赤丸が表示されるというただのマルチタッチの動作確認用のものです。しかし、実際に実行すればちゃんとマルチタッチとして認識されていることが分かると思います。
 しかし、個体差によってずれが出るのではないかと思われます。座標補正ルーチンの最初の配列変数に代入しているデータは私の手元の3DSに合わせて調整されています。もしも、タッチしている場所と赤丸が大幅(平均15〜20ドット程度)にずれている場合はこの部分の数値を変える必要があります。

 このサンプルプログラムを使って行うキャリブレーションの方法を書きます。
 画面上に5つの点が表示されていると思いますが、それを元にして田型に4つのブロックに分けて行います。
 MTX(0)MTY(0)が画面左上、MTX(1)MTY(1)が画面右上、MTX(2)MTY(2)が画面左下、MTX(3)MTY(3)が画面右下を示します。四隅の点付近のいずれかをタッチします。そして、それをタッチした状態で他の部分のブロックをタッチしていきます。その座標のずれの大きさによって対応している方向のMTX()MTY()の値を変えてください。これを4方向分すべて行います。
 ただし、この際には「後からタッチしたブロックが3つともすべて同じ方向にずれている」という場合は元となる値がずれていると考えられるためそれはMTX(4)MTY(4)MTX(7)MTY(7)の値を変える必要があります。こちらも同様に左上、右上、左下、右下の順になっています。
 うまく調整できれば、概ね誤差は平均10ドット前後(円の半径の1.5倍前後)に収まると思います。といっても、タッチする座標によって変わるため誤差を減らすのはなかなか難しいです。これ以上精度を高めるにはブロック数を4ブロックからさらに増やす必要がありますが、そうなるとキャリブレーションの手間は激増してしまいます。4ブロックに対して残り3方向の合計12通りだけですべてが網羅できる(といっても微調整の繰り返しだから実際は12回では済まないけど)のですが、ブロック数が16になったら1つのブロックに対して残り15ブロック分の調整を行う必要があり、ほぼブロック数の2乗に比例して手間が増えていきます。そこまでやる価値があるのか微妙に感じたためデメリットとメリットを天秤にかけて4ブロックのみで判断しています。誤差が非常に大きい(概ね50ドット以上)のは四隅付近なのでそれを元に補正を掛けている(補正値はタッチしている座標が中央に近づくほどそれに比例して小さくなるようにしている)ため誤差が非常に大きい状態は避けられていると思います。なお、この四隅より外側は誤差がさらに大きくなるためできるだけ使用しないようにしてください。

《 マルチタッチの原理 》

 DSで使用されている感圧式(抵抗膜)タッチパネルは2つ以上の場所を抑えた際にはそのほぼ中間の座標を示します。例えば(100,40)と(200,100)をタッチしたらTCHX=150TCHY=70くらいの値になります。(上記サンプルでは緑の点がTCHX、TCHYの値を示しています)
 しかし、TCHX、TCHYの値からではどことどこをタッチしているのかというものは逆算できません。しかし、1フレームごとにTCHXとTXHYの変化を見ていけばそれで判断することが可能になります。
 このプログラムではタッチしている(TCHST=True)状態で20ドット以上離れた別の点をタッチしたらマルチタッチと認識されます。この20という数字は変数RMに代入されている値を変えることで変更できますが、この数字は指が動いてタッチしている座標が変わった場合に「1点タッチから2点タッチになった」と誤認識されるのを避ける役割もあるのであまり小さな値にすると頻繁に誤認識してしまいます。
 しかし、マルチタッチの場合はTCHX、TCHYの値は常に変動しているためどこで確定させるかが問題になります。それを決めているのが変数RLの値です。デフォではこの値を1にしているためTCHX、TCHYの移動量が1以下(つまりほぼ静止状態)ならばマルチタッチの確定としています。この値を大きくすればマルチタッチ確定までの時間が短くなり反応も良くなりますがその分だけ誤差も大きくなります。軽く押した場合にはTCHX、TCHYの変化は緩やかになりマルチタッチを確定しても誤差が非常に大きくなるため1フレームあたりRMの値以上の移動量がないとマルチタッチとは認識しないようにしています。また、認識しなかった場合にタッチを離したら一気にTCHX、TCHYの値が変わり逆にマルチタッチと認識とされてしまうためそうならないようにRM以上離れてないと離した際にもマルチタッチと認識しないようにしています。これらの処理によってマルチタッチの誤認識はほとんど無くなっていると思います。

 実際のところタッチする場所や強さによって2点の中間の座標とは大きくかけ離れた場所になってしまうためマルチタッチそのものの仕組みは単純ですが、満足いく精度を達成するのは非常に難しいです。

《 注意点 》

 マルチタッチ検出を確実に行うためにはタッチペンや爪のようなある程度の堅さがあるもので下画面をタッチしてください。あと一定以上の強さでないとTCHX、TCHYは2点の中央付近の座標を示してくれないためそれなりにしっかりタッチしてください。
 基本的にタッチしたあとはタッチしている場所から指を動かさないでください。
 2点をタッチしてもその瞬間にTCHX、TCHYがそのタッチしている中間の値を指すことはなく数フレーム遅れます。そのためあまり激しいタッチ操作には対応はできません。また、TCHX、TCHYの変化からマルチタッチを検出しているため2カ所を1フレームの遅れもなく同時に押してしまったらマルチタッチとは見なされずその中間付近をタッチしたと見なされます。

 あと、このルーチンでは2点までしか対応しておらず、プログラムリストの単純化とタッチ精度を少しでも上げるため3点を押すと誤動作してしまいます。(2点タッチ時からTCHX、TCHYに一定以上の変動があった場合には「1点タッチになった」と見なしているため3点のほぼ中心座標をタッチしていると認識されてしまう)
 もしも誤って3点タッチしてしまいタッチ数を誤認識している場合は一旦タッチパネルから指を離せば大丈夫です。

 このように多くの問題を抱えていますが、タッチする強さでTCHX、TCHYの値が変わる関係上、精度をこれ以上大幅に高めるのは難しいためやや不満ではありますがせっかく作ったので今回公開に至りました。それらを解決する良いアイデアをお持ちの方はぜひご教示お願いします。

《 使用動画 》


 ※上記のサンプルプログラムを動作させています。(緑の点は後から追加したのでこの動画にはありません)
  タッチ数の変化と複数の場所を同時にタッチしてもそれなりに正確な場所(誤差はほぼ10ドット前後)が認識できているというのが分かると思います。


RETURN/RETURN *MAIN

inserted by FC2 system