14

The Short Version (TL;DR)

I have a Camera attached to a SceneNode and movement works fine as long as the SceneNode's rotation/axes are aligned with the world's. However, when an object rotates to "look" in a different direction and is told to move "forward" it does not move along the new "forward" direction. Instead, it continues to move in the same direction it was facing before the rotation was applied.

Details and Example

I have a scene graph to manage a 3D scene. The graph is a tree of SceneNode objects, which know about their transformations relative to their parent and the world.

As per the TL;DR; snippet, imagine you have a cameraNode with zero rotation (e.g. facing north) and then rotate the cameraNode 90 degrees to the left around the +Y "up" axis, i.e. make it look to the west. Things are OK so far. If you now try to move the cameraNode "forward", which is now to the west, the cameraNode instead moves as if "forward" were still facing north.

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

The code below shows what I've attempted most recently and my (current) best guess at narrowing down the areas most likely to be related to the problem.

Relevant SceneNode Members

The SceneNode implementation has the following fields (only those relevant to this question are shown):

class GenericSceneNode implements SceneNode {
    // this node's parent; always null for the root scene node in the graph
    private SceneNode parentNode;

    // transforms are relative to a parent scene node, if any
    private Vector3 relativePosition = Vector3f.createZeroVector();
    private Matrix3 relativeRotation = Matrix3f.createIdentityMatrix();
    private Vector3 relativeScale    = Vector3f.createFrom(1f, 1f, 1f);

    // transforms are derived by combining transforms from all parents;
    // these are relative to the world --in world space
    private Vector3 derivedPosition = Vector3f.createZeroVector();
    private Matrix3 derivedRotation = Matrix3f.createIdentityMatrix();
    private Vector3 derivedScale    = Vector3f.createFrom(1f, 1f, 1f);
    // ...
}

Adding a Camera to a scene simply means that it gets attached to a SceneNode in the graph. Since the Camera has no positional/rotational information of its own, the client simply handles the SceneNode to which the Camera is attached and that's it.

Except for the issue mentioned in this question, everything else appears to be working as expected.

SceneNode Translation

The math to translate the node in a specific direction is straightforward and basically 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 than the issue mentioned in this question, things around as expected.

SceneNode Rotation

The client application rotates the cameraNode as follows:

final Angle rotationAngle = new Degreef(-90f);
// ...
cameraNode.yaw(rotationAngle);

And the SceneNode implementation is also fairly 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 for the rotation is encapsulated in a 3x3 matrix object. Note that, during tests, you can see the scene being rotated around the camera, so rotations are indeed being applied, which makes this issue even more puzzling to me.

Direction Vectors

The directional vectors are simply columns from taken from the derived 3x3 rotation matrix, relative to the 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, this is how the parentNode transforms are combined to compute the 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 is used to propagate transforms through the scene graph, so that child SceneNodes can take their parent's transforms into account.


Other/Related Questions

I've gone through several answers inside and outside of SO during the last ~3 weeks prior to posting this question (e.g. here, here, here, and here, among several others). Obviously, though related, they really weren't helpful in my case.


Answers to Questions in the Comments

Are you sure that when computing derivedTransform your parent's derivedTransform is already computed?

Yes, the parent SceneNode is always updated before updating children. The update logic is:

@Override
public void update(boolean updateChildren, boolean parentHasChanged) {
    boolean updateRequired = parentHasChanged || isOutOfDate;

    // update this node's transforms before updating children
    if (updateRequired)
        updateFromParent();

    if (updateChildren)
        for (Node n : childNodesMap.values())
            n.update(updateChildren, updateRequired);

    emitNodeUpdated(this);
}

@Override
public void updateFromParent() {
    updateDerivedTransforms();  // implementation above
    isOutOfDate = false;
}

This piece invokes the private method in the previous section.

Community
  • 1
  • 1
