1
0

More than 1 year has passed since last update.

[C++]タッチで3Dオブジェクトを移動させる計算方法

Posted at

概要

スマホに3Dの空間が描かれているとして、オブジェクトをタッチして移動させる計算方法を考えてみた。
移動は、cameraのある平面に対して平行に移動させる。

前提

文法はC++で記載しているが、計算方法のアイデアが主な目的なので、コードはあまり気にしなくてよいかも。
OpenGLやVulkanの座標系を想定し、cameraはこちら向きを正とする。
https://learnopengl.com/Getting-started/Camera

結論

忙しい人向け。

glm::vec4 Camera2TouchScreen(glm::vec2* touchPos)
{
  //screen space
  float x = touchPos[0].x / width;
  float y = touchPos[0].y / height;
  //screen space to clip space
  x = x * 2.0f - 1.0f;
  y = y * 2.0f - 1.0f;
  //clip space to view space
  glm::mat4 invp = glm::inverse(proj);
  glm::vec4 sview = invp * glm::vec4(x, y, 1.0f, 1.0f);
  //view space to world space
  return glm::inverse(view) * glm::vec4(glm::vec3(sview), 0.0f);
}

//calc diff(translate matrix)
glm::vec3 o = object - cameraPos;
float dotNO = glm::dot(-d, glm::normalize(o));
float nLength = dotNO * glm::length(o);
glm::vec3 directionM = Camera2TouchScreen(touchPos);
float dotNM = glm::dot(-d, glm::normalize(directionM));
float mLength = nLength / dotNM;
glm::vec3 m = cameraPos + mLength * glm::normalize(directionM);
mat4 diff = glm::translate(diff, m - object);

座標系の用語

image.png
引用:https://learnopengl.com/Getting-started/Coordinate-Systems
The global picture

SCREEN空間 -> WORLD空間

image.png

SCREEN空間にあるタッチ位置をWORLD空間に変換する。
ただし、SCREEN空間は2次元でWORLD空間は3次元なので、変換して得られるものはWORLD空間内の、cameraを原点とするタッチ位置への方向ベクトルであることに注意する。

glm::vec4 Camera2TouchScreen(glm::vec2* touchPos)
{
  //screen space
  float x = touchPos[0].x / width;
  float y = touchPos[0].y / height;
  //screen space to clip space
  x = x * 2.0f - 1.0f;
  y = y * 2.0f - 1.0f;
  //clip space to view space
  glm::mat4 invp = glm::inverse(proj);
  glm::vec4 sview = invp * glm::vec4(x, y, 1.0f, 1.0f);
  //view space to world space
  return glm::inverse(view) * glm::vec4(glm::vec3(sview), 0.0f);
}

AndroidではAMotionEvent_getで取ってこれるタッチ位置はピクセルで返されるので、取得したピクセル位置からWORLD空間内の、camera -> タッチ位置の4次元方向ベクトルが上記で得られる。
ここで、
touchPos : タッチ位置(ピクセル)
width : スマホ画面の横の長さ(ピクセル)
height : スマホ画面の縦の長さ(ピクセル)
proj : projection行列(mat4)
view : view行列(mat4)
である。

WORLD空間

計算したいベクトルがすべてWORLD空間に持ってこれたので、WORLD空間内で計算を行う。
行いたいことはcameraの向きに垂直な平面に平行になるように、タッチ位置の分だけobjectを移動させることなので、diffを求めることである。
アイデアとしては、mの方向ベクトルだけは上記のCamera2TouchScreen()で求まるので、長さを内積を駆使して求める。

//length n
glm::vec3 o = object - cameraPos;
float dotNO = glm::dot(-d, glm::normalize(o));
float nLength = dotNO * glm::length(o);
//direction of m
glm::vec3 directionM = Camera2TouchScreen(touchPos);
float dotNM = glm::dot(-d, glm::normalize(directionM));
float mLength = nLength / dotNM;
glm::vec3 m = cameraPos + mLength * glm::normalize(directionM);
mat4 diff = glm::translate(diff, m - object);

objectの位置は分かっているが、objectがあってcameraの向きに垂直な平面Pに垂直なベクトルnの長さがわかっていないので、これを求める。
dは正規化されたcameraの方向ベクトルとして、doの内積dotNOを求めて、これをoに掛けることでnの長さを求める。
方向ベクトルdirectionMは上記の関数から求まり、方向ベクトルが求まれば内積dotNMは計算できるので、これを計算する。
今度はmの長さを求めたいので、nの長さをdotNMで割る。
方向ベクトルがわかり、長さもわかったのでベクトルmが一意に定まる。
あとはmodel行列のtranslate行列として、位置の差分diffを計算する。

感想

要は平面P上にあって、求まる方向ベクトルとの交点を求めたいのだが、ググると直線と平面の交点を求める方程式がヒットする。
人間は方程式の方が扱いやすいが、コンピュータはベクトルをそのまま用いるほうが扱いやすい。
方程式の解を求めるプログラムを書くことは大変なので、何とかしてコンピュータが得意な演算で求められないか、を考えた結果が以上となっている。
Qiitaで検索してもヒットしなかったので、有名なゲームエンジンには内部関数として用意されているのかもしれないが、自力でやってみると以上の方法もある(もちろん別の方法もある)程度の記事でした。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0