Source: dev/inputVPad.js

/**
 * VirtualPadControl
 * タッチパットから方向とボタン入力コントロール
 * @class
 * @classdesc 
 * マウスやタッチパッドの入力を仮想ゲームパッドの入力に変換します。<br>\
 * 画面上の仮想パッド領域とボタン領域へのタッチ/クリックを検出し、<br>\
 * 方向(角度と距離)とボタンの押下状態として提供します。
 */
class inputVirtualPad {
    /**
     * @constructor
     * @param {inputMouse} mouse inputMouseインスタンス 
     * @param {inputTouchPad} touchpad inputTouchPadインスタンス
     */
    constructor(mouse, touchpad) {
        //vControllerの入力位置設定

        const ResoX = 640;
        const ResoY = 400;

        let vCntlPos = {};

        vCntlPos.X = 0;
        vCntlPos.Y = 0;

        let Pad_Loc = {};

        Pad_Loc.X = 80;
        Pad_Loc.Y = ResoY - 80;
        Pad_Loc.R = 75;

        let Button_Loc = [];

        for (let i = 0; i <= 1; i++) {

            Button_Loc[i] = {};

            Button_Loc[i].X = ResoX - 200 + 80 * (i + 1);
            Button_Loc[i].Y = ResoY - 80;
            Button_Loc[i].R = 28;
            Button_Loc[i].ON = false;
        }


        for (let i = 0; i <= 1; i++) {

            Button_Loc[i + 2] = {};

            Button_Loc[i + 2].X = ResoX - 80;
            Button_Loc[i + 2].Y = (ResoY - 120) + 80 * i;
            Button_Loc[i + 2].R = 28;
            Button_Loc[i + 2].ON = false;
        }

        let pos = [];

        let now_vdeg = 0;
        let now_vbutton = [];
        let now_vdistance = -1;

        let viewf = false;

        /**
         * @typedef {object} vPadState 仮想ゲームパッド状態
         * @property {number} deg 仮想パッド方向
         * @property {boolean[]} button 仮想ボタン押下状態
         * @property {number} distance 仮想パッド距離
         */
        /**
         * @method
         * @returns {vPadState} 仮想ゲームパッド状態
         * @example
         * //input mouse_state, touchpad_state
         * //return deg = 0 -359 ,button[0-n] = false or true;
         * //       distance
         * @description
         * マウスとタッチパッドの最新の入力状態を処理し、仮想パッドの入力を更新します。<br>\
         * 仮想パッドの中心からの角度、距離、そして仮想ボタンの押下状態を計算し<br>\
         * その結果を返します。
         */
        this.check = function () {
            let ts = touchpad.check_last();
            let ms = mouse.check_last();

            pos = [];
            if (ts.pos.length <= 0) {
                if (ms.button != -1) {
                    pos.push({ x: ms.x, y: ms.y });
                    viewf = true;
                }
                else
                    viewf = false;
            } else {
                pos = ts.pos;
                viewf = true;
            }

            now_vdeg = 0;
            now_vbutton = [];

            let bn = Button_Loc.length - 1;

            for (let j = 0; j <= bn; j++) now_vbutton[j] = false;

            now_vdistance = -1;

            let tr = 0; // deg;
            let dst = -1;

            if (pos.length > 0) {
                for (let i = 0; i < pos.length; i++) {
                    let wdst = dist(pos[i].x, pos[i].y, Pad_Loc.X, Pad_Loc.Y);

                    if (Pad_Loc.R > wdst) {
                        //パッドに複数点入力の場合は最後のものを優先
                        tr = Math.floor(target_r(Pad_Loc.X, Pad_Loc.Y, pos[i].x, pos[i].y));
                        dst = wdst;
                    }

                    for (let j = 0; j <= bn; j++) {
                        if (Button_Loc[j].R > dist(Button_Loc[j].X, Button_Loc[j].Y, pos[i].x, pos[i].y)) {
                            now_vbutton[j] = true;
                        } else {
                            // now_vbutton[j] = false;
                        }
                    }
                }
            }

            now_vdeg = tr;
            now_vdistance = dst;

            let state = {};

            state.button = now_vbutton;
            state.deg = tr; // deg;
            state.distance = dst; //dstns;

            return state;
        };

        /**
         * @method
         * @returns {vPadState} 仮想ゲームパッド状態
         * @description
         * 最後に計算された仮想パッドの入力状態を、値をリセットせずに返します。<br>\
         * このメソッドは、前フレームの状態を参照したい場合や、<br>\
         * 値のリセットが不要な場合に利用されます。
         */
        this.check_last = function () {

            let state = {};

            state.button = now_vbutton;
            state.deg = now_vdeg; // deg;
            state.distance = now_vdistance; //dstns;

            return state;
        };

        /**
         * @method
         * @param {DisplayControl} context 描画先 
         * @description
         * 画面上に仮想ゲームパッドのグラフィックを描画します。<br>\
         * 方向パッドとボタンの形状、そして現在の入力状態を示すインジケータが表示され、<br>\
         * タッチやマウス操作に視覚的なフィードバックを提供します。
        */
        this.draw = function (context) {

            if (!viewf) return;

            let st = this.check_last();

            let bn = Button_Loc.length - 1;

            let cl = {};
            cl.x = Pad_Loc.X;
            cl.y = Pad_Loc.Y;
            cl.r = Pad_Loc.R;
            cl.bt = Button_Loc;
            cl.draw = function (device) {
                let context = device;

                context.beginPath();
                context.arc(this.x, this.y, this.r, 0, 2 * Math.PI, true);
                context.fillStyle = "gray"; //"black";
                context.globalAlpha = 0.5;
                context.fill();
                for (let i = 0; i <= this.bt.length - 1; i++) {

                    context.beginPath();
                    context.arc(this.bt[i].X, this.bt[i].Y, this.bt[i].R, 0, 2 * Math.PI, true);
                    context.fillStyle = "gray"; //"black";

                    context.fill();
                }

            };
            context.putFunc(cl);

            let s = "p " + pos.length + "/";

            if (st.distance > 0) {

                s = s + "d " + st.deg + ":" + st.distance;

                let cl = {};
                cl.x = Pad_Loc.X;
                cl.y = Pad_Loc.Y;
                cl.vx = Math.cos(ToRadian(st.deg - 90)) * Pad_Loc.R; //st.distance;
                cl.vy = Math.sin(ToRadian(st.deg - 90)) * Pad_Loc.R; //st.distance;

                cl.sr = ((st.deg + 225) % 360 / 180) * Math.PI;
                cl.draw = function (device) {
                    let context = device;
                    context.beginPath();
                    context.strokeStyle = "orange";
                    context.lineWidth = 60;
                    context.arc(this.x, this.y, 40, this.sr, this.sr + (1 / 2) * Math.PI, false);
                    context.stroke();
                };
                context.putFunc(cl);
            }

            for (let j = 0; j <= bn; j++) {
                if (st.button[j]) {
                    s = s + "b" + j + " ";

                    let cl = {};
                    cl.x = Pad_Loc.X;
                    cl.y = Pad_Loc.Y;
                    cl.r = Pad_Loc.R;
                    cl.bt = Button_Loc[j];
                    cl.draw = function (device) {
                        let context = device;

                        context.beginPath();
                        //context.arc(Button_Loc[j].X, Button_Loc[j].Y, Button_Loc[j].R - 5, 0, 2 * Math.PI, true);
                        context.arc(this.bt.X, this.bt.Y, this.bt.R - 5, 0, 2 * Math.PI, true);
                        context.fillStyle = "orange";
                        context.fill();
                    };
                    context.putFunc(cl);
                }
            }
        };

        //自分( x,y )から目標( tx, ty )の
        //	方向角度を調べる(上が角度0の0-359)
        function target_r(x, y, tx, ty) {
            let r;

            let wx = tx - x;
            let wy = ty - y;

            if (wx == 0) {
                if (wy >= 0) r = 180; else r = 0;
            } else {
                r = ToDegree(Math.atan(wy / wx));

                if ((wx >= 0) && (wy >= 0)) r = 90 + r;
                if ((wx >= 0) && (wy < 0)) r = 90 + r;
                if ((wx < 0) && (wy < 0)) r = 270 + r;
                if ((wx < 0) && (wy >= 0)) r = 270 + r;
            }

            return r;
        }

        //角度からラジアンに変換
        //
        function ToRadian(d){
            return (d * (Math.PI / 180.0));
        };

        //ラジアンから角度に変換
        //
        function ToDegree(r) {
            return (r * (180.0 / Math.PI));
        }

        //2点間の距離
        function dist(x, y, tx, ty) {

            return Math.floor(Math.sqrt(Math.pow(Math.abs(x - tx), 2) + Math.pow(Math.abs(y - ty), 2)));
        }
    }
}