ノートPCのタッチパッドにおけるホイール処理

ノートPCについているキーボード下のタッチパッドにおけるマウスホイール処理についてメモしておきたいと思います。
実は難物なんですこれ・・・。

イベント内容

パッドを使ったホイールスクロールの場合は、マウスによるホイールスクロールとは異なり、特殊な数値のイベントが飛んでくるので、処理やインターフェイスの根本的な見直しが必要になります。

・マウスによるホイール
MouseWheel(WheelDelta:-120) 230ms
MouseWheel(WheelDelta:-120) 102ms
MouseWheel(WheelDelta:-120) 85ms
MouseWheel(WheelDelta:-120) 123ms

・ノートPCのパッドによるホイール
MouseWheel(WheelDelta:-14) 12ms
MouseWheel(WheelDelta:-10) 10ms
MouseWheel(WheelDelta:-8) 13ms
MouseWheel(WheelDelta:-9) 8ms
・・・

マウスのホイールでは上下に1回2回といったグリッド間隔による回数によってイベントが発生していますが、パッドの場合はもっと細かい単位(ピクセル)の連続したイベントが飛んできます。
また、デバイスの設定によって離した後のイージングも発生します。
(イージングを判定するコードは見つけられませんでした。判別出来るか不明)


と、こんな違いがありますので、対応処理が必要です。
まじめに対応すると場合は、プログラム的(イベント処理)な対応と、GUI(グラフィックインターフェイス)の両方が必要になります。

対応しないとどうなるの

対応していないからと言って、そこまで深刻なバグが発生するわけではありません。
よくある実害が、パッドでスクロールさせると超高速でスクロールするぐらいです。
上記の通り、スクロールイベントが細切れで大量に飛んでくるのが原因です。

対応 - イベント処理

パッドによるイベントは、間隔が非常に狭いため、Wheel値をポーリング処理(ゲームの1frame単位で値を取得して処理)で取得している場合はかならず抜けオチが発生します。
抜けオチが発生すると、スクロールする移動量が一定で無くなります。(早くスクロールしたのに遅かったり、逆になったり)
なので、イベントから値を取得し、WheelDelta値は加算、累積しておく必要があります。

