ä»®æ³çŸå®ã¯ãŠãŒã¶ãŒã®éã§æ¥éã«äººæ°ãåããŠããŸãããããã§ãå€ãã®éçºè ãã¢ã¯ã»ã¹ã§ããŸããã ãã®çç±ã¯ç°¡åã§ããå€ãã®äººãCardboard SDKã«çµã³ä»ããããªããã¬ãŒã ã¯ãŒã¯ã§ã²ãŒã ãæžããŠãããå¥ã®ãã¬ãŒã ã¯ãŒã¯ã§äœæ¥ããããšãåŠã¶ããšã¯äžå¯èœã§ããããããŸãã«ãæ zyã§ãã ãã®ãããLibgdxã§ã¯ãããªããºããè¶ããããšããŠããVRã²ãŒã ãã¢ããªã±ãŒã·ã§ã³ãäœæããæ¹æ³ã¯ãŸã ãããŸããã æ°ãæåãç§ã¯èªåã®VRããã¡ããäœæããããšããè¡åã«é§ãããŸãããç§ã¯Libgdxã«ç²ŸéããŠãããé·ãéä»äºãããŠããã®ã§ãèªåã§ãã¹ãŠãåŠã³ãLibgdxã§èªåã®VR
å 責äºé
Libgdxã¯ã¯ãã¹ãã©ãããã©ãŒã ãã¬ãŒã ã¯ãŒã¯ãšããŠäœçœ®ä»ããããŠãããšããäºå®ã«ããããããããã®èšäºã§ã¯Androidå°çšã«èšèšãããã¢ããªã±ãŒã·ã§ã³ã®äŸãæäŸããŸãã ãã©ãããã©ãŒã åºæã®ã³ãŒãã«åãæ¿ããçç±ã¯2ã€ãããŸãã
1ïŒ Libgdxã®æšæºGdx.inputã§ã¯ãã¹ããŒããã©ã³ã®ç£åèšïŒã³ã³ãã¹ïŒãããçã®ãããŒã¿ãååŸã§ããŸããã ãžã£ã€ãã¹ã³ãŒããšå é床èšãšã®é¡æšã«ãã3ã€ã®æ¹æ³ãè¿œå ããããšã®åé¡ã¯ãç§ã«ã¯ããããŸããããããããAndroidã¢ãžã¥ãŒã«ã®ã»ã³ãµãŒã䜿çšãããã¹ãŠã®äœæ¥ã®çµè«ã®çç±ã§ããã
2ïŒ Wiki㯠ãLibgdxãiOSãžã£ã€ãã¹ã³ãŒãããµããŒãããŠããªããšèšã£ãŠããŸããããã¯ããã®æ å ±ãçŸæç¹ã§ã¯é¢é£ããŠããªãéãã§ãã
ã»ã³ãµãŒ
ãã®ããã3ã€ã®ã»ã³ãµãŒãæèŒããã¹ããŒããã©ã³ãçæ³çã§ãã OpenGLã§ã«ã¡ã©ã®å転ã®ã¯ã©ãŒã¿ããªã³ãååŸããã«ã¯ããã®ããŒã¿ãå€æããã³ãã£ã«ã¿ãŒåŠçããå¿ èŠããããŸãã ã¯ã©ãŒã¿ããªã³ãšã¯äœã§ãããã©ã®ããã«åœ¹ç«ã€ãã«ã€ããŠã¯ã ããã§è©³ãã説æããŠããŸã ã ç§ãã¡ãäœãæ±ã£ãŠããã®ããç解ããããã«ãåã¿ã€ãã®ã»ã³ãµãŒãåå¥ã«ç°¡åã«èŠãããšããå§ããããšããå§ãããŸãã
ãžã£ã€ãã¹ã³ãŒã
ãžã£ã€ãã¹ã³ãŒãã¯ãåãä»ããããŠãã身äœã®åãã®è§åºŠã®å€åã«å¿çã§ããããã€ã¹ã§ãã æ©æ¢°åŒãžã£ã€ãã¹ã³ãŒãã¯å€ãããããç¥ãããŠãããäž»ã«ããŸããŸãªæ £æ§ã·ã¹ãã ã§äœ¿çšãããã³ãŒã¹ãšããã²ãŒã·ã§ã³ãå®å®ãããŸãã
ææ°ã®ã¹ããŒããã©ã³ã¯MEMSãžã£ã€ãã¹ã³ãŒãã䜿çšããŠããããã¯ãã«ã®åœ¢åŒã§3ã€ã®è»žã«æ²¿ã£ãŠè§å転é床ãæäŸããŸã ã
ããŒã¿ã®æž¬å®åäœïŒã©ãžã¢ã³ãŸãã¯åºŠïŒã«é¢ä¿ãªããããã€ã¹ã®è§å転é床ã«çŽæ¥æ¯äŸããããšãéèŠã§ãã æããã«ãçæ³çãªéæ¢ç¶æ ã®ãžã£ã€ãã¹ã³ãŒãã¯ãŒããäžããã¯ãã§ãïŒ ããããããã¯MEMSãžã£ã€ãã¹ã³ãŒãã®å Žåã§ã¯ãããŸããã äžè¬ã«ãMEMSãžã£ã€ãã¹ã³ãŒãã¯ãæ¢åã®ãã¹ãŠã®ãžã£ã€ãã¹ã³ãŒãã®äžã§æãå®äŸ¡ã§äžæ£ç¢ºã§ã;éæ¢ç¶æ ã§ã¯ã匷åãªãŒãããªããããããŸãã ãŒãè¿ãã§ãžã£ã³ããããããã®è§é床ãæ¹äœè§ã«çµ±åãããšã誀差ãèç©ãå§ãããã®çµæããããããžã£ã€ãããªãããçºçããŸããããã¯å€ãã®ãã¡ã³ãVRããã¡ãããã¬ã€ããããšã§ããç¥ãããŠããŸãã ãŒãããªãããæžããã«ã¯ãç¹å¥ãªä¿¡å·ãã£ã«ã¿ãŒãšè§é床ã®ãããå€ã䜿çšãããŸãããããã¯äžèœè¬ã§ã¯ãããŸããã VRãšã¯ã¹ããªãšã³ã¹ïŒç»åãšãžã£ãŒã¯ã®æ £æ§ããããŸãïŒããããŠ2çªç®ã«ãããªãããå®å šã«æ ¹çµ¶ããããšã¯ãŸã äžå¯èœã§ãã ãã®å Žåãã¹ããŒããã©ã³ã®ä»ã®2ã€ã®ã»ã³ãµãŒãå©ããšãªããVRãšã¯ã¹ããªãšã³ã¹ãç¶æããªããããªãããã»ãŒå®å šã«æé€ã§ããŸãã
å é床èš
å é床èšã¯ãåãä»ããããŠãã身äœã®å é床ã«å¿çããããã€ã¹ã§ãã ã¹ããŒããã©ã³ã®å é床èšã¯ã軞ã«æ²¿ã£ãå é床ãã¯ãã«ãæäŸããŸã ã枬å®åäœã¯ã»ãšãã©ã®å Žåm / sã§ãããç§ãã¡ã«ãšã£ãŠãéèŠã§ã¯ãããŸããã éæ¢æã«ã¯ãå é床èšã¯éåãã¯ãã«ã®æ¹åã瀺ããŸãããã®æ©èœã䜿çšããŠãæ°Žå¹³ç·ãå®å®ãããããšãã§ããŸãïŒåŸæè£æ£ïŒã å é床èšã«ãæ¬ ç¹ããããŸãã ãžã£ã€ãã¹ã³ãŒããäž»ã«éæ¢ããŠãããšãã«ãã€ãºãå€ãå Žåãå察ã«ãå é床èšã¯åãããããªããŸãããã®ãããããã2ã€ã®ã»ã³ãµãŒããã®ããŒã¿ã®çµã¿åããã«è³¢æã«ã¢ãããŒãããå¿ èŠããããŸãã ããŸããŸãªANNã§ãã¯ã¯ããã³ãã¿ãŒã«ã«ã«ãã³ãã£ã«ã¿ãŒã䜿çšãããŸãããVRã®å Žåãããã§éåžžã®è£å®ãè¡ãããšãã§ãããšæããŸãã
ãã®çµæããžã£ã€ãã¹ã³ãŒããšå é床èšã®ãã³ãã«ã«ããã²ãŒã ãäœæããããšãã§ããåãCardboard SDKããã®ããã«æ©èœããŸãã ããããç£åèšã䜿çšããŠé€å»ã§ããåçŽè»žã®åšãã«ããªãããæ®ã£ãŠããŸãã Cardboard SDKã§ã¯ãç£åèšã¯ç£æ°ãã¿ã³ã§åäœããããã«æå®ãããŠããããããã¹ãŠã®Cardboardã²ãŒã ã«ã¯åžžã«ããªããã³ãŒã¹ããããŸãã
ç£åèš
ç£åèš-ç£å Žã«å¿çããããã€ã¹ã éæ¢æãé»ç£ããã³ç£æ°å¹²æžããªãå Žåãã¹ããŒããã©ã³ã®ç£åèšã¯å°çã®ç£æ°èªå°ãã¯ãã«ã®æ¹åã瀺ããŸã ãå€ã¯éåžžãã€ã¯ããã¹ã©ïŒÎŒTïŒã§ãã
ææã®ç£å Žãšãã圢ã§ã®ãã®ç®ã«èŠããªãæ¯æã«ãããåçŽè»žã®åšãã®ä»»æã®å転ãæé€ããããšãã§ããããã«ããããªããå šäœãå®å šã«æé€ã§ããŸãã ç£æ°ããªããè£æ£ãåžžã«æ©èœããããã§ã¯ãªããã©ãã§ãåžæã©ããã«åäœããããã§ã¯ãªãããšã«æ³šæããŠãã ããã ãŸããã¹ããŒããã©ã³ã®ã«ããŒãŸãã¯VRãã«ã¡ããã«ããŒã®ç£ç³ããã®ããããªå€éšç£å Žã¯ãäºæž¬ã§ããªãçµæã«ã€ãªãããŸãã 第äºã«ãç£å Žã¯ææã®ããŸããŸãªã³ãŒããŒã§ç°ãªããç£æ°èªå°ãã¯ãã«ã®æ¹åãç°ãªããŸãã ããã¯ãç£åç·ãå°çã®è¡šé¢ã«ã»ãŒåçŽã§ãããåºç¹ã®æ¹åã«é¢ããæçšãªæ å ±ãéã°ãªããããç£åèšã«ããããªããè£æ£ã極ã®è¿ãã§æ©èœããªãããšãæå³ããŸãã ç§ãã¡ã®éã«æ¥µå°æ¢æ€å®¶ãããªãããšãé¡ã£ãŠããŸã
çè«
é»è©±ã®çŸåšã®åãã®ã¯ã©ãŒã¿ããªã³ãååŸããã«ã¯ããã¹ãŠã®ã»ã³ãµãŒããæ å ±ãåšæçã«ååŸããããã«åºã¥ããŠåã®ç¬éã«ååŸããã¯ã©ãŒã¿ããªã³ã«å¯ŸããŠæäœãå®è¡ããå¿ èŠããããŸãã ããã -ãµã€ã¯ã«ãéå§ããåã«ãå¿ èŠãªæ¹åã®ã¯ã©ãŒã¿ããªã³ã«åæå€ãå²ãåœãŠãŸã ã
1.ãžã£ã€ã枬å®å€ãçµ±åãã
å ã»ã©èšã£ãããã«ããžã£ã€ãã¹ã³ãŒãã¯è§é床ã®ãã¯ãã«ãæäŸããŸãã è§é床ããè§åº§æšãååŸããã«ã¯ãããããçµ±åããå¿ èŠããããŸãã ããã¯æ¬¡ã®ããã«è¡ãããŸãã
1.1ã ã¯ã©ãŒã¿ããªã³ã宣èšãã ãããŠæ¬¡ã®ããã«èšå®ããŸãïŒ
ã©ã㧠-ãµã€ã¯ã«ã®ååã®å埩ããçµéããæéã
1.2ã çµæã䜿çšããŠqãæŽæ° ïŒ ã
説æããã¢ã¯ã·ã§ã³ã®çµæãã¯ã©ãŒã¿ããªã³qã¯æ¢ã«å転ã«äœ¿çšã§ããŸãããã¹ããŒããã©ã³ãžã£ã€ãã®ç²ŸåºŠãéåžžã«äœãããã3軞ãã¹ãŠã«æ²¿ã£ãŠã²ã©ãæµ®ããŠããŸãã
2.æ°Žå¹³é¢ã®äœçœ®åããïŒåŸãè£æ£ïŒ
å é床èšã¯ããã«åœ¹ç«ã¡ãŸãã èŠããã«ããã®ããã«ã¯ãä¿®æ£ã¯ã©ãŒã¿ããªã³ãèŠã€ããåã®ã¹ãããã§ååŸããã¯ã©ãŒã¿ããªã³ãæããå¿ èŠããããŸãã ä¿®æ£åå æ°ã¯ããã¯ãã«å転軞ãšå転è§åºŠã䜿çšããŠåœ¢æãããŸãã
2.1ã å é床èšãã¯ãã«ãåå æ°ãšããŠååŸããŸãã
2.2ã ãã®å é床èšã®åå æ°ããžã£ã€ãåå æ°ã§å転ãããŸãã
2.3ã ã¯ã©ãŒã¿ããªã³ã®æ£èŠåããããã¯ãã«éšåãååŸããŸã ïŒ
2.4ã ããã䜿çšããŠãå転軞ãå®çŸ©ãããã¯ãã«ãèŠã€ããŸãã
2.5ã ããã§ãè§åºŠãèŠã€ããããšãã§ããŸãã
2.6ã ãžã£ã€ãããåå æ°ã調æŽããŸãã ã©ã㧠-å¹³æ»åä¿æ°ããããå°ãã-æ°Žå¹³ç·ãããæ»ããã§é·ãå®å®ãããããã»ãšãã©ã®å Žåã®æé©å€ã¯0.1ã§ãã
ããã§ã qã¯ã«ã¡ã©ãäžäžéããŸã«ãããY軞ã®åšãã®ããããªããªããã®ã¿ãå¯èœã«ãªããŸãã
3.ç£åèšã§Y軞åšãã®ããªãããé€å»ããŸãïŒãšãŒè£æ£ïŒã
ã¹ããŒããã©ã³ã®ã³ã³ãã¹ã¯ããªãæ°ãŸãããªãã®ã§ãããåèµ·åãããã³ã«ãã£ãªãã¬ãŒã·ã§ã³ãè¡ãã巚倧ãªã°ã©ã³ããŸãã¯ãã°ãããã«æã¡èŸŒãå¿ èŠããããŸãã VRã®å Žåããã£ãªãã¬ãŒã·ã§ã³ã倱ããããšããããã®å転ã«å¯Ÿããã«ã¡ã©ã®äºæž¬ã§ããªãå¿çãçºçããŸãã 99ïŒ ã®å Žåãå¹³åçãªãŠãŒã¶ãŒã®ã³ã³ãã¹ã¯ãã£ãªãã¬ãŒã·ã§ã³ãããŠããªããããããªããè£æ£æ©èœãããã©ã«ãã§ãªãã«ããŠããããšã匷ããå§ãããŸãããªãã«ããªããšãåŠå®çãªã¬ãã¥ãŒãæŸãããšãã§ããŸãã ãŸããä¿®æ£ãæå¹ã«ããŠã¢ããªã±ãŒã·ã§ã³ãèµ·åãããã³ã«ããã£ãªãã¬ãŒã·ã§ã³ã®å¿ èŠæ§ã«é¢ããèŠåã衚瀺ãããšäŸ¿å©ã§ãã Androidããã£ãªãã¬ãŒã·ã§ã³èªäœãåŒãç¶ããŸãããããåŒã³åºãã«ã¯ãã¹ããŒããã©ã³ã空äžã§æ°åã8ããŸãã¯ãâããšæç»ããå¿ èŠããããŸãã
Androidãã³ã³ãã¹ãã£ãªãã¬ãŒã·ã§ã³ã®ã¹ããŒã¿ã¹ã確èªããæ¹æ³ãæäŸãããããã¹ãŠããã ãããæ¯ãããªã©ã®ã¡ãã»ãŒãžã衚瀺ããããšã¯æ®å¿µã§ãããŠãŒã¶ãŒã®ç¥çèœåã«é Œããªããã°ãªããŸããã ååãšããŠãæ··ä¹±ããŠãæ³¢ãå é床èšãšèŠãªãããšãã§ããŸããããã¡ããããã¯è¡ããŸããã å é床èšã䜿çšããå°å¹³ç·è£æ£ãšããã»ã©å€ãããªãã¢ã«ãŽãªãºã ã«ç§»ããŸãããã
3.1ã åå æ°ã®åœ¢ã§ã³ã³ãã¹ãã¯ãã«ãæç»ããŸãã
3.2ã ãããŠã¿ãŒã³ïŒ
3.3ã ãã®å Žåã®å転軞ã¯YïŒ0ã1ã0ïŒãªã®ã§ãè§åºŠã®ã¿ãå¿ èŠã§ãã
3.4ã æ£è§£ïŒ ã©ã㧠-ãšåãå¹³æ»åä¿æ° äž
ç£åèšãé©åã«èŒæ£ããããŠãŒã¶ãŒãå°çã®æ¥µã«å°ççã«è¿ãããªãå Žåãããªããã¯å®å šã«ãªããªããŸãã ç§ã®æ¹æ³ã¯ãOculus Riftã§äœ¿çšãããŠããæ¹æ³ãšã¯å€å°ç°ãªãããšã«æ³šæããŠãã ããã ãšãã»ã³ã¹ã¯æ¬¡ã®ãšããã§ãããµã€ã¯ã«ã®æåŸã®æ°åã®ç¹°ãè¿ãã§ãå転ã®åå æ°ãšå¯Ÿå¿ããç£åèšã®èªã¿åãå€ãä¿åãããŸãïŒããããåºæºç¹ãäœæãããŸãïŒã ããã«èª¿ã¹ãŸããç£åèšã®èªã¿åãå€ãå€ãããªãå Žåãã¯ã©ãŒã¿ããªã³ããé²ããå Žåãããªããè§åºŠãèšç®ãããã¯ã©ãŒã¿ããªã³ã¯å察æ¹åã«åããããŸãã ãã®ã¢ãããŒãã¯Oculusã§ã¯ããŸãæ©èœããŸãããç£åèšã®ç²ŸåºŠãäœããããããã¹ããŒããã©ã³ã«ã¯é©çšã§ããŸããã ç§ã¯èšäºããã¡ãœãããå®è£ ããããšããŸãã-ã¹ããŒããã©ã³ã§ã圌ã¯ã«ã¡ã©ãåŒã£åŒµããåæã«ããªãããå®éã«é€å»ããŸããã
å®è£
ãŸãã gdx-setup.jarã䜿çšããŠç©ºã®Androidãããžã§ã¯ããäœæããŸãã
å žåçãªandroid libgdxãããžã§ã¯ãã¯ãandroidãšcoreã®2ã€ã®ã¢ãžã¥ãŒã«ã«åãããŠããŸãã æåã®ã¢ãžã¥ãŒã«ã«ã¯ãã©ãããã©ãŒã åºæã®ã³ãŒããå«ãŸãã2çªç®ã®ã¢ãžã¥ãŒã«ã«ã¯éåžžãã²ãŒã ããžãã¯ãšã¬ã³ããªã³ã°ãå«ãŸããŸãã ã³ã¢ã¢ãžã¥ãŒã«ãšAndroidã¢ãžã¥ãŒã«éã®çžäºäœçšã¯ãã€ã³ã¿ãŒãã§ã€ã¹ãä»ããŠå®è¡ãããŸããããã«åºã¥ããŠã3ã€ã®ãã¡ã€ã«ãäœæããå¿ èŠããããŸãã
- VRSensorManager-ã¿ãããããŒãžã£ãŒã€ã³ã¿ãŒãã§ã€ã¹
- VRSensorManagerAndroid-ãã®å®è£
- VRCamera-ã¬ã³ããªã³ã°çšã®ã·ã³ãã«ãªã«ã¡ã©
ãããŠã2ã€ã®ãããžã§ã¯ããã¡ã€ã«ã«å€æŽãå ããŸãã
- AndroidLauncher -Androidãããžã§ã¯ãã¹ã¿ãŒã¿ãŒã¯ã©ã¹
- GdxVR-ã¢ããªã±ãŒã·ã§ã³ã®ã¡ã€ã³ã¯ã©ã¹
ãããžã§ã¯ãã®ãœãŒã¹ã³ãŒããgithubãªããžããªã«ã¢ããããŒãããŸãããå¯èœãªéãã³ãŒããææžåããããšããã®ã§ãèšäºå ã®äž»èŠãªãã€ã³ãã®ã¿ã説æããŸãã
VRSensorManager
ã»ã³ãµãŒã§ã®ãã¹ãŠã®äœæ¥ãæšæž¬ããAndroidã¢ãžã¥ãŒã«ã§åå æ°ãèšç®ããŸããããã®ã€ã³ã¿ãŒãã§ã€ã¹ã䜿çšããŠãã³ã¢ã¢ãžã¥ãŒã«ã§åå æ°ãååŸããŸãã
VRSensorManager.java
package com.sinuxvr.sample; import com.badlogic.gdx.math.Quaternion; /** - */ interface VRSensorManager { /** */ boolean isGyroAvailable(); /** */ boolean isMagAvailable(); /** */ void startTracking(); /** */ void endTracking(); /** - * @param use - true - , false - */ void useDriftCorrection(boolean use); /** * @return */ Quaternion getHeadQuaternion(); }
ããã§ã®æ¹æ³ã¯ãã¹ãŠçŽæçã§ã誰ã質åãããŠããªããšæããŸãã äŸã®ã¡ãœããisGyroAvailableãšisMagAvailableã¯ã©ãã«ãé¢äžããŠããŸãããã誰ãã«åœ¹ç«ã€å¯èœæ§ããããããã²ãŒã ã§äœ¿çšããŠããŸãã
VRSensorManagerAndroid
çè«çã«ã¯ãAndroidã¢ãžã¥ãŒã«ã§ã¯ãã»ã³ãµãŒããã®ã¿å€ãååŸã§ããæ¢ã«ã³ã¢ã«ãããããããåå æ°ãèšç®ã§ããŸãã ã³ãŒããä»ã®ãã¬ãŒã ã¯ãŒã¯ã«ç§»æ€ããããããããã«ããã¹ãŠã1ãæã«ãŸãšããããšã«ããŸããã
VRSensorManagerAndroid.java
package com.sinuxvr.sample; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; /** Android. * . * : , + , + , * + + */ class VRSensorManagerAndroid implements VRSensorManager { /** */ private enum VRControlMode { ACC_ONLY, ACC_GYRO, ACC_MAG, ACC_GYRO_MAG } private SensorManager sensorManager; // private SensorEventListener accelerometerListener; // private SensorEventListener gyroscopeListener; // private SensorEventListener compassListener; // private Context context; // /** */ private final float[] accelerometerValues = new float[3]; // private final float[] gyroscopeValues = new float[3]; // private final float[] magneticFieldValues = new float[3]; // private final boolean gyroAvailable; // private final boolean magAvailable; // private volatile boolean useDC; // /** , headOrientation */ private final Quaternion gyroQuaternion; private final Quaternion deltaQuaternion; private final Vector3 accInVector; private final Vector3 accInVectorTilt; private final Vector3 magInVector; private final Quaternion headQuaternion; private VRControlMode vrControlMode; /** */ VRSensorManagerAndroid(Context context) { this.context = context; // sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); // ( 100%, ) magAvailable = (sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null); gyroAvailable = (sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null); useDC = false; // vrControlMode = VRControlMode.ACC_ONLY; if (gyroAvailable) vrControlMode = VRControlMode.ACC_GYRO; if (magAvailable) vrControlMode = VRControlMode.ACC_MAG; if (gyroAvailable && magAvailable) vrControlMode = VRControlMode.ACC_GYRO_MAG; // gyroQuaternion = new Quaternion(0, 0, 0, 1); deltaQuaternion = new Quaternion(0, 0, 0, 1); accInVector = new Vector3(0, 10, 0); accInVectorTilt = new Vector3(0, 0, 0); magInVector = new Vector3(1, 0, 0); headQuaternion = new Quaternion(0, 0, 0, 1); // startTracking(); } /** */ @Override public boolean isGyroAvailable() { return gyroAvailable; } /** */ @Override public boolean isMagAvailable() { return magAvailable; } /** - */ @Override public void startTracking() { // sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); Sensor accelerometer = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0); accelerometerListener = new SensorListener(this.accelerometerValues, this.magneticFieldValues, this.gyroscopeValues); sensorManager.registerListener(accelerometerListener, accelerometer, SensorManager.SENSOR_DELAY_GAME); // if (magAvailable) { sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); Sensor compass = sensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).get(0); compassListener = new SensorListener(this.accelerometerValues, this.magneticFieldValues, this.gyroscopeValues); sensorManager.registerListener(compassListener, compass, SensorManager.SENSOR_DELAY_GAME); } // if (gyroAvailable) { sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); Sensor gyroscope = sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE).get(0); gyroscopeListener = new SensorListener(this.gyroscopeValues, this.magneticFieldValues, this.gyroscopeValues); sensorManager.registerListener(gyroscopeListener, gyroscope, SensorManager.SENSOR_DELAY_GAME); } } /** - */ @Override public void endTracking() { if (sensorManager != null) { if (accelerometerListener != null) { sensorManager.unregisterListener(accelerometerListener); accelerometerListener = null; } if (gyroscopeListener != null) { sensorManager.unregisterListener(gyroscopeListener); gyroscopeListener = null; } if (compassListener != null) { sensorManager.unregisterListener(compassListener); compassListener = null; } sensorManager = null; } } /** - */ @Override public void useDriftCorrection(boolean useDC) { // , this.useDC = useDC; } /** */ @Override public synchronized Quaternion getHeadQuaternion() { // switch (vrControlMode) { // case ACC_ONLY: updateAccData(0.1f); // Yaw ( ) headQuaternion.setFromAxisRad(0, 1, 0, -MathUtils.sin(accelerometerValues[0] / 200f)).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); break; // + ( , // , ) case ACC_MAG: updateAccData(0.2f); if (!useDC) { headQuaternion.setFromAxisRad(0, 1, 0, -MathUtils.sin(accelerometerValues[0] / 200f)).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); } else updateMagData(1f, 0.05f); break; // + case ACC_GYRO: updateGyroData(0.1f); updateAccData(0.02f); break; // - must have, case ACC_GYRO_MAG: float dQLen = updateGyroData(0.1f); updateAccData(0.02f); if (useDC) updateMagData(dQLen, 0.005f); } return headQuaternion; } /** * * @param driftThreshold - * @return - deltaQuaternion */ private synchronized float updateGyroData(float driftThreshold) { float wX = gyroscopeValues[0]; float wY = gyroscopeValues[1]; float wZ = gyroscopeValues[2]; // float l = Vector3.len(wX, wY, wZ); float dtl2 = Gdx.graphics.getDeltaTime() * l * 0.5f; if (l > driftThreshold) { float sinVal = MathUtils.sin(dtl2) / l; deltaQuaternion.set(sinVal * wX, sinVal * wY, sinVal * wZ, MathUtils.cos(dtl2)); } else deltaQuaternion.set(0, 0, 0, 1); gyroQuaternion.mul(deltaQuaternion); return l; } /** Tilt * @param filterAlpha - */ private synchronized void updateAccData(float filterAlpha) { // accInVector.set(accelerometerValues[0], accelerometerValues[1], accelerometerValues[2]); gyroQuaternion.transform(accInVector); accInVector.nor(); // accInVector UP(0, 1, 0) float xzLen = 1f / Vector2.len(accInVector.x, accInVector.z); accInVectorTilt.set(-accInVector.z * xzLen, 0, accInVector.x * xzLen); // accInVector UP(0, 1, 0) float fi = (float)Math.acos(accInVector.y); // Tilt- headQuaternion.setFromAxisRad(accInVectorTilt, filterAlpha * fi).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); } /** Yaw * @param dQLen - deltaQuaternion * @param filterAlpha - * */ private synchronized void updateMagData(float dQLen, float filterAlpha) { // deltaQuaternion if (dQLen < 0.1f) return; // magInVector.set(magneticFieldValues[0], magneticFieldValues[1], magneticFieldValues[2]); gyroQuaternion.transform(magInVector); // Yaw float theta = MathUtils.atan2(magInVector.z, magInVector.x); // headQuaternion.setFromAxisRad(0, 1, 0, filterAlpha * theta).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); } /** ( AndroidInput) */ private class SensorListener implements SensorEventListener { final float[] accelerometerValues; final float[] magneticFieldValues; final float[] gyroscopeValues; SensorListener (float[] accelerometerValues, float[] magneticFieldValues, float[] gyroscopeValues) { this.accelerometerValues = accelerometerValues; this.magneticFieldValues = magneticFieldValues; this.gyroscopeValues = gyroscopeValues; } // ( ) @Override public void onAccuracyChanged (Sensor arg0, int arg1) { } // @Override public synchronized void onSensorChanged (SensorEvent event) { if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { accelerometerValues[0] = -event.values[1]; accelerometerValues[1] = event.values[0]; accelerometerValues[2] = event.values[2]; } if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { magneticFieldValues[0] = -event.values[1]; magneticFieldValues[1] = event.values[0]; magneticFieldValues[2] = event.values[2]; } if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { gyroscopeValues[0] = -event.values[1]; gyroscopeValues[1] = event.values[0]; gyroscopeValues[2] = event.values[2]; } } } }
ããã§ããããããããã€ãã®èª¬æãããŸãã éåžžã®ãªã¹ããŒã䜿çšããŠã»ã³ãµãŒããŒã¿ãååŸããŸãããã®ããŒãã«é¢ããå€ãã®ããã¥ã¢ã«ãã€ã³ã¿ãŒãããäžã«ãããŸãã ã¯ã©ãŒã¿ããªã³ã䜿çšããäœæ¥ããçè«éšåã«åŸã£ãŠ3ã€ã®æ¹æ³ã«åå²ããŸããã
- updateGyroData-ãžã£ã€ãã¹ã³ãŒãã®è§é床ã®çµ±å
- updateAccData-å é床èšã«ããå°å¹³ç·ã®å®å®å
- updateMagData-ã³ã³ãã¹ã®ããªããè£æ£
é»è©±æ©ã«åžžã«å é床èšããããšä»®å®ããå Žåãã»ã³ãµãŒã®çµã¿åããã¯4ã€ãããããŸããããããã¯ãã¹ãŠVRControlModeåæã§å®çŸ©ãããŠããŸãã
private enum VRControlMode { ACC_ONLY, ACC_GYRO, ACC_MAG, ACC_GYRO_MAG }
ããã€ã¹ã»ã³ãµãŒã®çµã¿åããã¯ã³ã³ã¹ãã©ã¯ã¿ãŒã§æ±ºå®ãããgetHeadQuaternionã¡ãœãããåŒã³åºããããšãããã«å¿ããŠäœããã®æ¹æ³ã§åå æ°ã圢æãããŸãã ãã®ã¢ãããŒãã®å©ç¹ã¯ãå©çšå¯èœãªã»ã³ãµãŒã«å¿ããŠupdateGyroData / updateAccData / updateMagDataã¡ãœããã®åŒã³åºããçµã¿åãããŠãé»è©±ã«å é床èšãããªãå Žåã§ãã¢ããªã±ãŒã·ã§ã³ãåäœããããã«ããããšã§ãã ããã«è¯ãããšã«ãå é床èšã«å ããŠé»è©±ã«ã³ã³ãã¹ãããå Žåããã®ãã³ãã«ã¯ã»ãŒãžã£ã€ãã¹ã³ãŒãã®ããã«åäœããé ã360°å転ãããããšãã§ããŸãã ãã®å Žåãéåžžã®VRäœéšã«çåã®äœå°ã¯ãããŸãããããã䜿ãã®æºåž¯é»è©±ã«ã¯ãžã£ã€ãã¹ã³ãŒãããããŸããããšãããœãŠã«ã¬ã¹ãªç¢æãããåªããŠããŸããïŒ useDriftCorrectionã¡ãœãããèå³æ·±ãã§ãããªã¹ããŒã«åœ±é¿ãäžããã«ãç£åèšã®äœ¿çšããªã³ã¶ãã©ã€ã§ãªã³/ãªãã§ããŸãïŒæè¡çã«ã¯ãupdateMagDataã®åŒã³åºããåæ¢ããŸãïŒã
VRã«ã¡ã©
ã¹ãã¬ãªãã¢ã®åœ¢åŒã§ç»åã衚瀺ããã«ã¯ãèŠå·®ããŒã¹ãšåŒã°ãã2ã€ã®ã«ã¡ã©ãå¿ èŠã§ãã ãããã£ãŠãVRCameraã«ã¯PerspectiveCameraã®2ã€ã®ã€ã³ã¹ã¿ã³ã¹ãå«ãŸããŸãã äžè¬ã«ããã®ã¯ã©ã¹ã§ã¯ãã«ã¡ã©ã§ã®äœæ¥ã®ã¿ãå®è¡ãããŸãïŒã¯ã©ãŒã¿ããªã³ã§å転ããŠç§»åããŸãïŒãGdxVRã®ã¡ã€ã³ã¯ã©ã¹ã«ã¹ãã¬ãªãã¢ã®ã¬ã³ããªã³ã°ãçŽæ¥é 眮ããŸããã
VRCamera.java
package com.sinuxvr.sample; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.PerspectiveCamera; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; /** VR * VRSensorManager update() */ class VRCamera { private PerspectiveCamera leftCam; // private PerspectiveCamera rightCam; // private Vector3 position; // VR private float parallax; // private Vector3 direction; // VR private Vector3 up; // UP VR private Vector3 upDirCross; // up direction ( 2, ) /** */ VRCamera(float fov, float parallax, float near, float far) { this.parallax = parallax; leftCam = new PerspectiveCamera(fov, Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight()); leftCam.near = near; leftCam.far = far; leftCam.update(); rightCam = new PerspectiveCamera(fov, Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight()); rightCam.near = near; rightCam.far = far; rightCam.update(); position = new Vector3(0, 0, 0); direction = new Vector3(0, 0, 1); up = new Vector3(0, 1, 0); upDirCross = new Vector3().set(direction).crs(up).nor(); } /** */ void update() { Quaternion headQuaternion = GdxVR.vrSensorManager.getHeadQuaternion(); // - // direction.set(0, 0, 1); headQuaternion.transform(direction); up.set(0, 1, 0); headQuaternion.transform(up); upDirCross.set(direction); upDirCross.crs(up).nor(); // float angle = 2 * (float)Math.acos(headQuaternion.w); float s = 1f / (float)Math.sqrt(1 - headQuaternion.w * headQuaternion.w); float vx = headQuaternion.x * s; float vy = headQuaternion.y * s; float vz = headQuaternion.z * s; // leftCam.view.idt(); // leftCam.view.translate(parallax, 0, 0); // + parallax X leftCam.view.rotateRad(vx, vy, vz, -angle); // leftCam.view.translate(-position.x, -position.y, -position.z); // position leftCam.combined.set(leftCam.projection); Matrix4.mul(leftCam.combined.val, leftCam.view.val); // rightCam.view.idt(); // rightCam.view.translate(-parallax, 0, 0); // + parallax X rightCam.view.rotateRad(vx, vy, vz, -angle); // rightCam.view.translate(-position.x, -position.y, -position.z); // position rightCam.combined.set(rightCam.projection); Matrix4.mul(rightCam.combined.val, rightCam.view.val); } /** */ void setPosition(float x, float y, float z) { position.set(x, y, z); } /** */ PerspectiveCamera getLeftCam() { return leftCam; } /** */ PerspectiveCamera getRightCam() { return rightCam; } /** , UP , */ public Vector3 getPosition() { return position; } public Vector3 getDirection() { return direction; } public Vector3 getUp() { return up; } public Vector3 getUpDirCross() { return upDirCross; } }
ããã§æãèå³æ·±ãã¡ãœããã¯ãã³ã³ã¹ãã©ã¯ã¿ãšæŽæ°ã§ãã ã³ã³ã¹ãã©ã¯ã¿ãŒã¯ãèŠéè§ïŒfovïŒãã«ã¡ã©éã®è·é¢ïŒèŠå·®ïŒãããã³è¿ããšé ãã®ã¯ãªããã³ã°ãã¬ãŒã³ïŒnearãfarïŒãŸã§ã®è·é¢ãåããŸãã
VRCamera(float fov, float parallax, float near, float far)
æŽæ°ã¡ãœããã§ã¯ãVRSensorManagerããã¯ã©ãŒã¿ããªã³ãååŸããã«ã¡ã©ãïŒÂ±èŠå·®ã0ã0ïŒã«ç§»åããå転ãããŠãããå ã®äœçœ®ã«æ»ããŸãã ãã®ã¢ãããŒãã§ã¯ãã«ã¡ã©éã«åžžã«ç¹å®ã®èŠå·®ããŒã¹ãååšãããŠãŒã¶ãŒã¯é ã®åãã«å¿ããŠç«äœç»åãèŠãããšãã§ããŸãã ã«ã¡ã©ã®ãã¥ãŒãããªãã¯ã¹ãçŽæ¥æäœããããšã«æ³šæããŠãã ãããã€ãŸããã«ã¡ã©ã®æ¹åãšã¢ãããã¯ãã«ã¯æŽæ°ãããŸããã ãããã£ãŠãVRCameraã¯2ã€ã®ãã¯ãã«ãå°å ¥ãããããã®å€ã¯ã¯ã©ãŒã¿ããªã³ã䜿çšããŠèšç®ãããŸãã
AndroidLauncher
ã¹ã¿ãŒã¿ãŒã¯ã©ã¹ã§ã¯ãã¢ããªã±ãŒã·ã§ã³ãåæåãããšãã«ãVRSensorManagerAndroidã®ã€ã³ã¹ã¿ã³ã¹ãäœæããã²ãŒã ã®ã¡ã€ã³ã¯ã©ã¹ïŒç§ã®å Žåã¯GdxVRïŒã«æž¡ãå¿ èŠããããŸãã
@Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); config.useWakelock = true; config.useAccelerometer = false; config.useGyroscope = false; config.useCompass = false; vrSensorManagerAndroid = new VRSensorManagerAndroid(this.getContext()); initialize(new GdxVR(vrSensorManagerAndroid), config); }
ãŸããã¢ããªã±ãŒã·ã§ã³ãé衚瀺/ãããã€ãããšãã«ãªã¹ããŒãç¡å¹å/ç»é²ããããšãå¿ããªãã§ãã ããã
@Override public void onPause() { vrSensorManagerAndroid.endTracking(); super.onPause(); } @Override public void onResume() { super.onResume(); vrSensorManagerAndroid.startTracking(); }
å®å šãªã¹ã¿ãŒã¿ãŒã¯ã©ã¹ã³ãŒãïŒ
AndroidLauncher.java
package com.sinuxvr.sample; import android.os.Bundle; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; public class AndroidLauncher extends AndroidApplication { private VRSensorManagerAndroid vrSensorManagerAndroid; // /** */ @Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); // libgdx config.useWakelock = true; config.useAccelerometer = false; config.useGyroscope = false; config.useCompass = false; config.numSamples = 2; // ( useAccelerometer .. ) vrSensorManagerAndroid = new VRSensorManagerAndroid(this.getContext()); initialize(new GdxVR(vrSensorManagerAndroid), config); } /** - */ @Override public void onPause() { vrSensorManagerAndroid.endTracking(); super.onPause(); } /** - */ @Override public void onResume() { super.onResume(); vrSensorManagerAndroid.startTracking(); } }
room.g3dbã¢ãã«ãã¡ã€ã«ãštexture.pngãã¯ã¹ãã£ãã¢ã»ãããã©ã«ãã«ããããããããšãå¿ããªãã§ãã ããããããã¯æ¬¡ã®ã¹ãããã§åœ¹ç«ã¡ãŸãã ããããããŠã³ããŒãã§ããŸãã ä»ã®ã©ã®ã·ãŒã³ã®ã¢ãã«ãé©ããŠããŸããç§ã¯ããŸãæ°ã«ãããå®æããã¢ãã«ãèªåã®ã²ãŒã ã®ã¬ãã«ããåããŸããã3Då¹æã¯ãå€ãã®å°ããªãã£ããŒã«ãååšããããã«ããæããããŸãã
Gdxvr
æåŸã«ãã¡ã€ã³ã¯ã©ã¹ã«é²ã¿ãŸããã ãŸããVRSensorManagerãšãã®äžã§ã³ã³ã¹ãã©ã¯ã¿ãŒã宣èšããå¿ èŠããããŸããããã¯ãAndroidLauncherãããã®ã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ãžã®åç §ãåãå ¥ããŸãã
static VRSensorManager vrSensorManager; GdxVR(VRSensorManager vrSensorManager) { GdxVR.vrSensorManager = vrSensorManager; }
å šäœãšããŠã®ã³ãŒãïŒ
Gdxvr.java
package com.sinuxvr.sample; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g3d.Model; import com.badlogic.gdx.graphics.g3d.ModelBatch; import com.badlogic.gdx.graphics.g3d.ModelInstance; /** , , */ class GdxVR extends ApplicationAdapter { static VRSensorManager vrSensorManager; // private int scrHeight, scrHalfWidth; // viewport private AssetManager assets; // private ModelBatch modelBatch; // private ModelInstance roomInstance; // private VRCamera vrCamera; // VR /** */ GdxVR(VRSensorManager vrSensorManager) { GdxVR.vrSensorManager = vrSensorManager; } /** */ @Override public void create () { // scrHalfWidth = Gdx.graphics.getWidth() / 2; scrHeight = Gdx.graphics.getHeight(); // modelBatch = new ModelBatch(); assets = new AssetManager(); assets.load("room.g3db", Model.class); assets.finishLoading(); Model roomModel = assets.get("room.g3db"); roomInstance = new ModelInstance(roomModel); // (fov, parallax, near, far) vrCamera = new VRCamera(90, 0.4f, 0.1f, 30f); vrCamera.setPosition(-1.7f, 3f, 3f); // vrSensorManager.useDriftCorrection(true); } /** viewport- */ @Override public void render () { // Gdx.gl.glClearColor(0f, 0f, 0f, 1f); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); // vrCamera.update(); // Gdx.gl.glViewport(0, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getLeftCam()); modelBatch.render(roomInstance); modelBatch.end(); // Gdx.gl.glViewport(scrHalfWidth, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getRightCam()); modelBatch.render(roomInstance); modelBatch.end(); } /** */ @Override public void dispose () { modelBatch.dispose(); assets.dispose(); } }
createã¡ãœããã§ã¯ãç»é¢ãµã€ãºïŒå¹ ã2ã§é€ç®ãããçç±ãããããŸãïŒã確èªããã·ãŒã³ã¢ãã«ãããŒãããŠãããã«ã¡ã©ãäœæããŠé 眮ããŸãã
vrCamera = new VRCamera(90, 0.4f, 0.1f, 30f); vrCamera.setPosition(-1.7f, 3f, 3f);
äŸãšããŠãããªããè£æ£ããªã³ã«ããŸããã誰ããèµ·ååŸã«ã«ã¡ã©ã«åé¡ãããå Žåã¯ãã³ã³ãã¹ã調æŽããçç±ãæ¢ããŠãã ããã
vrSensorManager.useDriftCorrection(true);
renderã¡ãœããã§ã¯ããã¹ãŠã®ã¬ã³ããªã³ã°ã®åã«ãã«ã¡ã©ã®æŽæ°ãåŒã³åºãå¿ èŠããããŸãã
vrCamera.update();
ã¹ãã¬ãªãã¢ã¯ãæšæºã®ãã¥ãŒããŒãã䜿çšããŠå®è£ ãããŸãã ãã¥ãŒããŒããç»é¢ã®å·Šååã«èª¿æŽããå·Šç®çšã®çµµãæããŸãã
Gdx.gl.glViewport(0, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getLeftCam()); modelBatch.render(roomInstance); modelBatch.end();
æ£ãããã®ã«ã€ããŠããŸã£ããåãã§ãïŒ
Gdx.gl.glViewport(scrHalfWidth, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getRightCam()); modelBatch.render(roomInstance); modelBatch.end();
ãããã«
ãã¹ãŠãæ£ããè¡ãããããã¹ããŒããã©ã³ãVRã¡ã¬ãã«æ¿å ¥ããŠãèªåã§äœæããä»®æ³äžçã«æ²¡é ã§ããŸãã
æ°ããçŸå®ãžããããïŒ ç¬¬2éšã§ã¯ãµãŠã³ãã®æäœã«ã€ããŠèª¬æããŸãããä»æ¥ã¯ãã¹ãŠæã£ãŠããŸãã ã質åããããŸãããããæž èŽããããšãããããŸãããã³ã¡ã³ãã§ãçãããŸãã