Algorithm for Calculating Directional Movement - c++

I'm trying to write a basic Asteroids game in C++ using SFML. While I'm able to move my triangle (the player) around rather easily, the way in which it moves I'm not quite satisfied with.
Currently, the movement isn't relative to the direction it's facing. For example, if I press "w" to move forward, the triangle will simply move up the screen regardless of its direction.
What I'd like for the triangle to do is move forward when the "w" key is pressed, but make the actual direction its moving (in respect to the dimensions of the screen) relative to the actual direction its facing.
So, if the forward tip of the triangle is facing right, when "W" is pressed, it will move right. If "S" is pressed in this case, it will move left. "A" would make it move up, and of course "D" would make it move down.
My current code does not accomplish this one bit - I think there's a really hacky solution to this, but I'd rather approach this from a more mathematical and elegant perspective if possible.
So, what would be the best way to accomplish this?
I have three methods of relevance: one for handling keyboard input, the other for handling mouse movement, and an update method.
Code
void Game::OnKeyPress( void )
{
int count = 0;
const float playerCenterMag = Magnitude( mPlayer.GetShape().GetCenter() );
//Shoddy attempt resulting in absolutely nothing of note
const float moveRateX = ( cosf( mMouseAoR ) * playerCenterMag ) + mPlayer.MoveSpeed;
const float moveRateY = ( sinf( mMouseAoR ) * playerCenterMag ) + mPlayer.MoveSpeed;
if ( mInput.IsKeyDown( sf::Key::W ) ) // Up
{
mPlayer.Position.y -= moveRateY;
++count;
}
if ( mInput.IsKeyDown( sf::Key::S ) ) // Down
{
mPlayer.Position.y += moveRateY;
++count;
}
if ( mInput.IsKeyDown( sf::Key::A ) ) // Left
{
mPlayer.Position.x -= moveRateX;
++count;
}
if ( mInput.IsKeyDown( sf::Key::D ) ) // Right
{
mPlayer.Position.x += moveRateX;
++count;
}
std::cout << "Key Press Rate => " << count << " seconds" << std::endl;
}
void Game::OnMouseEvent( void )
{
mPlayer.GetShape().Rotate( mMouseAoR + 5 );
}
void Game::Update( void )
{
const int x = mInput.GetMouseX();
const int y = mInput.GetMouseY();
mMouseAoR = ( y == 0 ) ? 0
: tanf( x / y );
PrintInfo();
}

There are two versions of this game. If you want, say, 'W' to "thrust" forward, then you need to add moveRate at each cycle and use W to change the rate.
Here apparently you move only if a button is pressed, so you need to update always both X and Y:
if ( mInput.IsKeyDown( sf::Key::W ) ) // Up
{
mPlayer.Position.x += moveRateX;
mPlayer.Position.y += moveRateY;
++count;
}
The moveRate changes according to orientation. When moving laterally, you would do
mPlayer.Position.x += moveRateY;
mPlayer.Position.y += moveRateX;
when moving left (or right), and -= in the other direction.

Related

Drag and Drop Item list not working properly on ImGUI