code_dredd
  • 5,915
  • 1
  • 25
  • 53
  • Which version of OpenGL are you using? – Francis Cugler Aug 08 '16 at 09:49
  • @FrancisCugler I'm using OpenGL 4.5. Do you need me to post the shaders? – code_dredd Aug 08 '16 at 09:50
  • No; just wanted to know if you was using modern OpenGL or the old 1.0 API calls. I'm not familiar with Java but I've been working with OpenGL in c++. I know that OpenGL doesn't have a Camera Object and that you have to create one and attach it to the scene. Then once you render it the moving and rotation of it depends on the Handedness of your defined coordinate system. Your MVP (Model View Projection) matrix calculations are important; and the order in which you do them for Rotation Translation and Scaling is important too. – Francis Cugler Aug 08 '16 at 09:55
  • @FrancisCugler I understand. The `Camera` and `SceneNode` objects were defined by me. Even if you're not familiar with Java, you're familiar with C++ and OpenGL, so you likely already understand the semantics of the code above (sans operator overloading). You might still be able to notice something I've missed (e.g. math or something else). If you want me to add more info, let me know what it'd be. – code_dredd Aug 08 '16 at 09:58
  • Have you tried to just render the camera separately instead of nesting its node under the scene's node? – Francis Cugler Aug 08 '16 at 10:01
  • @FrancisCugler Yes, I have a separate dev branch in my repo where the `Camera` has its own information and does not need to rely on a `SceneNode` for its position/rotation and can move on its own. It behaves exactly the same as above with the same issue. – code_dredd Aug 08 '16 at 10:03
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/120414/discussion-between-francis-cugler-and-ray). – Francis Cugler Aug 08 '16 at 10:04
  • Are you sure that when computing `derivedTransform` your parent's derivedTransform is already computed? Or maybe you could create a scene with single node and Camera attached, and debug your computations? – kolenda Aug 11 '16 at 15:48
  • @kolenda, Yes, I'm sure. The `SceneNode` will update itself before trying to update any children it may have. – code_dredd Aug 11 '16 at 21:16
  • @kolenda, Also, I already have 2 demo programs with scene nodes and cameras showing the problem. Can you clarify your 2nd question? – code_dredd Aug 11 '16 at 21:29
  • I meant to create just one node, attach a camera to it and rotate the node. Then log out or debug values for local/global rotation and forward vector. You should get some wrong values but in such simple setup it should be easier to spot the bug. – kolenda Aug 12 '16 at 15:08
  • @kolenda I did that days before I ended up posting this question. Nothing "stood out" to me as "aha!, there's the problem". I try to avoid posting questions as much as I can... I've normally run out of ideas by the time I post (which was 1.5+ weeks of prior debugging attempts). – code_dredd Aug 15 '16 at 11:20

1 Answers1

1

This is not meant to be a direct answer but as reference upon the request of the OP.

OpenGL v1.0 using old API calls: Implementation of a Camera Class Object while using it in a Scene Class outside of the Scene Class's Scene Graph. This is 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 the Camera's Render function using the old OpenGL API calls we first load in the Modelview matrix, then we load the identity matrix; then we finally use glu's gluLookAt(...) method to set the positions of the needed vectors.

Scene.h - Has many members and functions; but as in regards with the Camera Object it has a Camera as a member and not a pointer to a 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 To Render
        return;
    }

    EnableLights();

    // Send Items To Be Rendered
    // Clear 2nd Render Pass Container
    DeleteAllAlphaObjects();

    // Render All Opaque Objects (1st Pass) & Store 2nd Pass Objects
    _vpNodes[0]->RenderOGL( false, true );

    // Render All Objects With Alpha Values (2nd Pass)
    glEnable( GL_BLEND );
    glMatrixMode( GL_MODELVIEW );

    for ( std::vector<AlphaObject*>::iterator it = _vpAlphaObjects.begin(); it != _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 the Camera is independent of the Player class as well as the Scene's Scene Graph Hierarchy and we use the Camera in the Scene's Render Call. Here we set the Camera by getting the Player's current Position, and the Player's LookCenter direction.

EDIT - Adding Player Class And Related Code For 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 For Looking Up And 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: {
            // Get "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: {
            // Get "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 This Deals With Player's Weapons    
} 

void Player::SetSpeed( float fLinear, float fAngular ) {        
    _fLinearSpeed  = fLinear;
    _fAngularSpeed = fAngular;    
} 

Scene.h - Same here as for the Camera; there is a Player Object and not a pointer to a player object. However there is a pointer to a playerTransform which is a NodeTransform. There are too many functions to list here because of the interaction of the Player with the Scene since this is a working 3D Game. I can provide a few functions that may be of interest.

Scene.cpp Scene::Update()

// -----------------------------------------------------------------------
// Update
// Animate Objects, Pickup Checks Etc. This All Happens At The
// 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 the order of operations for 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 For Later Retrieval
        glMatrixMode( GL_MODELVIEW );
        glPushMatrix();

        if ( _bHaveMatrix ) {
            // Use Transformation Matrix
            glMultMatrixf( &_f16Matrix[0] );
        }

        // Transalate
        glTranslatef( _v3Translate._fX, _v3Translate._fY, _v3Translate._fZ );

        // Move Back To 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 By -ve Center Value
        glTranslatef( -_v3Center._fX, -_v3Center._fY, -_v3Center._fZ );

        // Move Down The Tree
        _pIn->RenderOGL( bSecondPass, true );

        // Get Old Matrix
        glMatrixMode( GL_MODELVIEW );
        glPopMatrix();
    }

    if ( _pNext && bRenderNext ) {
        _pNext->RenderOGL( bSecondPass, true );
    }    
} // RenderOGL
Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • 1
    Your computation to move the `Player` in the direction it's facing seems similar to mine, though. – code_dredd Aug 08 '16 at 12:16
  • 1
    Went through the, rather large, code dump in a bit more detail, but unfortunately it wasn't too helpful in trying to figure out what I'm doing incorrectly. The math does look similar to mine, particularly the player movement, but other than that, the post has a lot of code that's not too relevant to the main issue I'm having. Thanks for trying it out, though. – code_dredd Aug 08 '16 at 23:56
  • No problem at all; I don't mind helping out others where and when I can. I enjoy helping so that one day when I may need help others would do the same. – Francis Cugler Aug 09 '16 at 13:46