対応 - GUI(グラフィックインターフェイス

大変なのがこれ(GUI)です。
デザインそのものから見直す場合もありますので、昔の作品をプログラムを弄って対応…と単純にはいかないでしょう。


簡潔に言ってしまえば、GUIピクセル単位のスクロールに対応させることが対応処理になります。
ということとは必然的に、「1回のスクロールでページが切り替わる」といった処理はこれに反します。
例えば、スクロールでページが1進むようなのがこれに該当します。
…厳しいですね。
「一定ピクセル数のスクロールになったらページを進ませる」というのも手ですが、引っ張り動作(スクロールさせた量だけ移動するが一定未満だと元の位置に戻る)を入れないと大変不親切なインターフェイスになってしまいます。
また、イージング処理イベントも飛んでくるので、引っ張り動作が正常に出来る保証もありません…。


というわけで、ピクセル単位のスムーズなスクロールを前提に作り直さないとどうにもならないなー、と個人的には思っています。
この方式は「見えている部分のリソースだけ処理して処理とメモリをコンパクトに」というのが大変やりにくいので、リッチな処理(重い)になる傾向が強いですし、全てを対応とかでなく、天秤にかけて取捨選択が現実ラインではないかと思います。
ページ切り替えぐらいで悩むならボタンでもいいかなってね。
かしこ

おまけ

タッチデバイス時の他のジェスチャについてのログです。
ズームなどはこのイベント情報から自前で処理する必要があります。
windowsはID番号があって、これがとても役に立って優秀なのですが、それでもジェスチャー開始と終了処理は相当気を配って処理する必要があります。

■twofingertap
・pressandhold enable
touch.down...0(130,132) 2641ms
touch.move...0(130,132) 5ms
touch.down...1(227,280) 3ms
touch.move...0(130,132) 3ms
touch.move...1(227,280) 3ms
touch.move...0(130,132) 5ms
touch.move...1(227,280) 3ms
touch.move...0(130,132) 16ms
touch.move...1(227,280) 4ms
touch.up...0(130,132) 4ms
touch.move...1(227,280) 3ms
touch.up...0(227,280) 5ms
mouse(FromTouch).move...0(130,132) 4ms
mouse(FromTouch).down...0(130,132)Left 4ms
mouse(FromTouch).up...0(130,132)Left 4ms
mouse(FromMouse).move...0(130,132) 4ms

・pressandhold disable
touch.down...0(141,176) 6649ms
touch.move...0(141,176) 6ms
touch.down...1(208,299) 3ms
touch.move...0(141,176) 3ms
touch.move...1(208,299) 3ms
mouse(FromTouch).move...0(141,176) 4ms
mouse(FromTouch).down...0(141,176)Left 3ms
touch.move...0(141,176) 3ms
touch.move...1(208,299) 3ms
touch.move...0(141,176) 9ms
touch.move...1(208,299) 4ms
touch.up...0(141,176) 3ms
touch.move...1(208,299) 4ms
touch.up...0(208,299) 5ms
mouse(FromTouch).up...0(141,176)Left 4ms
mouse(FromMouse).move...0(141,176) 4ms

■touch.zoom
touch.down...0[2276](179,160) 27957ms
touch.move...0[2276](179,160) 7ms
touch.down...1[2277](336,261) 2ms
touch.move...0[2276](179,160) 4ms
touch.move...1[2277](352,286) 5ms
touch.move...0[2276](158,148) 7ms
touch.move...1[2277](356,292) 6ms
mouse(FromTouch).move...0(179,160) 6ms
mouse(FromTouch).down...0(179,160)Left 6ms
mouse(FromTouch).move...0(154,144)Left 5ms
touch.move...0[2276](154,144) 7ms
touch.move...1[2277](366,305) 5ms
mouse(FromTouch).move...0(146,137)Left 6ms
touch.move...0[2276](146,137) 7ms
touch.move...1[2277](372,312) 5ms
mouse(FromTouch).move...0(141,132)Left 5ms
touch.move...0[2276](141,132) 7ms
touch.move...1[2277](377,316) 5ms
mouse(FromTouch).move...0(138,129)Left 6ms
touch.move...0[2276](131,123) 6ms
touch.move...1[2277](391,331) 5ms
touch.up...0[2276](131,123) 5ms
touch.move...1[2277](391,331) 5ms
touch.move...0[2277](391,331) 6ms
mouse(FromTouch).up...0(131,123)Left 5ms
touch.move...0[2277](391,331) 6ms
touch.up...0[2277](391,331) 5ms
mouse(FromMouse).move...0(131,123) 6ms

ジェスチャーデバイス

タッチ操作をOSが処理して、専用のイベントを発生させます。
このモードの特徴は、クリックとプレスアンドホールド以外は、マウスイベントが全く発生しません。
パンであってもマウスムーブ(MouseMove)が発生しません。
これはスクロールバーの操作などを行えないことを意味します。(クリックはできるけど)
また、ウインドウのタイトルバーでもイベントを吸収してしまうため、ウインドウの移動やリサイズなどの処理に特殊なコードを書く必要があります。


まさしくタッチ操作に特化したモードではあるのですが、マウスやペンと共存させる場合はいささか機能不足や痒いところに手が届かない事になるでしょう。
フルスクリーンタッチアプリを作るのであればこれほど強力な物は無いのですが。

■gestue.singletouch
・pressandhold enbale
wait		//指を一定以上動かすまで反応無し
mouse(FromTouch).move...0(186,180) 3627ms
mouse(FromTouch).down...0(186,180)Left 5ms
mouse(FromTouch).up...0(186,180)Left 7ms
mouse(FromMouse).move...0(186,180) 6ms

・pressandhold disable
※singletouch.enableに同じ

■gestue.pressandhold
・pressandhold enable
wait		//指を一定以上動かすまで反応無し
mouse(FromTouch).move...0(158,172) 8052ms
mouse(FromTouch).down...0(158,172)Right 13ms
mouse(FromTouch).up...0(158,172)Right 8ms  //singletouchと異なりmoveが来ない

・pressandhold disable
※singletouch.enableに同じ

■gestue.enable.pan
・pressandhold enable
wait		//指を一定以上動かすまで反応無し
gesture.begin...(137,129) 4220ms  //マウスイベントは発生しない
gesture.pan...length=0 vector=0,0(137,129) 9ms
gesture.pan...length=0 vector=0,0(137,155) 9ms
gesture.pan...length=0 vector=0,0(137,157) 7ms
〜〜〜
gesture.pan...length=0 vector=0,1(137,184) 9ms
gesture.pan...length=0 vector=0,1(137,184) 8ms
gesture.pan...length=0 vector=0,0(137,184) 19ms
gesture.pan...length=0 vector=0,1308104(137,184) 6ms
gesture.end...(137,184) 6ms

・pressandhold disable
※pan.enableに同じ

■gesture.zoom
・pressandhold enable
gesture.begin...(201,139) 6558ms
gesture.zoom...zoom=106 pos=0,0(214,190) 27ms
gesture.zoom...zoom=148 pos=0,0(233,206) 9ms
〜〜〜
gesture.zoom...zoom=193 pos=0,0(230,203) 10ms  //zoomには慣性は無い
gesture.zoom...zoom=193 pos=0,0(230,203) 6ms
gesture.end...(230,203) 6ms

・pressandhpld disable
※zoom.enableに同じ


■gesture.rotate
・pressandhold enable
gesture.begin...(221,312) 4195ms
gesture.rotate...rot=40807 pos=0,0(221,312) 6ms
gesture.rotate...rot=33791 pos=0,0(221,312) 7ms
〜〜〜
gesture.rotate...rot=34541 pos=0,0(225,313) 5ms	 //rotateに慣性は無い
gesture.rotate...rot=34541 pos=0,0(225,313) 5ms
gesture.end...(199,231) 49ms


・pressandhold disable
※enableに同じ

タッチデバイス

生データに直接アクセス、がこのモードになります。
生データだけあって、触れている指の数(ID)、座標、ぐらいしか取得出来ません。
反応は最も早く、ログを見て貰うとわかるとおり、マウスのマウスダウンよりも10ms以上早くタッチイベントが飛んでくるので、ゲーム向きな素早い入力を得ることが出来ます。
ただし、プレスアンドホールドを考慮するのであれば、このモードの恩恵(素早い入力)は全く受けられないので注意してください。

コードサンプルはこちらの方が簡潔にまとめられております。
http://wlog.flatlib.jp/item/1320

■touch.singletouch
・pressandhold enable
touch.down...0(177,126) 3875ms		//LeftClickの前にsigletouchが発生する
touch.move...0(177,126) 2ms
touch.move...0(177,126) 5ms
〜〜〜
touch.move...0(177,126) 9ms
touch.move...0(177,126) 9ms
touch.move...0(177,126) 18ms
touch.up...0(177,126) 5ms
mouse(FromTouch).move...0(177,126) 5ms  //指を離したタイミングでmouseイベントが発生
mouse(FromTouch).down...0(177,126)Left 4ms
mouse(FromTouch).up...0(177,126)Left 4ms
mouse(FromMouse).move...0(177,126) 4ms

・pressandhold disable
touch.down...0(191,152) 4397ms
mouse(FromTouch).move...0(191,152) 3ms
mouse(FromTouch).down...0(191,152)Left 3ms  //mousedownがちゃんと発生
touch.move...0(191,152) 3ms
touch.move...0(191,152) 7ms
〜〜〜
touch.move...0(191,152) 9ms
touch.move...0(191,152) 19ms
touch.up...0(191,152) 3ms
mouse(FromTouch).up...0(191,152)Left 5ms
mouse(FromMouse).move...0(191,152) 4ms

■touch.pressandhold
・pressandhold enable
touch.down...0(177,189) 7346ms    //RightClickの前にsigletouchが発生する
touch.move...0(177,189) 10ms
touch.move...0(177,189) 8ms
〜〜〜
touch.move...0(177,189) 9ms
touch.move...0(177,189) 8ms
touch.move...0(177,189) 19ms
touch.up...0(177,189) 6ms
mouse(FromTouch).move...0(177,189) 6ms  //指を離したタイミングでmouseイベントが発生
mouse(FromTouch).down...0(177,189)Right 5ms
mouse(FromTouch).up...0(177,189)Right 5ms

・pressandhold disable
touch.down...0(227,193) 5982ms
touch.move...0(227,193) 9ms
mouse(FromTouch).move...0(227,193) 5ms
mouse(FromTouch).down...0(227,193)Left 5ms
touch.move...0(227,193) 5ms
touch.move...0(227,193) 5ms
〜〜〜
touch.move...0(227,193) 8ms
touch.move...0(227,193) 20ms
touch.up...0(227,193) 7ms
mouse(FromTouch).up...0(227,193)Left 5ms
mouse(FromMouse).move...0(227,193) 5ms

■touch.pan
・pressandhold enable
touch.down...0(141,154) 11386ms  //pan開始前にtouch.downが発生
touch.move...0(141,154) 14ms
touch.move...0(141,154) 7ms
〜〜〜
mouse(FromTouch).move...0(141,154) 7ms  //pan開始と同時にmouse.moveが追加発生
mouse(FromTouch).down...0(141,154)Left 6ms
mouse(FromTouch).move...0(155,172)Left 5ms
touch.move...0(160,182) 5ms
mouse(FromTouch).move...0(160,182)Left 5ms
touch.move...0(163,188) 5ms
〜〜〜
mouse(FromTouch).move...0(181,275)Left 6ms
touch.move...0(181,276) 5ms
mouse(FromTouch).move...0(181,276)Left 6ms
touch.move...0(181,276) 5ms
touch.move...0(181,276) 12ms
touch.up...0(181,276) 6ms  //touch.up/mouse.upの順序は機種依存
mouse(FromTouch).up...0(181,276)Left 5ms
mouse(FromMouse).move...0(181,276) 6ms

・pressandhold disable
touch.down...0(170,146) 5781ms
touch.move...0(170,146) 16ms
mouse(FromTouch).move...0(170,146) 7ms
mouse(FromTouch).down...0(170,146)Left 5ms  //mouse.downはtouch.downより遅延がある
touch.move...0(170,146) 5ms
touch.move...0(170,146) 5ms
〜〜〜
touch.move...0(184,157) 9ms
touch.move...0(186,159) 9ms
〜〜〜
mouse(FromTouch).move...0(186,159)Left 5ms  //pan開始と同時にmouseイベントが追加される
touch.move...0(188,163) 5ms
mouse(FromTouch).move...0(188,163)Left 6ms
touch.move...0(190,166) 5ms
〜〜〜
mouse(FromTouch).move...0(162,299)Left 5ms
touch.move...0(154,299) 6ms
mouse(FromTouch).move...0(154,299)Left 5ms
touch.move...0(154,299) 10ms
touch.up...0(154,299) 6ms
mouse(FromTouch).up...0(154,299)Left 6ms
mouse(FromMouse).move...0(154,299) 6ms

プレスアンドホールド対応時の注意点

生データだけあって、マウスイベントより早い段階でタッチ操作イベントが飛んできます。
コレを使えばより素早い反応が可能ですが、プレスアンドホールドを考慮する場合は、このイベントが使用出来ません。
ログを見て貰うとわかるのですが、

・pressandhold enable
touch.down...0(177,189) 7346ms    //RightClickの前にsigletouchが発生する
touch.move...0(177,189) 10ms
touch.move...0(177,189) 8ms
〜〜〜
touch.move...0(177,189) 9ms
touch.move...0(177,189) 8ms
touch.move...0(177,189) 19ms
touch.up...0(177,189) 6ms
mouse(FromTouch).move...0(177,189) 6ms  //指を離したタイミングでmouseイベントが発生
mouse(FromTouch).down...0(177,189)Right 5ms
mouse(FromTouch).up...0(177,189)Right 5ms

[タッチイベント(左)][マウスイベント(右)]と完全に分かれてくるため、左クリックして右クリックしたかのような挙動を示します。
対応を考えるなら、タッチイベントは捨ててしまうのが賢明なようです。

レガシーデバイス

旧来のそのままの状態をレガシーとwindowsは定義しています。
この状態ではwindowsはタッチ操作をマウス操作(イベント)に置き換え、マウスの代行としてアプリを制御出来るようにしています。
Windows Touch Gestures Overview
http://www.domoneo.com/windows_touch_gestures_overview.htm
(うちではpanによるマウススクロールが何故か拾えなかったんですけど、まぁいいや)

結論から言うと、プレスアンドホールドを無効化してもマウスダウンが発生しないのがレガシーの最大の弱点ですが、後記する魔法のおまじないを行うことで発生させることが可能なため、意外と使えます。

■legacy.singletouch
・pressandhold enable
wait	//指を離すまでイベント無し
mouse(FromTouch).move...0(194,154) 7543ms
mouse(FromTouch).down...0(194,154)Left 11ms
mouse(FromTouch).up...0(194,154)Left 6ms
mouse(FromMouse).move...0(194,154) 7ms

・pressandhold disable
mouse(FromTouch).move...0(149,187) 2542ms
mouse(FromTouch).down...0(149,187)Left 3ms
wait	//指を離すまでイベント無し
mouse(FromTouch).up...0(149,187)Left 60ms
mouse(FromMouse).move...0(149,187) 7ms

■legacy.pressandhold
・pressandhold enable
wait    //指を離すまでイベント無し
mouse(FromTouch).move...0(121,171) 9534ms
mouse(FromTouch).down...0(121,171)Right 18ms
mouse(FromTouch).up...0(121,171)Right 11ms

・pressandhold disable
wait	//SetGestureConfig(Block:Allgesture)で発生しなくなる。OSのバグ?
mouse(FromTouch).move...0(131,207) 4246ms
mouse(FromTouch).down...0(131,207)Left 3ms
wait	//指を離すまでイベント無し
mouse(FromTouch).up...0(131,207)Left 1374ms
mouse(FromMouse).move...0(131,207) 8ms

■legasy.pan
・pressandhold enable
mouse(FromTouch).move...0(132,104) 6204ms
mouse(FromTouch).down...0(132,104)Left 24ms
mouse(FromTouch).move...0(132,126)Left 12ms
mouse(FromTouch).move...0(132,134)Left 5ms
〜〜〜
mouse(FromTouch).move...0(132,170)Left 18ms
mouse(FromTouch).move...0(132,171)Left 27ms
mouse(FromTouch).up...0(132,171)Left 48ms
mouse(FromMouse).move...0(132,171) 7ms

・pressandhold disable
mouse(FromTouch).move...0(89,151) 7831ms
mouse(FromTouch).down...0(89,151)Left 7ms
mouse(FromTouch).move...0(89,179)Left 49ms
mouse(FromTouch).move...0(92,186)Left 9ms
〜〜〜
mouse(FromTouch).move...0(110,292)Left 18ms
mouse(FromTouch).move...0(111,293)Left 8ms
mouse(FromTouch).up...0(111,293)Left 22ms
mouse(FromMouse).move...0(111,293) 5ms

レガシーでマウスダウンを発生させる

レガシーに以下のおまじないをかけると発生するようになります。

gconf.dwID   := 0;
gconf.dwWant := 0;
gconf.dwBlack:= GC_ALLGESTURES;
SetGestureConfig(Handle, 0, 1, gconf, sizeof(TGESTURECONFIG));

後記するジェスチャーモードを全てロックする命令です。
これにより、プレスアンドホールド無効化時にマウスダウンが正しく発生してくれるようになります。
きっと、初期状態ではなにかのジェスチャーと干渉しているのでしょうね。
初期状態のロックについては資料が全然無かったのでちょっとよくわかりません。

タッチデバイス

何年ぶりかわからないですが、タッチデバイスについて調べる機会があったので記しておきたいと思います。
ログまでとってあるので、それなりに役立つはず・・・。

まず使う必要はあるのか?

マウスの代用であればそのままの状態である「レガシーモード」でも一応は事足ります。
が、タッチ操作においては「マウスダウン相当が無い」というのがマウスやペンと大きく異なる挙動の1つです。
これは主に「プレスアンドホールド」機能(押しっぱなしで右クリック)の弊害で、指を離すまでは左クリックか右クリックかを決定出来ないため、指で押してもマウスダウンが発生しません。(勿論回避方法はあるのでそれは後記)

もし、クリックだけで構成されたユーザインターフェイスであれば、そのままでもタッチデバイス対応のアプリであることを理解してください。
もし自前で実装したスクロールバーや特殊なGUI(ゲームライクなGUI)がある場合は、タッチデ操作を意識したプログラムを組む必要があるでしょう。

モード

windowsにおけるタッチデバイスは主に3種類のモードがあります。

  1. LegacyDevice(レガシーデバイス
  2. TouchMDevice(タッチデバイス
  3. GestureDevice(ジェスチャーバイス

レガシーはそのままの状態でOSがタッチ操作をマウス操作に置き換えてくれます。
タッチはタッチ操作の生データを貰う方法で、より反応の良い挙動が可能です。が、後記する制約により単純に反応が早くなるわけではなく、ズームジェスチャーなどにも対応しません。
ジェスチャーはパンやズームなど特殊な操作をOSで解析して使用出来るモードです。タッチといえばコレですが、ほぼタッチアプリ専用です。マウスと混同使用を考えると相当苦戦されるでしょう。

プレスアンドホールド

windowsではジェスチャの1つであるプレスアンドホールドだけが特別な機能として別枠でもうけられています。
ペン(デジタイザ)とタッチ共用設定で、OSの設定関係なくアプリケーション層でOn/Off切り替えが可能です。
設定はグローバルアトムにて行います。

const TABLET_DISABLE_PRESSANDHOLD=$00000001;

TabletAtom := 'MicrosoftTabletPenServiceProperty';
AtomID := GlobalFindAtom(TabletAtom);
if (AtomID=0)then AtomID := GlobalAddAtom(TabletAtom);
flg := GetProp(windowhandle,TabletAtom);
SetProp(windowhandle, TabletAtom,flg or TABLET_DISABLE_PRESSANDHOLD);

このプレスアンドホールドの有無によってタッチ操作時のイベントの内容が変わります。
なので、3モードx2プレスアンドホールド=6 のパターンが存在します。

マウスダウンとクリック

タッチ操作でもうひとつ考慮したい点がこのマウスダウンです。
マウスで左ボタンを押し込んだ時にボタンのグラフィックが変わる挙動を実現するには、このイベントが必要になります。

タッチ操作では基本マウスダウンが指を押したときに発生しません。
プレスアンドホールドの関係で指を離すまで左右のボタン判定が決定しないため、離すタイミングで一気に(クリック)イベントがなだれ込みます。

wait	//指を離すまでイベント無し
mouse(FromTouch).move...0(194,154) 7543ms
mouse(FromTouch).down...0(194,154)Left 11ms
mouse(FromTouch).up...0(194,154)Left 6ms
mouse(FromMouse).move...0(194,154) 7ms

これはペンやマウスとは全く異質なイベントの流れで、もしクリック処理を「GetAsyncKeyState」によってポーリングを行っている場合、マウスダウン間隔が短すぎてクリック処理がすり抜けます。
必ずイベントを見て補間なりしないと、クリックすらできないアプリが出来上がります。

と、ここまでが前置きです。

PhtoShopのレイヤ結合計算式

さて、今回は画像の結合を行う必要があったので、普通のアルファブレンドではなく、Photoshopにおけるレイヤ結合の計算式を使用しました。
普通の合成と違ってMatrixのCompositeと似てるように思えます。
ゲームではほとんど、または全く使用されない類のものだと思います。


基本的には2枚の画像、src(上)、dest(下)を元に計算します。
何枚も重なる場合は、下から上に順に何回も行っていきます。
(たぶん上からでも大丈夫なんじゃないかな。そのほうが手前描画と同じ原理で最適化が利く事が多いかもしれません)
計算式はこんなかんじ。

src...上の画像
dest..下の画像
out...結合結果

da = (1.0 - src.a) * dest.a
out.a = src.a + dest.a

out.rgb = src.rgb * (src.a/out.a) + dest.rgb * (da/out.a)
  ↓
oa=1.0/out.a
out.rgb = src.rgb * (src.a*oa) + dest.rgb * (da*oa)

※例外
src.a==0.0...スキップ
src.a==1.0...dest.argb=src.argb
dest.a==0.0..dest.argb=src.argb

Alpha50%とAlpha50%を合成する場合、上の画像が50%、下の画像が0.5x0.5=25%の配分になるよー、ということ以外はアルファブレンドと似たような計算式です。
ただ50%と25%では100%にならないので、足した75%で割ることで、66.6%と33.3%に比率を調整する必要があり、割り算が発生します。
でも逆数(1.0/n)を一回求めておけば加算と乗算の式に置き換えることが出来るので、最適化する際は行列計算を上手く使えそうな気がします。
srcが100%と0%の時に最適化、destが0%の時に例外があるので、Shaderの時はちょっと考えないといけませんね。
上の式では「通常」合成に当たります。

ゲームでは割り算は御法度なため、alphaの計算にスクリーン*1を用いられることが常な用です。

*1:a1+a2)-(a1*a2