I'm coding a simple roguelike game in C++ using SDL library, and I have some problems moving my character on the screen. Each time a frame needs to be rendered, I update the position of the sprite using the update() function, which does nothing if the player is standing still. To issue the movement command, and thus starting the animation, I use the step() function called only once per each player movement from one tile to another. Upon receiving the "up" command, the game behaves fine and the character moves smoothly in one second to the new position. However, when the "down" command is given, he moves at about half the speed, and obviously after one second has passed, he is instantly "teleported" to the final position, with a sudden flicker. The code for the movement is basically identical, but for the fact that in one case the delta movement is summed to the y position, in the other case is subtracted. Maybe the fact that the position is an integer and the delta is a double is causing problems? Does sum and subract behave differently (maybe different rounding)? Here is the relevant code (sorry for the length):
void Player::step(Player::Direction dir)
{
if(m_status != STANDING) // no animation while standing
return;
switch(dir)
{
case UP:
if(m_currMap->tileAt(m_xPos, m_yPos - m_currMap->tileHeight())->m_type == Tile::FLOOR)
{
// if next tile is not a wall, set up animation
m_status = WALKING_UP;
m_yDelta = m_currMap->tileHeight(); // sprite have to move by a tile
m_yVel = m_currMap->tileHeight() / 1000.0f; // in one second
m_yNext = m_yPos - m_currMap->tileHeight(); // store final destination
}
break;
case DOWN:
if(m_currMap->tileAt(m_xPos, m_yPos + m_currMap->tileHeight())->m_type == Tile::FLOOR)
{
m_status = WALKING_DOWN;
m_yDelta = m_currMap->tileHeight();
m_yVel = m_currMap->tileHeight() / 1000.0f;
m_yNext = m_yPos + m_currMap->tileHeight();
}
break;
//...
default:
break;
}
m_animTimer = SDL_GetTicks();
}
void Player::update()
{
m_animTimer = SDL_GetTicks() - m_animTimer; // get the ms passed since last update
switch(m_status)
{
case WALKING_UP:
m_yPos -= m_yVel * m_animTimer; // update position
m_yDelta -= m_yVel * m_animTimer; // update the remaining space
break;
case WALKING_DOWN:
m_yPos += m_yVel * m_animTimer;
m_yDelta -= m_yVel * m_animTimer;
break;
//...
default:
break;
}
if(m_xDelta <= 0 && m_yDelta <= 0) // if i'm done moving
{
m_xPos = m_xNext; // adjust position
m_yPos = m_yNext;
m_status = STANDING; // and stop
}
else
m_animTimer = SDL_GetTicks(); // else update timer
}
EDIT: I removed some variables and only left the elapsed time, the speed and the final position. Now it moves without flickering, but the down and right movements are visibly slower than the up and left ones. Still wonder why...
EDIT 2: Ok, I figured out why this is happening. As I supposed in the first place, there is a different rounding from double to integer when it comes to sum and subtraction. If I perform a cast like this:
m_xPos += (int)(m_xVel * m_animTimer);
the animation speed is the same, and the problem is solved.
Consider the following:
#include <iostream>
void main()
{
int a = 1, b = 1;
a += 0.1f;
b -= 0.1f;
std::cout << a << std::endl;
std::cout << b << std::endl;
}
During the implicit conversion of float to int when a and b are assigned, everything past the decimal point will be truncated and not rounded. The result of this program is:
1
0
You've said that m_yPos is an integer and m_yVel is a double. Consider what happens in Player::update if the result of m_yVel * m_animTimer is less than 1. In the UP case, the result will be that your sprite moves down one pixel, but in the DOWN case, your sprite won't move at all, because if you add less than one to an integer, nothing will happen. Try storing your positions as doubles and only converting them to integers when you need to pass them to the drawing functions.
A trick you can do to ensure rounding instead of truncation during conversion is to always add 0.5 to the floating point value during assignment to an integer.
For example:
double d1 = 1.2;
double d2 = 1.6;
int x = d1 + 0.5;
int y = d2 + 0.5;
In this case, x will become 1, while y will become 2.
I'd rather not do incremental calculations. This is simpler, will give correct results even if you go back in time, doesn't suffer from lost of precision, and will be just as fast, if not faster, on modern hardware:
void Player::step(Player::Direction dir)
{
// ...
case UP:
if(m_currMap->tileAt(m_xPos, m_yPos - m_currMap->tileHeight())->m_type == Tile::FLOOR)
{
// if next tile is not a wall, set up animation
m_status = WALKING_UP;
m_yStart = m_yPos;
m_yDelta = -m_currMap->tileHeight(); // sprite have to move by a tile
m_tStart = SDL_GetTicks(); // Started now
m_tDelta = 1000.0f; // in one second
}
break;
case DOWN:
if(m_currMap->tileAt(m_xPos, m_yPos + m_currMap->tileHeight())->m_type == Tile::FLOOR)
{
m_status = WALKING_DOWN;
m_yStart = m_yPos;
m_yDelta = m_currMap->tileHeight();
m_tStart = SDL_GetTicks(); // Started now
m_tDelta = 1000.0f; // in one second
}
break;
// ...
}
void Player::update()
{
auto tDelta = SDL_GetTicks() - m_tStart;
switch(m_status)
{
case WALKING_UP:
case WALKING_DOWN:
m_yPos = m_yStart + m_yDelta*tDelta/m_tDelta; // update position
break;
default:
break;
}
if(tDelta >= m_tDelta) // if i'm done moving
{
m_xPos = m_xStart + m_xDelta; // adjust position
m_yPos = m_yStart + m_yDelta;
m_status = STANDING; // and stop
}
}
Related
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?
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;
I'm using delta time so I can make my program frame rate independent.
However I can't get the jump height it be the same, the character always jumps higher on a lower frame rate.
Variables:
const float gravity = 0.0000000014f;
const float jumpVel = 0.00000046f;
const float terminalVel = 0.05f;
bool readyToJump = false;
float verticalVel = 0.00f;
Logic code:
if(input.isKeyDown(sf::Keyboard::Space)){
if(readyToJump){
verticalVel = -jumpVel * delta;
readyToJump = false;
}
}
verticalVel += gravity * delta;
y += verticalVel * delta;
I'm sure the delta time is correct because the character moves horizontally fine.
How do I get my character to jump the same no matter the frame rate?
The formula for calculating the new position is:
position = initial_position + velocity * time
Taking into account gravity which reduces the velocity according to the function:
velocity = initial_velocity + (gravity^2 * time)
NOTE: gravity in this case is not the same as the gravity.
The final formula then becomes:
position = initial_position + (initial_velocity + (gravity^2 * time) * time
As you see from the above equation, initial_position and initial_velocity is not affected by time. But in your case you actually set the initial velocity equal to -jumpVelocity * delta.
The lower the frame rate, the larger the value of delta will be, and therefore the character will jump higher. The solution is to change
if(readyToJump){
verticalVel = -jumpVel * delta;
readyToJump = false;
}
to
if(readyToJump){
verticalVel = -jumpVel;
readyToJump = false;
}
EDIT:
The above should give a pretty good estimation, but it is not entirely correct. Assuming that p(t) is the position (in this case height) after time t, then the velocity given by v(t) = p'(t)', and the acceleration is given bya(t) = v'(t) = p''(t)`. Since we know that the acceleration is constant; ie gravity, we get the following:
a(t) = g
v(t) = v0 + g*t
p(t) = p0 + v0*t + 1/2*g*t^2
If we now calculate p(t+delta)-p(t), ie the change in position from one instance in time to another we get the following:
p(t+delta)-p(t) = p0 + v0*(t+delta) + 1/2*g*(t+delta)^2 - (p0 + v0*t + 1/2*g*t^2)
= v0*delta + 1/2*g*delta^2 + g*delta*t
The original code does not take into account the squaring of delta or the extra term g*delta*t*. A more accurate approach would be to store the increase in delta and then use the formula for p(t) given above.
Sample code:
const float gravity = 0.0000000014f;
const float jumpVel = 0.00000046f;
const float limit = ...; // limit for when to stop jumping
bool isJumping = false;
float jumpTime;
if(input.isKeyDown(sf::Keyboard::Space)){
if(!isJumping){
jumpTime = 0;
isJumping = true;
}
else {
jumpTime += delta;
y = -jumpVel*jumpTime + gravity*sqr(jumpTime);
// stop jump
if(y<=0.0f) {
y = 0.0f;
isJumping = false;
}
}
}
NOTE: I have not compiled or tested the code above.
By "delta time" do you mean variable time steps? As in, at every frame, you compute a time step that can be completely different from the previous?
If so, DON'T.
Read this: http://gafferongames.com/game-physics/fix-your-timestep/
TL;DR: use fixed time steps for the internal state; interpolate frames if needed.
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^^
I've been working on detecting collision between to object in my game. Right now everything tavels vertically, but would like to keep the option for other movement open. It's classic 2d vertical space shooter.
Right now I loop through every object, checking for collisions:
for(std::list<Object*>::iterator iter = mObjectList.begin(); iter != mObjectList.end();) {
Object *m = (*iter);
for(std::list<Object*>::iterator innerIter = ++iter; innerIter != mObjectList.end(); innerIter++ ) {
Object *s = (*innerIter);
if(m->getType() == s->getType()) {
break;
}
if(m->checkCollision(s)) {
m->onCollision(s);
s->onCollision(m);
}
}
}
Here is how I check for a collision:
bool checkCollision(Object *other) {
float radius = mDiameter / 2.f;
float theirRadius = other->getDiameter() / 2.f;
Vector<float> ourMidPoint = getAbsoluteMidPoint();
Vector<float> theirMidPoint = other->getAbsoluteMidPoint();
// If the other object is in between our path on the y axis
if(std::min(getAbsoluteMidPoint().y - radius, getPreviousAbsoluteMidPoint().y - radius) <= theirMidPoint.y &&
theirMidPoint.y <= std::max(getAbsoluteMidPoint().y + radius, getPreviousAbsoluteMidPoint().y + radius)) {
// Get the distance between the midpoints on the x axis
float xd = abs(ourMidPoint.x - theirMidPoint.x);
// If the distance between the two midpoints
// is greater than both of their radii together
// then they are too far away to collide
if(xd > radius+theirRadius) {
return false;
} else {
return true;
}
}
return false;
}
The problem is it will randomly detect collisions correctly, but other times does not detect it at all. It's not the if statement breaking away from the object loop because the objects do have different types. The closer the object is to the top of the screen, the better chance it has of collision getting detected correctly. Closer to the bottom of the screen, the less chance it has of getting detected correctly or even at all. However, these situations don't always occur. The diameter for the objects are massive (10 and 20) to see if that was the problem, but it doesn't help much at all.
EDIT - Updated Code
bool checkCollision(Object *other) {
float radius = mDiameter / 2.f;
float theirRadius = other->getDiameter() / 2.f;
Vector<float> ourMidPoint = getAbsoluteMidPoint();
Vector<float> theirMidPoint = other->getAbsoluteMidPoint();
// Find the distance between the two points from the center of the object
float a = theirMidPoint.x - ourMidPoint.x;
float b = theirMidPoint.y - ourMidPoint.y;
// Find the hypotenues
double c = (a*a)+(b*b);
double radii = pow(radius+theirRadius, 2.f);
// If the distance between the points is less than or equal to the radius
// then the circles intersect
if(c <= radii*radii) {
return true;
} else {
return false;
}
}
Two circular objects collide when the distance between their centers is small enough. You can use the following code to check this:
double distanceSquared =
pow(ourMidPoint.x - theirMidPoint.x, 2.0) +
pow(ourMidPoint.x - theirMidPoint.x, 2.0);
bool haveCollided = (distanceSquared <= pow(radius + theirRadius, 2.0));
In order to check whether there was a collision between two points in time, you can check for collision at the start of the time interval and at the end of it; however, if the objects move very fast, the collision detection can fail (i guess you have encountered this problem for falling objects that have the fastest speed at the bottom of the screen).
The following might make the collision detection more reliable (though still not perfect). Suppose the objects move with constant speed; then, their position is a linear function of time:
our_x(t) = our_x0 + our_vx * t;
our_y(t) = our_y0 + our_vy * t;
their_x(t) = their_x0 + their_vx * t;
their_y(t) = their_y0 + their_vy * t;
Now you can define the (squared) distance between them as a quadratic function of time. Find at which time it assumes its minimum value (i.e. its derivative is 0); if this time belongs to current time interval, calculate the minimum value and check it for collision.
This must be enough to detect collisions almost perfectly; if your application works heavily with free-falling objects, you might want to refine the movement functions to be quadratic:
our_x(t) = our_x0 + our_v0x * t;
our_y(t) = our_y0 + our_v0y * t + g/2 * t^2;
This logic is wrong:
if(std::min(getAbsoluteMidPoint().y - radius, getPreviousAbsoluteMidPoint().y - radius) <= theirMidPoint.y &&
theirMidPoint.y <= std::max(getAbsoluteMidPoint().y + radius, getPreviousAbsoluteMidPoint().y + radius))
{
// then a collision is possible, check x
}
(The logic inside the braces is wrong too, but that should produce false positives, not false negatives.) Checking whether a collision has occurred during a time interval can be tricky; I'd suggest checking for a collision at the present time, and getting that to work first. When you check for a collision (now) you can't check x and y independently, you must look at the distance between the object centers.
EDIT:
The edited code is still not quite right.
// Find the hypotenues
double c = (a*a)+(b*b); // actual hypotenuse squared
double radii = pow(radius+theirRadius, 2.f); // critical hypotenuse squared
if(c <= radii*radii) { // now you compare a distance^2 to a distance^4
return true; // collision
}
It should be either this:
double c2 = (a*a)+(b*b); // actual hypotenuse squared
double r2 = pow(radius+theirRadius, 2.f); // critical hypotenuse squared
if(c2 <= r2) {
return true; // collision
}
or this:
double c2 = (a*a)+(b*b); // actual hypotenuse squared
double c = pow(c2, 0.5); // actual hypotenuse
double r = radius + theirRadius; // critical hypotenuse
if(c <= r) {
return true; // collision
}
Your inner loop needs to start at mObjectList.begin() instead of iter.
The inner loop needs to iterate over the entire list otherwise you miss collision candidates the further you progress in the outer loop.