Im using ImGUI and I want to implement a layer menu for the images and to move them im using
Drag to reorder items in a vector.
Sometimes it works just fine but others the images just jumps from the current position to a random one.
for (int i = 0; i < this->Images->size(); i++) {
ImGui::Image((void*)(intptr_t)this->Images->at(i).texture, ImVec2(100 * temp_percentage, 100 * temp_percentage));
ImGui::SameLine();
ImGui::Selectable(this->Images->at(i).name.c_str());
if (ImGui::IsItemActive() && !ImGui::IsItemHovered())
{
int n_next = i + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1);
if (n_next >= 0 && n_next < this->Images->size())
{
std::swap(this->Images->at(i), this->Images->at(n_next));
*this->CurrentImage = this->Images->front();
centerImage();
ImGui::ResetMouseDragDelta();
}
}
ImGui::Separator();
}
The problem lies at !ImGui::IsItemHovered(), there is small spacing between the lines (cell, selectable,... ), so when the mouse hovers over that spacing, the item isn't hovered but still is actived, and therefore will execute the swap and reset mouse delta multiple times making it goes to the top or bottom of the list. This will also happen if the mouse goes out of the table/window bounds.
To make the problem more visible, you can make the spacing bigger using ImGui::GetStyle().ItemSpacing.y = 50.f;.
To actually fix the problem, you'll have to calculate the item index using the mouse position, here is a way to do it, tho not perfect but it works.
ImGuiStyle& style = ImGui::GetStyle();
ImVec2 windowPosition = ImGui::GetWindowPos();
ImVec2 cursorPosition = ImGui::GetCursorPos();
// this is not a pixel perfect position
// you can try to make it more accurate by adding some offset
ImVec2 itemPosition (
windowPosition.x + cursorPosition.x,
windowPosition.y + cursorPosition.y - style.ItemSpacing.y
);
for (int i = 0; i < this->Images->size(); i++) {
ImGui::Image((void*)(intptr_t)this->Images->at(i).texture, ImVec2(100 * temp_percentage, 100 * temp_percentage));
ImGui::SameLine();
ImGui::Selectable(this->Images->at(i).name.c_str());
if (ImGui::IsItemActive() && ImGui::IsMouseDragging(0))
{
int n_next = floorf((ImGui::GetMousePos().y - itemPosition.y) / itemHeight);
if (n_next != i && n_next >= 0 && n_next < this->Images->size())
{
std::swap(this->Images->at(i), this->Images->at(n_next));
*this->CurrentImage = this->Images->front();
centerImage();
}
}
ImGui::Separator();
}
There is also another problem in your code, if there are multiple items with the same name, ImGui::IsItemActive() will return true for all of them if one is actived.
You can fix this easily by adding ##some_unique_string after the name, for example ImGui::Selectable("Image#image_1") will just display Image.

What's the fastest way to convert a 2D L-system to lines

