Jak przekonwertować kliknięcie myszy na promień?

18

Mam rzut perspektywiczny. Gdy użytkownik kliknie ekran, chcę obliczyć promień między płaszczyzną bliską i daleką, która wystaje z punktu myszy, aby móc wykonać kod przecięcia promienia z moim światem.

Korzystam z własnych klas macierzy oraz klas wektorowych i ray i wszystkie działają zgodnie z oczekiwaniami.

Jednak kiedy próbuję przekonwertować promień na współrzędne świata, moje dalekie zawsze kończy się na 0,0,0, więc mój promień przechodzi od kliknięcia myszką do środka przestrzeni obiektu, a nie przez nią. (Współrzędne xiy odległości bliskiej i dalekiej są identyczne, różnią się tylko współrzędnymi z, gdzie są względem siebie ujemne)

GLint vp[4];
glGetIntegerv(GL_VIEWPORT,vp);
matrix_t mv, p;
glGetFloatv(GL_MODELVIEW_MATRIX,mv.f);
glGetFloatv(GL_PROJECTION_MATRIX,p.f);
const matrix_t inv = (mv*p).inverse();
const float
    unit_x = (2.0f*((float)(x-vp[0])/(vp[2]-vp[0])))-1.0f,
    unit_y = 1.0f-(2.0f*((float)(y-vp[1])/(vp[3]-vp[1])));
const vec_t near(vec_t(unit_x,unit_y,-1)*inv);
const vec_t far(vec_t(unit_x,unit_y,1)*inv);
ray = ray_t(near,far-near);

Co się pomyliłem? (Jak odsunąć punkt myszy?)

Wola
źródło
Z ciekawości, czy istnieje powód, dla którego nie korzystasz z wbudowanego trybu GL_SELECTION? (informacje tutaj)
Ricket
@Ricket Czy to nie jest przestarzałe? Nie wiem, ale całkiem pewny.
Notabene
Czy xiy są w pikselach? to byłby problem ...
Notabene
I mnożymy vec3 przez matrix4. To nie jest dobre ... jaka liczba idzie na 4. pozycję, kiedy wykonujesz vec_t (1., 2., 3., 4.)?
Notabene
@notebene Wow, chyba nie mam pojęcia. Ta dyskusja jest dobra i stanowi alternatywę, nad którą muszę się zastanowić, a teraz NAPRAWDĘ mam nadzieję na dobrą odpowiedź na to pytanie.
Ricket

Odpowiedzi:

14

Ostatnio musiałem to rozwiązać samodzielnie dla aplikacji WebGL. Dołączyłem pełny kod źródłowy, ale ponieważ nie działa on od razu z nietoperza, oto kilka wskazówek dotyczących debugowania:

  1. Nie debuguj swojej nieprojektowanej metody w grze. Jeśli to możliwe, spróbuj napisać testy w stylu testu jednostkowego, aby łatwiej było ustalić, co się dzieje.
  2. Pamiętaj, aby wydrukować promienie wyjściowe zarówno dla bliskiej, jak i dalekiej płaszczyzny obcinania.
  3. Pamiętaj, że matematyka macierzy NIE jest przemienna. A x C! = C x A. Sprawdź dokładnie swoją matematykę.

Ponadto, aby odpowiedzieć na niektóre powyższe komentarze, prawie nigdy nie chcesz używać API wyboru OpenGL. Pomaga to wybrać istniejące elementy, na przykład podczas tworzenia menu, jednak nie wykonuje większości rzeczywistych scenariuszy, takich jak edycja modelu 3D. Gdzie należy dodać geometrię w wyniku kliknięcia.

Oto moja implementacja. Tutaj nie dzieje się nic magicznego. Tylko JavaScript i biblioteka Google Closure.

gluUnProject

/**
 * Port of gluUnProject. Unprojects a 2D screen coordinate into the model-view
 * coordinates.
 * @param {Number} winX The window point for the x value.
 * @param {Number} winY The window point for the y value.
 * @param {Number} winZ The window point for the z value. This should range
 *    between 0 and 1. 0 meaning the near clipping plane and 1 for the far.
 * @param {goog.math.Matrix} modelViewMatrix The model-view matrix.
 * @param {goog.math.Matrix} projectMatrix The projection matrix.
 * @param {Array.<Number>} view the viewport coordinate array.
 * @param {Array.<Number>} objPos the model point result.
 * @return {Boolean} Whether or not the unprojection was successful.
 */
