Fusion360用3Dマウスの自作

## 背景
[前回](https://qiita.com/shinsuki/items/33bd633145d316ac1353)に続いて、Fusion360用のデバイスを制作しました。
作成に至った詳細やハードウエアについては、[別のブログ記事](https://shk-maker.hatenablog.jp/entry/2019/10/12/154328)に書いています。
こちらでは、電気回路とコード内容について書きました。

## 電気回路
使用した電気部品は次の通りです。

|アイテム|数量|備考|
|---|:-:|---|
|マイコンProMicro|1個||
|ジョイスティックモジュールA|1個|傾けるタイプ|
|ジョイスティックモジュールB|2個|平行移動タイプ|
|タクトスイッチ|1個|リセット用|

次のブレッドボード図を参考お願いします。

![3Dマウス_パターン2_ブレッドボード.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247233/8ebbb93e-63bd-e995-bfbc-d75301ab2713.jpeg)

可変抵抗(ボリューム)が2個並んでいるのは、ジョイスティックB(平行移動タイプ)1個
の意味です。丁度良い素材が無く、回路的に等価なので、このような表現にさせて
いただきました。  


## コード内容
Arduino IDE 1.8.0 を使用しました。
スケッチ例 => 09.USB => Mouse => JoystickMouseControl を参考にしました。

以下、コードを少し区切って説明します。
順番に全てを繋げれば動くはずですが、必要に応じて適時修正お願いします。

```cpp
#include "Keyboard.h"
#include "Mouse.h"

// set pin numbers for switch, joystick axes, and LED:
const int txAxis = A0;         // tilt joystick X axis
const int tyAxis = A1;         // tilt joystick Y axis
const int s1xAxis = A2;         // shift1 joystick X axis
const int s1yAxis = A3;         // shift1 joystick Y axis
const int s2xAxis = A6;         // shift2 joystick X axis(A6=4)
const int s2yAxis = A7;         // shift2 joystick Y axis(A7=6)
const int fitButton = 7;         // input pin for zoom fit mode

// parameters for reading the joystick:
int range = 30;               // output range of X or Y movement
int responseDelay = 40;        // response delay of the mouse, in ms
int threshold = range / 6;    // resting threshold
int center = range / 2;       // resting position value
int zmSens = 6;            // zoom sensitivity

int modeState = 0;          // effective mode state
int lastModeState = 0;      // previous mode state
int modeTimes = 10;         // reset at stated periods
int modeTime = 0;
int xMoved = 0;             // x moved total
int yMoved = 0;             // y moved total

void setup() {
  pinMode(fitButton, INPUT_PULLUP);
  // take control of the mouse:
  Mouse.begin();
  Keyboard.begin();
}

void loop() {

  // read and scale the two axes:
  int txMove = - readAxis(txAxis);
  int tyMove = - readAxis(tyAxis);
  int sxMove = - (readAxis(s1xAxis) + readAxis(s2xAxis) ) / 2;
  int syMove = (readAxis(s1yAxis) - readAxis(s2yAxis) ) / 2;
  int zmMove = (readAxis(s1yAxis) + readAxis(s2yAxis) ) / 2;
  int fitMode = digitalRead(fitButton);
```
冒頭は、I/Oピン定義、各変数の定義です。
全ての変数の意味は、すみませんが説明省略します。
loopの最初、各ジョイスティックの入力を変数に代入しています。
向きを間違えていたので、マウス座標に合わせるために正負符号付けました。

| 変数 | 目的 | センサー | 操作 |
|---|---|---|---|
|t*Move| 3Dモデル回転 | ジョイスティックA| 傾ける|
|s*Move| 3Dモデル移動 | ジョイスティックB| 2個を同じ方向に動かす (=水平移動)|
|zmMove| 拡大縮小 | ジョイスティックB| 2個を逆方向に動かす (=ひねる)|
|fitMode| 全画面表示 | ジョイスティックA| 押し込む|

```cpp
  // mode select
  if (fitMode == 0){
    modeState = 1;
  }
  else if (txMove != 0 or tyMove != 0){
    if (modeTime > 0){
      modeState = 2;
      modeTime--;
    }
    else{
      modeState = 0;
    }
  }
  else if (sxMove != 0 or syMove != 0 or zmMove != 0){
    if (modeTime > 0){
      modeState = 3;
      modeTime--;
    }
    else{
      modeState = 0;
    }
  }
  else{
    modeState = 0;
  }

  // screen fit mode
  switch (modeState){
    case 1:
      if (lastModeState != 1){
        if (!Mouse.isPressed(MOUSE_MIDDLE)) {
          Keyboard.releaseAll();
          Mouse.release(MOUSE_MIDDLE);
        }
        delay(responseDelay);
        if (fitMode == 0){
          Mouse.press(MOUSE_MIDDLE);      // middle button double click
          delay(100);
          Mouse.release(MOUSE_MIDDLE);
          delay(100);
          Mouse.press(MOUSE_MIDDLE);
          delay(100);
          Mouse.release(MOUSE_MIDDLE);
          delay(500);
          lastModeState = 1;
        }
      }
      break;

    // tilt mode
    case 2:
      if (!Mouse.isPressed(MOUSE_MIDDLE)) {
        Keyboard.press(KEY_LEFT_SHIFT);
        Mouse.press(MOUSE_MIDDLE);
      }
      Mouse.move(txMove, tyMove, 0);
      xMoved += txMove;
      yMoved += tyMove;
      lastModeState = 2;
      break;

    // shift and zoom mode
    case 3:
      if (!Mouse.isPressed(MOUSE_MIDDLE)) {
        Keyboard.releaseAll();
        Mouse.press(MOUSE_MIDDLE);
      }
      Mouse.move(sxMove, syMove, 0);
      if (zmMove > zmSens){
        Mouse.move(0, 0, 1);
      }
      else if (zmMove < -zmSens){
        Mouse.move(0, 0, -1);
      }      
      xMoved += sxMove;
      yMoved += syMove;
      lastModeState = 3;
      break;

    // release and reset
    default:
      if (Mouse.isPressed(MOUSE_MIDDLE)) {
        Keyboard.releaseAll();
        Mouse.release(MOUSE_MIDDLE);
      }
      Mouse.move(-xMoved, -yMoved, 0);
      xMoved = 0;
      yMoved = 0;
      modeTime = modeTimes;
      lastModeState = 0;
      break;
  }
  delay(responseDelay);
}
```
各操作をswich文で切り替えています。

| 場合分け | 目的 | 命令 |
|---|---|---|
|case1| 全画面表示 | 真ん中ダブルクリック |
|case2| 3Dモデル回転 | Shift＋真ん中ボタン＋マウス移動 |
|case3| 3Dモデル移動＆拡大縮小 | 真ん中ボタン＋マウス移動 or スクロール |
|その他| リセット | キー＆マウスボタン離す、マウスカーソル移動 |

3Dモデル「回転」と「移動」は命令が重複しており、同時に出せないので
(少なくとも私には方法が分からない....)、切り替えています。
また、画面のマウスカーソルも動いてしまうので、一定時間毎に戻しています。

```cpp
int readAxis(int thisAxis) {
  // read the analog input:
  int reading = analogRead(thisAxis);

  // map the reading from the analog input range to the output range:
  reading = map(reading, 0, 1023, 0, range);

  // if the output reading is outside from the rest position threshold, use it:
  int distance = reading - center;

//  if (abs(distance) < threshold) {
//    distance = 0;
//  }
  if (distance > 0){
    distance = distance - threshold;
      if (distance < 0){
        distance = 0;
      }
  }
  else {
    distance = distance + threshold;
      if (distance > 0){
        distance = 0;
      }
  }

  // return the distance for this axis:
  return distance;
}
```
ジョイスティックのアナログ入力をデジタルに変換、処理しています。
中央位置の遊び(=threshold)をキャンセルする処理方法は少し変更しました。
元のスケッチ例をコメントアウトしているので参考ください。

## 最後に
最後まで読んでいただき、ありがとうございました。

末尾に参考にさせていただいたブログ記事リンクを記載しました。感謝いたします！
また、自作キーボード(Self-Made Keyboards in Japan)のDiscordにて、カテゴリ違い
気味にも関わらずアドバイスくださった有識者の方々にも、改めて感謝いたします！

[◆Hobby Plus：第62回 USBデバイスが作れるArduino？Arduino Pro Micro(Leonardo)でHID機能を使ってみる](https://deviceplus.jp/hobby/entry_062/)
[◆物を作る者：ジョイスティックとタクトスイッチでマウスを作る](https://novicengineering.com/__trashed/)
[◆K.H.WEBLOG：Arduino Micro を使って、試しに USB スライドパッド マウスを作ってみた（小型USBキーボード自作のための準備色々）](http://kato-h.cocolog-nifty.com/khweblog/2015/10/arduino-micro-u.html)