I'm building a 3D graphics engine, and I want to draw 2D L-systems. But I noticed that this gets quite slow, once you increase the number of iterations. I'm searching a way to rapidly expand my L-system into a vector<Line>, with Line a class containing 2 points. this is my current code:
// LParser::LSystem2D contains the L-system (replacement rules, angle increase, etc..)
// the turtle is a class I use to track the current angle and position as I generate lines
// Lines2D is a std::list of Lines (with lines a class containing 2 points and a color)
void expand(char c, const LParser::LSystem2D &ls2D, Turtle &T, Lines2D &L2D, const Color &line_color, int max_depth,
int depth = 0) {
const std::string str = ls2D.get_replacement(c);
for (const auto &character: str) {
if (character == '+' || character == '-') {
T.angle += (-((character == '-') - 0.5) * 2) * ls2D.get_angle(); // adds or subtracts the angle
continue;
} else if (character == '(') {
T.return_pos.push({T.pos, T.angle}); // if a bracket is opened the current position and angle is stored
continue;
} else if (character == ')') {
T.pos = T.return_pos.top().first; // if a bracket is closed we return to the stored position and angle
T.angle = T.return_pos.top().second;
T.return_pos.pop();
continue;
} else if (max_depth > depth + 1) {
expand(character, ls2D, T, L2D, line_color, max_depth, depth + 1); // recursive call
} else {
// max depth is reached, we add the line to Lines2D
L2D.emplace_back(Line2D(
{T.pos, {T.pos.x + cos(toRadians(T.angle)), T.pos.y + sin(toRadians(T.angle))}, line_color}));
T.pos = {T.pos.x + cos(toRadians(T.angle)), T.pos.y + sin(toRadians(T.angle))};
};
}
}
Lines2D gen_lines(const LParser::LSystem2D &ls2D, const Color &line_color) {
std::string init = ls2D.get_initiator();
Lines2D L2D;
Turtle T;
T.angle = ls2D.get_starting_angle();
for (const auto &c:init) {
if (c == '+' || c == '-') {
T.angle += (-((c == '-') - 0.5) * 2) * ls2D.get_angle();
continue;
} else if (c == '(') {
T.return_pos.push({T.pos, T.angle});
continue;
} else if (c == ')') {
T.pos = T.return_pos.top().first;
T.angle = T.return_pos.top().second;
T.return_pos.pop();
continue;
}
expand(c, ls2D, T, L2D, line_color, ls2D.get_nr_iterations());
}
return L2D;
}
Alphabet = {L, R, F}
Draw = {
L -> 1,
R -> 1,
F -> 1
}
Rules = {
L -> "+RF-LFL-FR+",
R -> "-LF+RFR+FL-",
F -> "F"
}
Initiator = "L"
Angle = 90
StartingAngle = 0
Iterations = 4
L-system example
I couldn't think of any way to increase performance (significantly). I though about multihtreading but you would need to now your position at the beginning of every thread, but then you would need to expand al the previous character.
Is there a more efficient algorithm to do this task? Or a way to implement this so I could use multithreading?
EDIT: I've looked into the answers and this is what I came up with, this increased performance, but one drawback is that my program will use more ram(and I'm limited to 2GB, which is alot but still.) One solution is using a queue, but this decreases performance.
Lines2D LSystem2DParser::generateLines() {
Lines2D lines;
drawing = l_system2d.get_initiator();
Timer T;
expand();
T.endTimer("end of expand: ");
Timer T2;
lines = convert();
T2.endTimer("end of convert: ");
return lines;
}
void LSystem2DParser::expand() {
if (depth >= max_depth) {
return;
}
std::string expansion;
for (char c : drawing) {
switch (c) {
case '+':
case '-':
case '(':
case ')':
expansion += c;
break;
default:
expansion += replacement_rules[c];
break;
}
}
drawing = expansion;
depth++;
expand();
}
Lines2D LSystem2DParser::convert() {
Lines2D lines;
double current_angle = toRadians(l_system2d.get_starting_angle());
double x = 0, y = 0, xinc = 0, yinc = 0;
std::stack<std::array<double, 3>> last_pos;
for (char c: drawing){
switch (c) {
case('+'):
current_angle += angle;
xinc = cos(current_angle);
yinc = sin(current_angle);
break;
case ('-'):
xinc = cos(current_angle);
yinc = sin(current_angle);
break;
case ('('):
last_pos.push({x, y, current_angle});
break;
case (')'):
x = last_pos.top()[0];
y = last_pos.top()[1];
current_angle = last_pos.top()[2];
last_pos.pop();
break;
default:
lines.emplace_back(Line2D(Point2D(x,y), Point2D(x+xinc, y+yinc), line_color));
x += xinc;
y += yinc;
break;
}
}
return Lines2D();
}
EDIT 2:
It's still slow, in comparison to the code posted below
EDIT 3: https://github.com/Robin-Dillen/3DEngine all the code
EDIT 4: having a weird bug with a loop not ending
for (std::_List_const_iterator<Point2D> point = ps.begin(); point != ps.end(); point++) {
std::_List_const_iterator<Point2D> point2 = point++;
img.draw_line(roundToInt(point->x * d + dx), roundToInt(point->y * d + dy), roundToInt(point2->x * d + dx),
roundToInt(point2->y * d + dy), line_color.convert());
}
I have implemented a Lsystem to generate and draw a Sierpinski ( https://en.wikipedia.org/wiki/L-system#Example_5:_Sierpinski_triangle ) This is very similar to what your are doing. I have implemented in a straightforward way with no tricks. Here is the result of time profiling the code for an iteration depth of 11.
raven::set::cRunWatch code timing profile
Calls Mean (secs) Total Scope
1 0.249976 0.249976 draw
11 0.0220157 0.242172 grow
grow is the recursive function. It is called 11 times, with a mean execution time of 22 milliseconds.
draw is the function that takes the final string produced and draws it on the screen. This is called once and needs 250 msec.
The conclusion from this is that the recursive function does not require optimization, since 50% of the application time is used by the drawing.
In your question you do not provide time profiling data, nor even what you mean by "quite slow". I would say that if your code takes more than, say, 100 milliseconds to generate ( not draw ) the final string, then you have a problem which is being caused by a poor implementation of the standard algorithm. If, however, the slowness you complain of is dues to the drawing of the lines, then your problem is likely with a poor choice of graphics library - some graphics libraries do even simple things like drawing lines hundreds of time faster than others.
I invite you take a look at my code at
https://github.com/JamesBremner/Lindenmayer/blob/main/main.cpp
If you just want to parse the string and save the lines to a vector, then things go even faster since no graphics library is involved.
raven::set::cRunWatch code timing profile
Calls Mean (secs) Total Scope
11 0.00229241 0.0252165 grow
1 0.0066558 0.0066558 VectorLines
Here is the code
std::vector<std::pair<int,int> >
VectorLines( const std::string& plant )
{
raven::set::cRunWatch aWatcher("VectorLines");
std::vector<std::pair<int,int> > vL;
int x = 10;
int y = 10;
int xinc = 10;
int yinc = 0;
float angle = 0;
for( auto c : plant )
{
switch( c )
{
case 'A':
case 'B':
break;;
case '+':
angle += 1;
xinc = 5 * cos( angle );
yinc = 5 * sin( angle );
break;
case '-':
angle -= 1;
xinc = 5 * cos( angle );
yinc = 5 * sin( angle );
break;
}
x += xinc;
y += yinc;
vL.push_back( std::pair<int,int>( x, y ) );
}
return vL;
}
Generally speaking, the first step in optimizing the performance of an application is to profile the code to see where exactly the most time is being spent. Without this step, a lot of effort can be wasted optimizing code that actually has little impact on performance.
However, in this particular case, I would look to simplifying your code so it is easier to see what is going on and so make it easier to interpret the results of performance profiling.
Your recursive function expand could be streamlined by
Moving all those parameters out of the signature. There is no need to place so many copies on the same things on stack!
The first thing a recursive function should do is check if recursion is complete. In this case, check the depth.
The second thing, if further recursion is required, is perform the preparation of the next call. In this case, production of the next string from the current.
Finally, the recursive function can be called.
Below I will post code that implements Lindenmayer's original L-system for modelling the growth of algae. This is much simpler that what you are doing, but hopefully showsthe the method and benefit of re-organizing recursive code into the "standard" style of doing recursion.
Is there a more efficient algorithm to do this task?
I doubt it. I suspect that you could improve your implementation, but it is hard to know without profiling your code.
a way to implement this so I could use multithreading?
Recursive algorithms are not good candidates for multithreading.
Here is simple code implementing a similar recursive algorithm
#include <iostream>
#include <map>
using namespace std;
class cL
{
public:
cL()
: myAlphabet("AB")
{
}
void germinate(
std::map< char, std::string>& rules,
const std::string& axiom,
int generations )
{
myRules = rules;
myMaxDepth = generations;
myDepth = 0;
myPlant = axiom;
grow();
}
private:
std::string myAlphabet;
std::map< char, std::string> myRules;
int myDepth;
int myMaxDepth;
std::string myPlant;
std::string production( const char c )
{
if( (int)myAlphabet.find( c ) < 0 )
throw std::runtime_error(
"production character not in alphabet");
auto it = myRules.find( c );
if( it == myRules.end() )
throw std::runtime_error(
"production missing rule");
return it->second;
}
/// recursive growth
void grow()
{
// check for completion
if( myDepth == myMaxDepth )
{
std::cout << myPlant << "\n";
return;
}
// produce the next growth spurt
std::string next;
for( auto c : myPlant )
{
next += production( c );
}
myPlant = next;
// recurse
myDepth++;
grow();
}
};
int main()
{
cL L;
std::map< char, std::string> Rules;
Rules.insert(std::make_pair('A',std::string("AB")));
Rules.insert(std::make_pair('B',std::string("A")));
for( int d = 2; d < 10; d++ )
{
L.germinate( Rules, "A", d );
}
return 0;
}
L2D.emplace_back(Line2D(
{T.pos, {T.pos.x + cos(toRadians(T.angle)), T.pos.y + sin(toRadians(T.angle))}, line_color}));
T.pos = {T.pos.x + cos(toRadians(T.angle)), T.pos.y + sin(toRadians(T.angle))};
Without profiling it is hard to know how important this is. However:
Why not store the angle in radians, instead of converting it to radians over and over?
If using radians would be problematical somewhere else, at least do the conversion once and store in local
Would be a good idea to add a Line2D constructor that takes a Turtle reference as a parameter and does its own calculations.
Is the recalculation of T.pos needed? Isn't the recusion now complete?

Collision detection in voxel world

I am kinda stuck with my basic voxel physics right now. It's very, very choppy and I am pretty sure my maths is broken somewhere, but let's see what you have to say:
// SOMEWHERE AT CLASS LEVEL (so not being reinstantiated every frame, but persisted instead!)
glm::vec3 oldPos;
// ACTUAL IMPL
glm::vec3 distanceToGravityCenter =
this->entity->getPosition() -
((this->entity->getPosition() - gravityCenter) * 0.005d); // TODO multiply by time
if (!entity->grounded) {
glm::vec3 entityPosition = entity->getPosition();
if (getBlock(floorf(entityPosition.x), floorf(entityPosition.y), floorf(entityPosition.z))) {
glm::vec3 dir = entityPosition - oldPos; // Actually no need to normalize as we check for lesser, bigger or equal to 0
std::cout << "falling dir: " << glm::to_string(dir) << std::endl;
// Calculate offset (where to put after hit)
int x = dir.x;
int y = dir.y;
int z = dir.z;
if (dir.x >= 0) {
x = -1;
} else if (dir.x < 0) {
x = 1;
}
if (dir.y >= 0) {
y = -1;
} else if (dir.y < 0) {
y = 1;
}
if (dir.z >= 0) {
z = -1;
} else if (dir.z < 0) {
z = 1;
}
glm::vec3 newPos = oldPos + glm::vec3(x, y, z);
this->entity->setPosition(newPos);
entity->grounded = true; // If some update happens, grounded needs to be changed
} else {
oldPos = entity->getPosition();
this->entity->setPosition(distanceToGravityCenter);
}
}
Basic idea was to determine from which direction entityt would hit the surface and then just position it one "unit" back into that direction. But obviously I am doing something wrong as that will always move entity back to the point where it came from, effectively holding it at the spawn point.
Also this could probably be much easier and I am overthinking it.
As #CompuChip already pointed out, your ifs could be further simplified.
But what is more important is one logical issue that would explain the "choppiness" you describe (Sadly you did not provide any footage, so this is my best guess)
From the code you posted:
First you check if entity is grounded. If so you continue with checking if there is a collision and lastly, if there is not, you set the position.
You have to invert that a bit.
Save old position
Check if grounded
Set the position already to the new one!
Do collision detection
Reset to old position IF you registered a collision!
So basically:
glm::vec3 distanceToGravityCenter =
this->entity->getPosition() -
((this->entity->getPosition() - gravityCenter) * 0.005d); // TODO multiply by time
oldPos = entity->getPosition(); // 1.
if (!entity->grounded) { // 2.
this->fallingStar->setPosition(distanceToGravityPoint); // 3
glm::vec3 entityPosition = entity->getPosition();
if (getBlock(floorf(entityPosition.x), floorf(entityPosition.y), floorf(entityPosition.z))) { // 4, 5
this->entity->setPosition(oldPos);
entity->grounded = true; // If some update happens, grounded needs to be changed
}
}
This should get you started :)
I want to elaborate a bit more:
If you check for collision first and then set position you create an "infinite loop" upon first collision/hit as you collide, then if there is a collision (which there is) you set back to the old position. Basically just mathematic inaccuracy will make you move, as on every check you are set back to the old position.
Consider the if-statements for one of your coordinates:
if (dir.x >= 0) {
x = -1;
}
if (dir.x < 0) {
x = 1;
}
Suppose that dir.x < 0. Then you will skip the first if, enter the second, and x will be set to 1.
If dir.x >= 0, you will enter the first if and x will be set to -1. Now x < 0 is true, so you will enter the second if as well, and x gets set to 1 again.
Probably what you want is to either set x to 1 or to -1, depending on dir.x. You should only execute the second if when the first one was not entered, so you need an else if:
if (dir.x >= 0) {
x = -1;
} else if (dir.x < 0) {
x = 1;
}
which can be condensed, if you so please, into
x = (dir.x >= 0) ? -1 : 1;

