java - Fix for 3D camera to move in the direction it's facing? -


the short version (tl;dr)

i have camera attached scenenode , movement works fine long scenenode's rotation/axes aligned world's. however, when object rotates "look" in different direction , told move "forward" not move along new "forward" direction. instead, continues move in same direction facing before rotation applied.

details , example

i have scene graph manage 3d scene. graph tree of scenenode objects, know transformations relative parent , world.

as per tl;dr; snippet, imagine have cameranode 0 rotation (e.g. facing north) , rotate cameranode 90 degrees left around +y "up" axis, i.e. make west. things ok far. if try move cameranode "forward", west, cameranode instead moves if "forward" still facing north.

in short, moves as if had never been rotated in first place.

the code below shows i've attempted , (current) best guess @ narrowing down areas related problem.

relevant scenenode members

the scenenode implementation has following fields (only relevant question shown):

class genericscenenode implements scenenode {     // node's parent; null root scene node in graph     private scenenode parentnode;      // transforms relative parent scene node, if     private vector3 relativeposition = vector3f.createzerovector();     private matrix3 relativerotation = matrix3f.createidentitymatrix();     private vector3 relativescale    = vector3f.createfrom(1f, 1f, 1f);      // transforms derived combining transforms parents;     // these relative world --in world space     private vector3 derivedposition = vector3f.createzerovector();     private matrix3 derivedrotation = matrix3f.createidentitymatrix();     private vector3 derivedscale    = vector3f.createfrom(1f, 1f, 1f);     // ... } 

adding camera scene means gets attached scenenode in graph. since camera has no positional/rotational information of own, client handles scenenode camera attached , that's it.

except issue mentioned in question, else appears working expected.

scenenode translation

the math translate node in specific direction straightforward , boils down to:

currentposition = currentposition + normalizeddirectionvector * offset; 

the scenenode implementation follows:

@override public void moveforward(float offset) {     translate(getderivedforwardaxis().mult(-offset)); }  @override public void movebackward(float offset) {     translate(getderivedforwardaxis().mult(offset)); }  @override public void moveleft(float offset) {     translate(getderivedrightaxis().mult(-offset)); }  @override public void moveright(float offset) {     translate(getderivedrightaxis().mult(offset)); }  @override public void moveup(float offset) {     translate(getderivedupaxis().mult(offset)); }  @override public void movedown(float offset) {     translate(getderivedupaxis().mult(-offset)); }  @override public void translate(vector3 tv) {     relativeposition = relativeposition.add(tv);     isoutofdate = true; } 

other issue mentioned in question, things around expected.

scenenode rotation

the client application rotates cameranode follows:

final angle rotationangle = new degreef(-90f); // ... cameranode.yaw(rotationangle); 

and scenenode implementation straightforward:

@override public void yaw(angle angle) {     // fixme?: rotate(angle, getderivedupaxis()) accumulates other rotations     rotate(angle, vector3f.createunitvectory()); }  @override public void rotate(angle angle, vector3 axis) {     relativerotation = relativerotation.rotate(angle, axis);     isoutofdate = true; } 

the math/code rotation encapsulated in 3x3 matrix object. note that, during tests, can see scene being rotated around camera, rotations indeed being applied, makes issue more puzzling me.

direction vectors

the directional vectors columns taken derived 3x3 rotation matrix, relative world:

@override public vector3 getderivedrightaxis() {     return derivedrotation.column(0); }  @override public vector3 getderivedupaxis() {     return derivedrotation.column(1); }  @override public vector3 getderivedforwardaxis() {     return derivedrotation.column(2); } 

computing derived transforms

if it's relevant, how parentnode transforms combined compute derived transforms of this instance:

private void updatederivedtransforms() {     if (parentnode != null) {         /**          * derivedrotation = parent.derivedrotation * relativerotation          * derivedscale    = parent.derivedscale    * relativescale          * derivedposition = parent.derivedposition + parent.derivedrotation * (parent.derivedscale * relativeposition)          */         derivedrotation = parentnode.getderivedrotation().mult(relativerotation);         derivedscale = parentnode.getderivedscale().mult(relativescale);          vector3 scaledposition = parentnode.getderivedscale().mult(relativeposition);         derivedposition = parentnode.getderivedposition().add(parentnode.getderivedrotation().mult(scaledposition));     } else {         derivedposition = relativeposition;         derivedrotation = relativerotation;         derivedscale = relativescale;     }      matrix4 t, r, s;      t = matrix4f.createtranslationfrom(relativeposition);     r = matrix4f.createfrom(relativerotation);     s = matrix4f.createscalingfrom(relativescale);     relativetransform = t.mult(r).mult(s);      t = matrix4f.createtranslationfrom(derivedposition);     r = matrix4f.createfrom(derivedrotation);     s = matrix4f.createscalingfrom(derivedscale);     derivedtransform = t.mult(r).mult(s); } 

this used propagate transforms through scene graph, child scenenodes can take parent's transforms account.


other/related questions

i've gone through several answers inside , outside of during last ~3 weeks prior posting question (e.g. here, here, here, , here, among several others). obviously, though related, weren't helpful in case.


answers questions in comments

are sure when computing derivedtransform parent's derivedtransform computed?

yes, parent scenenode updated before updating children. update logic is:

@override public void update(boolean updatechildren, boolean parenthaschanged) {     boolean updaterequired = parenthaschanged || isoutofdate;      // update node's transforms before updating children     if (updaterequired)         updatefromparent();      if (updatechildren)         (node n : childnodesmap.values())             n.update(updatechildren, updaterequired);      emitnodeupdated(this); }  @override public void updatefromparent() {     updatederivedtransforms();  // implementation above     isoutofdate = false; } 

this piece invokes private method in previous section.

this not meant direct answer reference upon request of op.

opengl v1.0 using old api calls: implementation of camera class object while using in scene class outside of scene class's scene graph. written in c++

camera.h

#ifndef camera_h #define camera_h  #include "core.h"  class camera {     private:     vector3 _v3eyeposition;     vector3 _v3lookcenter;     vector3 _v3up;  public:     camera();     ~camera();          void get3rdpersonlocation( vector3 &v3position, float &fangle );     void set( vector3 v3eyeposition, vector3 v3lookcenter, vector3 v3up = vector3( 0.0f, 1.0f, 0.0f ) );     void render();     };   #endif 

camera.cpp

#include "stdafx.h" #include "camera.h"  camera::camera() {         _v3eyeposition = vector3( 0.0f, 0.0f,  0.0f );     _v3lookcenter  = vector3( 0.0f, 0.0f, -1.0f );     _v3up          = vector3( 0.0f, 1.0f,  0.0f );     }   camera::~camera() { }   void camera::get3rdpersonlocation( vector3 &v3position, float &fangle ) {        v3position._fx = _v3lookcenter._fx;     v3position._fy = _v3eyeposition._fy;     v3position._fz = _v3lookcenter._fz;      // find angle     float fx = _v3lookcenter._fx - _v3eyeposition._fx;     float fz = _v3lookcenter._fz - _v3eyeposition._fz;      // angle in degrees     fangle = math::radian2degree( atan2( fx, fz ) );     }       void camera::set( vector3 v3eyeposition, vector3 v3lookcenter, vector3 v3up ) {         _v3eyeposition = v3eyeposition;     _v3lookcenter  = v3lookcenter;     _v3up          = v3up;     }  void camera::render() {          glmatrixmode( gl_modelview );     glloadidentity();      glulookat( _v3eyeposition._fx, _v3eyeposition._fy, _v3eyeposition._fz,                _v3lookcenter._fx,  _v3lookcenter._fy,  _v3lookcenter._fz,                _v3up._fx,          _v3up._fy,          _v3up._fz );      } 

in camera's render function using old opengl api calls first load in modelview matrix, load identity matrix; use glu's glulookat(...) method set positions of needed vectors.

scene.h - has many members , functions; in regards camera object has camera member , not pointer camera.

scene.cpp - render()

void scene::render() {         // update camera     _camera.set( _player.getposition(), _player.getlookcenter() );      // position camera     _camera.render();          if ( usersettings::get()->_bquit ) {         return;     }      if ( _vpnodes.size() < 1 ) {         // no scenegraph render         return;     }      enablelights();      // send items rendered     // clear 2nd render pass container     deleteallalphaobjects();      // render opaque objects (1st pass) & store 2nd pass objects     _vpnodes[0]->renderogl( false, true );      // render objects alpha values (2nd pass)     glenable( gl_blend );     glmatrixmode( gl_modelview );      ( std::vector<alphaobject*>::iterator = _vpalphaobjects.begin(); != _vpalphaobjects.end(); ++it ) {         // set model view matrix         glmatrixmode( gl_modelview );         glpushmatrix();         glloadmatrixf( &(*it)->f16matrix[0] );          (*it)->pshape->renderogl( true, false );          glmatrixmode( gl_modelview );         glpopmatrix();     }      // show selected weapon     _player.renderweapon();      gldisable( gl_blend );      disablelights();      return;     }  

here camera independent of player class scene's scene graph hierarchy , use camera in scene's render call. here set camera getting player's current position, , player's lookcenter direction.

edit - adding player class , related code movement calculations

enum action {     no_action = -1,     moving_forward = 0,     moving_back,     moving_left,     moving_right,     looking_left,     looking_right,     looking_up,     looking_down, }; // action 

player.h

#ifndef player_h #define player_h  #include "core.h"  class weapon; class nodetransform;  class player { private:     enum mouselook {         ml_normal = 1,         ml_invert = -1,     } _mouselookstate; // mouselook      vector3 _v3position;     vector3 _v3lookcenter;      float _flookdistance;     float _fmaxup;     float _fmaxdown;      float _flinearspeed;     float _fangularspeed;  public:     player( float flookdistance );     ~player();      void    setspeed( float flinear, float fangular );      void    setmousey( bool binvert );     void    setlocation( vector3 v3position, vector3 v3direction = vector3( 0.0f, 0.0f, -1.0f ) );     void    move( action action, float fdeltatime );      bool    update();         inline void     setposition( vector3 v3position );     inline vector3  getposition();     inline vector3  getlookcenter();     inline vector3  getlookdirection();          };  inline void player::setposition( vector3 v3position ) {     vector3 v3lookdirection;     v3lookdirection = _v3lookcenter - _v3position;      _v3position   = v3position;     _v3lookcenter = v3position + v3lookdirection; }  inline vector3 player::getposition() {       return _v3position; }   inline vector3 player::getlookcenter() {     return _v3lookcenter; }   inline vector3 player::getlookdirection() {         vector3 v3lookdirection;     v3lookdirection = _v3lookcenter - _v3position;         v3lookdirection.normalize();         return v3lookdirection;     }  #endif 

player.cpp

#include "stdafx.h" #include "player.h" #include "usersettings.h" #include "nodetransform.h"  player::player( float flookdistance ) {         _flookdistance  = flookdistance;         // calculate maximum limits looking , down     _fmaxup         = _flookdistance * tan( math::degree2radian( 50 ) );     _fmaxdown       = _flookdistance * tan( math::degree2radian( 40 ) );      _v3position     = vector3( 0.0f, 0.5f, 0.0f );     _v3lookcenter   = vector3( 0.0f, 0.5f, -flookdistance );      _flinearspeed   = 15.0f; // units per second     _fangularspeed  = 3.0f; // radians per second      setmousey( usersettings::get()->getmouseinvert() );     }   player::~player() { } // ~player  void player::setmousey( bool binvert ) {         if ( binvert ) {         _mouselookstate = ml_invert;     } else {         _mouselookstate = ml_normal;     }        }   void player::setlocation( vector3 v3position, vector3 v3direction ) {         _v3position   = v3position;     _v3lookcenter = v3position + _flookdistance*v3direction;     }  void player::move( action action, float fdeltatime ) {         vector3 v3lookdirection;     v3lookdirection = _v3lookcenter - _v3position;      switch ( action ) {         case moving_forward: {             // prevent vertical motion             v3lookdirection._fy = 0.0f;             _v3position   += v3lookdirection*fdeltatime*_flinearspeed;             _v3lookcenter += v3lookdirection*fdeltatime*_flinearspeed;             break;         }         case moving_back: {             // prevent vertical motion             v3lookdirection._fy = 0.0f;             _v3position   -= v3lookdirection*fdeltatime*_flinearspeed;             _v3lookcenter -= v3lookdirection*fdeltatime*_flinearspeed;             break;         }         case moving_left: {             // "side" direction & prevent vertical motion             v3lookdirection._fy = v3lookdirection._fx;             v3lookdirection._fx = -v3lookdirection._fz;             v3lookdirection._fz = v3lookdirection._fy;             v3lookdirection._fy = 0.0f;              _v3position   -= v3lookdirection*fdeltatime*_flinearspeed;             _v3lookcenter -= v3lookdirection*fdeltatime*_flinearspeed;             break;         }         case moving_right: {             // "side" direction & prevent vertical motion             v3lookdirection._fy = v3lookdirection._fx;             v3lookdirection._fx = -v3lookdirection._fz;             v3lookdirection._fz = v3lookdirection._fy;             v3lookdirection._fy = 0.0f;              _v3position   += v3lookdirection*fdeltatime*_flinearspeed;             _v3lookcenter += v3lookdirection*fdeltatime*_flinearspeed;             break;         }         case looking_left: {              /*float fsin = -sin( fdeltatime*_fangularspeed );             float fcos =  cos( fdeltatime*_fangularspeed );              _v3lookcenter._fx = _v3position._fx + (-fsin * v3lookdirection._fz + fcos * v3lookdirection._fx );             _v3lookcenter._fz = _v3position._fz + ( fcos * v3lookdirection._fz + fsin * v3lookdirection._fx );             break;*/              // third person             float fsin = sin( fdeltatime*_fangularspeed );             float fcos = -cos( fdeltatime*_fangularspeed );              _v3position._fx = _v3lookcenter._fx + (-fsin * v3lookdirection._fz + fcos * v3lookdirection._fx );             _v3position._fz = _v3lookcenter._fz + ( fcos * v3lookdirection._fz + fsin * v3lookdirection._fx );             break;         }         case looking_right: {             /*float fsin = sin( fdeltatime*_fangularspeed );             float fcos = cos( fdeltatime*_fangularspeed );              _v3lookcenter._fx = _v3position._fx + (-fsin * v3lookdirection._fz + fcos * v3lookdirection._fx );             _v3lookcenter._fz = _v3position._fz + ( fcos * v3lookdirection._fz + fsin * v3lookdirection._fx );             break;*/              // third person             float fsin = -sin( fdeltatime*_fangularspeed );             float fcos = -cos( fdeltatime*_fangularspeed );              _v3position._fx = _v3lookcenter._fx + (-fsin * v3lookdirection._fz + fcos * v3lookdirection._fx );             _v3position._fz = _v3lookcenter._fz + ( fcos * v3lookdirection._fz + fsin * v3lookdirection._fx );             break;         }         case looking_up: {             _v3lookcenter._fy -= fdeltatime*_fangularspeed*_mouselookstate;              // check maximum values             if ( _v3lookcenter._fy > (_v3position._fy + _fmaxup ) ) {                 _v3lookcenter._fy = _v3position._fy + _fmaxup;             } else if ( _v3lookcenter._fy < (_v3position._fy - _fmaxdown) ) {                 _v3lookcenter._fy = _v3position._fy - _fmaxdown;             }             break;         }     }     }  bool player::update() {          // stripped down deals player's weapons     }   void player::setspeed( float flinear, float fangular ) {             _flinearspeed  = flinear;     _fangularspeed = fangular;     }  

scene.h - same here camera; there player object , not pointer player object. there pointer playertransform nodetransform. there many functions list here because of interaction of player scene since working 3d game. can provide few functions may of interest.

scene.cpp scene::update()

// ----------------------------------------------------------------------- // update // animate objects, pickup checks etc. happens @ // physics refresh rate void scene::update() {      usersettings* pusersettings = usersettings::get();     audiomanager* paudio = audiomanager::getaudio();      bool bplayermoving = false;      // movement     if ( pusersettings->isaction( moving_forward ) ) {         _player.move( moving_forward, gameogl::getphysicstimestep() );         bplayermoving = true;     }      if ( pusersettings->isaction( moving_back ) ) {         _player.move( moving_back, gameogl::getphysicstimestep() );         bplayermoving = true;     }      if ( pusersettings->isaction( moving_left ) ) {         _player.move( moving_left, gameogl::getphysicstimestep() );         bplayermoving = true;     }      if ( pusersettings->isaction( moving_right ) ) {         _player.move( moving_right, gameogl::getphysicstimestep() );         bplayermoving = true;     }          if ( bplayermoving && !_bplayerwalking ) {         paudio->setlooping( audio_footsteps, true );         paudio->play( audio_footsteps );         _bplayerwalking = true;     }     else if ( !bplayermoving && _bplayerwalking ) {         paudio->stop( audio_footsteps );         _bplayerwalking = false;     }        // ... other code here     } 

edit - adding nodetransform::render() - show order of operations mvp

// move model view matrix m = (t c r s c^) void nodetransform::renderogl( bool bsecondpass, bool brendernext ) {         if ( _pin && _bvisible ) {         // put matrix onto stack later retrieval         glmatrixmode( gl_modelview );         glpushmatrix();          if ( _bhavematrix ) {             // use transformation matrix             glmultmatrixf( &_f16matrix[0] );         }          // transalate         gltranslatef( _v3translate._fx, _v3translate._fy, _v3translate._fz );          // move center         gltranslatef( _v3center._fx, _v3center._fy, _v3center._fz );          // rotate         glrotatef( _frotateangle, _v3rotateaxis._fx, _v3rotateaxis._fy, _v3rotateaxis._fz );          // scale         glscalef( _v3scale._fx, _v3scale._fy, _v3scale._fz );          // offset -ve center value         gltranslatef( -_v3center._fx, -_v3center._fy, -_v3center._fz );          // move down tree         _pin->renderogl( bsecondpass, true );          // old matrix         glmatrixmode( gl_modelview );         glpopmatrix();     }      if ( _pnext && brendernext ) {         _pnext->renderogl( bsecondpass, true );     }     } // renderogl 

Comments

Popular posts from this blog

Spring Boot + JPA + Hibernate: Unable to locate persister -

go - Golang: panic: runtime error: invalid memory address or nil pointer dereference using bufio.Scanner -

c - double free or corruption (fasttop) -