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 scenenode
s 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'sderivedtransform
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
Post a Comment