Player Bullets going the wrong way and Enemy bullets not firing

Bit of a major predicament as the largest aspect of my game seems to be non-functioning.
I'm making a 3D space invaders game and I'm trying to get the player(and enemies) to fire bullets at each other. But neither seem to be doing so.
I have an attack manager for both that handles their code.
/**
* Prepares a bullet for firing if the current time is greater than
* the last fired time + the firing delay. This purpose of this is to
* put a delay between each bullet as it is fired. A bullet is only
* 'readied' for firing if it is available, i.e. it's alive property
* is set to false.
*
*/
void PlayerAttackManager::fireBullet() {
if ( mCurrentTime > ( mLastFiredTime + sFIRE_DELAY ) ) {
// Find the next available bullet (if one exists)
std::vector<Bullet>::iterator end = mBullets.end();
std::vector<Bullet>::iterator curr = mBullets.begin();
for ( ; curr != end && (*curr).alive() == true; ++curr );
// If there is a bullet available.
if ( curr != end ) {
// Move bullet to firing location and set alive.
Vector3 bulletPos = mSceneMgr->getSceneNode( "PlayerParentNode" )->getPosition();
Vector3 bulletDir = mSceneMgr->getSceneNode( "PlayerParentNode" )->getOrientation() * Vector3::UNIT_Z;
// Pass the initial screen position and direction of travel for the bullet.
(*curr).reset( bulletPos, bulletDir );
}
mLastFiredTime = mCurrentTime;
}
}
The above method is called every time the space bar is pressed but it sends one enemy bullet from the player's position moving downwards and another from above the player moving downwards.
The enemies should attack(one every second) which is called for the EnemyAttackManager to fire from the main.cpp frameRenderingQueued method
The EnemyAttackManager's update is as follows:
void EnemyAttackManager::update(Real const & timeSinceLastFrame, string name)
{
mName = name;
std::vector<Bullet>::iterator end = mBullets.end();
std::vector<Bullet>::iterator curr = mBullets.begin();
for ( ; curr != end && (*curr).alive() == true; ++curr ) {
if ( curr != end ) {
// Move bullet to firing location and set alive.
Vector3 bulletPos = mSceneMgr->getSceneNode( mName + "Node" )->getPosition();
Vector3 bulletDir =mSceneMgr->getSceneNode(mName+ "Node" )->getOrientation() * Vector3::UNIT_Z;
// Pass the initial screen position and direction of travel for the bullet.
(*curr).reset( bulletPos, bulletDir );
(*curr).update(timeSinceLastFrame);
}
}
// Add time since last frame.
mCurrentTime += timeSinceLastFrame;
}
And the bullet's update and reset functions are these respectively:
void Bullet::update( Real const & timeSinceLastFrame ) {
mTtl -= timeSinceLastFrame;
if ( mTtl <= 0 ) {
mNode->setVisible( false );
}
else {
Enemy* enem = new Enemy();
mNode->translate( sMOVE * mDir * timeSinceLastFrame, Node::TS_LOCAL );
// Retrieve a list of possible enemies.
std::vector<Enemy *> enemyVec = enem->getEnemies();
std::vector<Enemy *>::iterator curr = enemyVec.begin();
std::vector<Enemy *>::iterator end = enemyVec.end();
// Check if the ray intersected any of the enemies
for ( ; curr != end && !CollisionManager::instance()->rayIntersects( mNode->getPosition(), 0, (*curr)->meshName(), mDir )
; ++curr );
if ( curr != end ) { // if bullet has collided with an enemy.
//(*curr)->applyDamage();
mTtl = 0;
//JetPack3D::score += 10;
mNode->setVisible( false );
}
//Check the current bullet pos vs. player
}
}
void Bullet::reset( Vector3 const & bulletPos, Vector3 const & bulletDir ) {
mNode->setPosition( bulletPos );
mNode->setVisible( true );
mTtl = sLIFE_TIME;
mDir = bulletDir;
// Need to reverse x and z components so bullet fires forward into the scene.
//now need it to fire right?
mDir.z *= -1;
mDir.x *= -1;
}
So my question is: How do I make the enemies and the player fire bullets(enemies firing at all and player in the right direction)