octorok.math.Matrix.gluUnProject = function(winX, winY, winZ,
                        modelViewMatrix, projectionMatrix,
                        viewPort, objPos) {
  // Compute the inverse of the perspective x model-view matrix.
  /** @type {goog.math.Matrix} */
  var transformMatrix =
    projectionMatrix.multiply(modelViewMatrix).getInverse();

  // Transformation of normalized coordinates (-1 to 1).
  /** @type {Array.<Number>} */
  var inVector = [
    (winX - viewPort[0]) / viewPort[2] * 2.0 - 1.0,
    (winY - viewPort[1]) / viewPort[3] * 2.0 - 1.0,
    2.0 * winZ - 1.0,
    1.0 ];

  // Now transform that vector into object coordinates.
  /** @type {goog.math.Matrix} */
  // Flip 1x4 to 4x1. (Alternately use different matrix ctor.
  var inMatrix = new goog.math.Matrix([ inVector ]).getTranspose();
  /** @type {goog.math.Matrix} */
  var resultMtx = transformMatrix.multiply(inMatrix);
  /** @type {Array.<Number>} */
  var resultArr = [
    resultMtx.getValueAt(0, 0),
    resultMtx.getValueAt(1, 0),
    resultMtx.getValueAt(2, 0),
    resultMtx.getValueAt(3, 0) ];

  if (resultArr[3] == 0.0) {
    return false;
  }

  // Invert to normalize x, y, and z values.
  resultArr[3] = 1.0 / resultArr[3];

  objPos[0] = resultArr[0] * resultArr[3];
  objPos[1] = resultArr[1] * resultArr[3];
  objPos[2] = resultArr[2] * resultArr[3];

  return true;
};

Stosowanie

  this.sys.event_mouseClicked = function(event) {
    // Relative x and y are computed via magic by SystemModule.
    // Should range from 0 .. viewport width/height.
    var winX = event.relativeX;
    var winY = event.relativeY;
    window.console.log('Camera at [' + me.camera.position_ + ']');
    window.console.log('Clicked [' + winX + ', ' + winY + ']');

    // viewportOriginX, viewportOriginY, viewportWidth, viewportHeight
    var viewPort = [0, 0, event.viewPortWidth, event.viewPortHeight];
    var objPos = [];  // out parameter.

    // The camera's model-view matrix is the result of gluLookAt.
    var modelViewMatrix = me.camera.getCameraMatrix();
    // The perspective matrix is the result of gluPerspective.
    var perspectiveMatrix = pMatrix.get();

    // Ray start
    var result1 = octorok.math.Matrix.gluUnProject(
      winX, winY, 0.0,
      modelViewMatrix, perspectiveMatrix,
      viewPort, objPos);
    window.console.log('Seg start: ' + objPos + ' (result:' + result1 + ')');

    // Ray end
    var result2 = octorok.math.Matrix.gluUnProject(
      winX, winY, 1.0,
      modelViewMatrix, perspectiveMatrix,
      viewPort, objPos);
    window.console.log('Seg end: ' + objPos + ' (result:' + result2 + ')');
  };
};
Chris Smith
źródło
W jakim przypadku, jeśli (wynikArr [3] == 0.0) ma wartość true?
Halsafar,
3

Nie widzę żadnych problemów z twoim kodem. Mam tylko kilka sugestii:

unit_x = (2.0f*((float)(x-vp[0])/(vp[2]-vp[0])))-1.0f,
unit_y = (2.0f*((float)(y-vp[1])/(vp[3]-vp[1])))-1.0f;

I próba zmiany mnożenia macierzy na (p*mv).inverse();. Tylko dlatego, że glMatrices są nutaralnie transponowane do pamięci. (to może jedna wielka przepraszam)

Kolejny raycasting
Unprojecting to nie tylko sposób na uzyskanie promienia z piksela. To jest mój kod dla Raycaster:

//works for right handed coordinates. So it is opengl friendly.
float mx = (float)((pixel.x - screenResolution.x * 0.5) * (1.0 / screenResolution.x) * camera.fovX * 0.5);
float my = (float)((pixel.y - screenResolution.y * 0.5) * (1.0 / screenResolution.x) * camera.fovX * 0.5);
float4 dx = cameraRight * mx;
float4 dy = cameraUp * my;

float3 dir = normalize(cameraDir + (dx + dy).xyz * 2.0);

CameraRight, Up i Direction można uzyskać z matrycy widoku (co jest najbardziej przydatne w module cieniującym) Wyświetl macierz w opengl

Pobieranie kolorów Wybór
kolorów to technika polegająca na renderowaniu każdego klikalnego obiektu innym kolorem (jednolitym), a następnie odczytywaniu wartości koloru w klikniętym punkcie. Działa jako GL_SELECTION w opengl. Ale teraz jest przestarzałe. Jednak wybieranie kolorów jest łatwe do zakodowania jako 1 dodatkowe przejście renderowania.

Użyj bufora ramki i renderuj do tekstury z taką samą rozdzielczością jak rozdzielczość ekranu, użyj prostego modułu cieniującego, który po prostu zwraca kolor w module cieniującym fragmenty. Po „przepuszczeniu koloru” przeczytaj wartość z adresowania tekstury za pomocą klikniętego punktu xiy. Znajdź obiekt o kolorze, który czytasz. Łatwo i dość szybko.

Notabene
źródło