OpenGL3 SFML 2.0rc FPS-style camera - shaky mouse movement

I'm trying to make a first person shooter style camera for my project. Everything looks smooth if I'm moving forward or back, strafing left or right, or going diagonal... The problem is when I look around with the mouse while moving, the movements get really jittery. It is most prominent when I am strafing and turning with the mouse at the same time.
I'm convinced my problem is similar to this: http://en.sfml-dev.org/forums/index.php?topic=4833.msg31550#msg31550 , however I am using Gentoo Linux, not OSX.
It's quite likely that it's not SFML's fault though, and that I did something wrong, so I would like to get some feedback on my event handling code to see if there's a better way to get smooth mouse movements.
In case you don't want to read through the link I posted, the short of what I think is happening is the mouse movement velocity is being lost every frame when I set the mouse's position back to the center of the screen, which causes a quick visible jerk on screen. This is my going theory as I've tried changing other stuff around for 3 days now and nothing I do makes it less jerky. So I would like to know if anyone has a better way of handling the mouse movements, or whether you think the problem lies elsewhere.
An important note is I have enabled vsync which made a lot of other jitteriness and tearing go away, and I tried using a hard framerate limit like sf::Window::setFramerateLimit(60), but that didn't help at all.
Here is the event handler (which uses the SFML 2.0 realtime interfaces instead of an event loop), you can probably ignore the part related to jumping:
void Test_World::handle_events(float& time)
{
// camera stuff
_mouse_x_pos = sf::Mouse::getPosition(*_window).x;
_mouse_y_pos = sf::Mouse::getPosition(*_window).y;
std::cout << "mouse x: " << _mouse_x_pos << std::endl;
std::cout << "mouse y: " << _mouse_y_pos << std::endl;
_horizontal_angle += time * _mouse_speed * float(_resolution_width/2 - _mouse_x_pos);
_vertical_angle += time * _mouse_speed * float(_resolution_height/2 - _mouse_y_pos);
// clamp rotation angle between 0 - 2*PI
if (_horizontal_angle > 3.14f*2) _horizontal_angle = 0;
if (_horizontal_angle < 0) _horizontal_angle = 3.14f*2;
// clamp camera up/down values so we can't go upside down
if (_vertical_angle >= 3.14f/2.0f) _vertical_angle = 3.14f/2.0f;
if (_vertical_angle <= -3.14f/2.0f) _vertical_angle = -3.14f/2.0f;
std::cout << "horiz angle: " << _horizontal_angle << std::endl;
std::cout << "vert angle: " << _vertical_angle << std::endl;
_direction = glm::vec3( cos(_vertical_angle) * sin(_horizontal_angle),
sin(_vertical_angle),
cos(_vertical_angle) * cos(_horizontal_angle) );
_right = glm::vec3( sin(_horizontal_angle - 3.14f/2.0f),
0,
cos(_horizontal_angle - 3.14f/2.0f) );
_up = glm::cross( _right, _direction );
// keyboard: left, right, up, down
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) || sf::Keyboard::isKeyPressed(sf::Keyboard::A))
{
if (_jumping)
{
_position -= _right * time * _speed * ((_jump_speed/2) + 0.1f);
}
else
{
_position -= _right * time * _speed;
}
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) || sf::Keyboard::isKeyPressed(sf::Keyboard::D))
{
if (_jumping)
{
_position += _right * time * _speed * ((_jump_speed/2) + 0.1f);
}
else
{
_position += _right * time * _speed;
}
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up) || sf::Keyboard::isKeyPressed(sf::Keyboard::W))
{
glm::vec3 old_direction(_direction);
_direction.y = 0;
if (_jumping)
{
_position += _direction * time * _speed * ((_jump_speed/2) + 0.1f);
}
else
{
_position += _direction * time * _speed;
}
_direction = old_direction;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down) || sf::Keyboard::isKeyPressed(sf::Keyboard::S))
{
glm::vec3 old_direction(_direction);
_direction.y = 0;
if (_jumping)
_position -= _direction * time * _speed * ((_jump_speed/2) + 0.1f);
else
_position -= _direction * time * _speed;
_direction = old_direction;
}
// keyboard: jump
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl))
{
// check if standing on something
if (_standing)
{
_standing = false;
_jumping = true;
}
}
// apply gravity if off the ground
if (_position.y > _main_character_height && !_jumping)
_position.y -= time * _speed * _global_gravity;
// if started jumping
else if (_position.y < _main_character_height + _jump_height - 3.0f && _jumping)
{
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl))
{
_position.y += time * _speed * _jump_speed;
}
else // if stopped jumping
{
_position += _direction * time * (_speed/(_jump_hang_time*2));
_jumping = false;
}
}
// if near the highest part of the jump
else if (_position.y <= _main_character_height + _jump_height && _jumping)
{
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl))
{
_position.y += time * _speed * (_jump_speed/_jump_hang_time);
}
else // if stopped jumping
{
_position += _direction * time * (_speed/(_jump_hang_time*2));
_jumping = false;
}
}
// if reached the highest part of the jump
else if (_position.y >= _main_character_height + _jump_height)
{
_position += _direction * time * (_speed/_jump_hang_time);
_jumping = false;
}
else if (_position.y <= _main_character_height)
{
_standing = true;
}
sf::Mouse::setPosition(_middle_of_window, *_window);
}
After the events, I am changing my _view_matrix like this:
_view_matrix = glm::lookAt( _position, _position+_direction, _up );
Then, I recalculate my _modelview_matrix and _modelviewprojection_matrix:
_modelview_matrix = _view_matrix * _model_matrix;
_modelviewprojection_matrix = _projection_matrix * _modelview_matrix;
After that I finally send the matrices to my shaders and draw the scene.
I am open to any sage wisdom/advice with regards to OpenGL3, SFML 2.0, and/or FPS-style camera handling, and please let me know if it would help to include more code (if you think the problem isn't in the event handling, for example). Thanks in advance for the help!
Edit: I still haven't solved this problem, and FYI it doesn't look like the framerate is dropping at all during the shaky movements...
If I was you I would hide the mouse using this tutorial and then only move the mouseposition when it got to the edge of the screen moving it to the opposite side so it can continue in that direction un-impeded. May not be the best solution, but I'm pretty sure it'll fix your issue.
... another idea is to do some remembering and reckoning as to what they are doing and not use the position data itself, but simply use the position data to guide the reckoning (i.e. use physics to control it instead of the raw data).
Just my thoughts, hope they help^^