Alright, so I am trying to use the analog stick on a gamepad to move the desktop mouse cursor around. The problem is that I need to be able to get the same smoothness as Attempt 2, but without using an absolute mouse position. The cursor needs to be moved relative to its current position. The reason for this is that many applications (mainly video games) also set the mouse to an absolute position. This causes the application and attempt 2 to fight one another for control of the mouse.
Attempt 1 (relative)
// keep updating the controller state and mouse position
while (true)
{
// getState returns a 2d float vector with normalized values from [-1, 1]
// The cursor is being set relative to its current position here.
SetCursorPosition(GetCursorPosition() + analogStick->getState());
}
This solution works, but suffers from a rounding issue because GetCursorPosition and SetCursorPosition are based on integers. As a result, small movements are not registered because smaller analog movements will always get truncated. Visually speaking, small movements on the analog stick will only move the mouse along the X or Y axis even if you are try to make a diagonal movement.
Attempt 2 (absolute)
vec2 mouseTargetPosition = GetCursorPosition(); // global cursor position
while (true)
{
mouseTargetPosition += leftStick->getState();
vec2 newPosition = lerp(GetCursorPos(), mouseTargetPosition, 0.8f);
SetCursorPos(round(newPosition.x), round(newPosition.y));
}
This solution works great, the mouse responds to the smallest of movements and moves very naturally as a result of interpolating the accumulated analog movements. But, it sets the mouse to an absolute position (mouseTargetPosition), making this solution a deal breaker.
This is an awfully specific question in the first place I suppose. After fooling around with several configurations this is the one that feels smoothest and works well. It's basically magic considering because it can add native feeling analog support for games and model viewers that don't have it :)
vec2 mouseTargetPos, mouseCurrentPos, change;
while (true)
{
// Their actual position doesn't matter so much as how the 'current' vector approaches
// the 'target vector'
mouseTargetPos += primary->state;
mouseCurrentPos = util::lerp(mouseCurrentPos, mouseTargetPos, 0.75f);
change = mouseTargetPos - mouseCurrentPos;
// movement was too small to be recognized, so we accumulate it
if (fabs(change.x) < 0.5f) accumulator.x += change.x;
if (fabs(change.y) < 0.5f) accumulator.y += change.y;
// If the value is too small to be recognized ( < 0.5 ) then the position will remain the same
SetCursorPos(GetCursorPos() + change);
SetCursorPos(GetCursorPos() + accumulator);
// once the accumulator has been used, reset it for the next accumulation.
if (fabs(accumulator.x) >= 0.5f) accumulator.x = 0;
if (fabs(accumulator.y) >= 0.5f) accumulator.y = 0;
}
Related
UPDATE:
I couldn't figure out the exact problem, however I made a fix that's good enough for me: Whenever the player's X value is less then half the screen's width, I just snap the view back to the center (up left corner) using sf::View::setCenter().
So I'm working on a recreating of Zelda II to help learn SFML good enough so I can make my own game based off of Zelda II. The issue is the screen scrolling, for some reason, if link walks away from the wall and initiated the camera to follow him, and then move back toward the wall, the camera won't go all the way back to the end of the wall, which occurs on the other wall at the end of the scene/room. This can be done multiple times to keep making the said camera block get further away from the wall. This happens on both sides of the scene, and I have reason to believe it has something to do with me trying to make the game frame independent, here's an included GIF of my issue to help understand:
My camera function:
void Game::camera() {
if (this->player.getVar('x') >= this->WIDTH / 2 and this->player.getVar('x') < this->sceneWidth - this->WIDTH / 2) {
this->view.move(int(this->player.getVar('v') * this->player.dt * this->player.dtM), 0);
}
}
player.getVar() is a temporary function I'm using to get the players x position and x velocity, using the argument 'x' returns the players x position, and 'v' returns the x velocity. WIDTH is equal to 256, and sceneWidth equals 767, which is the image I'm using for the background's width. dt and dtM are variables for the frame independence I mentioned earlier, this is the deceleration:
sf::Clock sclock;
float dt = 0;
float dtM = 60;
int frame = 0;
void updateTime() {
dt = sclock.restart().asSeconds();
frame += 1 * dt * dtM;
}
updateTime() is called every frame, so dt is updated every frame as well. frame is just a frame counter for Link's animations, and isn't relevant to the question. Everything that moves and is rendered on the screen is multiplied by dt and dtM respectively.
There's a clear mismatch between the movement of the player and the one of the camera... You don't show the code to move the player, but if I guess you don't cast to int the movement there, as you are doing on the view.move call. That wouldn't be a problem if you were setting the absolute position of the camera, but as you are constantly moving it, the little offset accumulates each frame, causing your problem.
One possible solution on is to skip the cast, which is unnecessary because sf::View::move accepts float as arguments.
void Game::camera() {
if (this->player.getVar('x') >= this->WIDTH / 2 and this->player.getVar('x') < this->sceneWidth - this->WIDTH / 2) {
this->view.move(this->player.getVar('v') * this->player.dt * this->player.dtM, 0);
}
}
Or even better, not to use view.move but to directly set the position of the camera each frame. Something like:
void Game::camera() {
if (this->player.getVar('x') >= this->WIDTH / 2 and this->player.getVar('x') < this->sceneWidth - this->WIDTH / 2) {
this->view.setCenter(this->player.getVar('x'), this->view.getCenter().y);
}
}
Alright, so I've been trying to figure this out for several years now (no joke, i'll work on it until I can't anymore and move onto another task telling myself i'll come back to it later and it's been a never ending loop), but I think I've finally collected enough information about what's going on that I can explain with enough detail that someone might be able to point me in the right direction.
The issue is that I cannot seem to achieve a smooth camera rotation based off mouse input in this 3d framework/engine I've been working on (hobby project) with c++/glfw/glad/opengl. I've gone through and made sure that within windows mouse acceleration is disabled, within glfw cursor is disabled and GLFW_RAW_MOUSE_MOTION is enabled. There is nothing (that i'm aware of) that should be skewing the mouse input values that are being obtained.
The below is debug output I generated which shows: frame_time|current_mouse_x_position|mouse_delta_for_tick
0.010011: 5803.000000: 9.000000
0.010001: 5810.000000: 7.000000
0.010011: 5819.000000: 9.000000
0.010002: 5836.000000: 17.000000
0.010011: 5845.000000: 9.000000
0.010001: 5854.000000: 9.000000
0.010001: 5861.000000: 7.000000
0.010015: 5876.000000: 15.000000
0.010002: 5884.000000: 8.000000
0.010002: 5894.000000: 10.000000
0.010264: 5902.000000: 8.000000
0.010009: 5919.000000: 17.000000
0.010010: 5928.000000: 9.000000
0.010011: 5935.000000: 7.000000
0.010006: 5943.000000: 8.000000
0.010010: 5958.000000: 15.000000
0.010090: 5965.000000: 7.000000
0.010005: 5971.000000: 6.000000
0.010013: 5979.000000: 8.000000
0.010042: 5994.000000: 15.000000
0.010012: 6001.000000: 7.000000
0.010004: 6009.000000: 8.000000
0.010009: 6016.000000: 7.000000
0.010007: 6033.000000: 17.000000
This data was collected while moving the mouse along the x-axis as consistently as my hand would allow, and you can see in the mouse_delta_for_tick column that every so often the values appear to be double what they should be.
My first thought was that this could be due to the amount of time between when the positions are sampled, but you can see in the first column that the frame_time stays relatively consistent. So I'm uncertain what could be causing these values to be so off.
You can imagine that when using these values to calculate the camera rotation, it becomes stuttery/clicky. Below is how I'm doing the update every tick:
void Client::updateMouseDelta()
{
// this->input updated elsewhere as fast as engine can update it, so in this function it is
// the current mouse position for this tick
this->currentMousePosition.x = this->input.mouseXPos;
this->currentMousePosition.y = this->input.mouseYPos;
if (this->firstMouseInput)
{
this->lastMousePosition = this->currentMousePosition;
this->firstMouseInput = false;
}
//TODO mousedelta calculation and framerate or something causing noisy deltas??????idk????
this->mouseDelta.x = this->currentMousePosition.x - this->lastMousePosition.x;
this->mouseDelta.y = this->lastMousePosition.y - this->currentMousePosition.y;
this->lastMousePosition = this->currentMousePosition;
}
void Client::updatePitchYaw()
{
this->yaw += this->mouseDelta.x * this->mouseSensitivity;
this->pitch += this->mouseDelta.y * this->mouseSensitivity;
// make sure that when pitch is out of bounds, screen doesn't get flipped
if (this->pitch > 89.0f)
this->pitch = 89.0f;
if (this->pitch < -89.0f)
this->pitch = -89.0f;
}
void Client::updateRotation(float frameTime, float renderLerpInterval)
{
this->updateMouseDelta();
this->updatePitchYaw();
this->lookDirection.x = cos(glm::radians(this->yaw)) * cos(glm::radians(this->pitch));
this->lookDirection.y = sin(glm::radians(this->pitch));
this->lookDirection.z = sin(glm::radians(this->yaw)) * cos(glm::radians(this->pitch));
this->lookDirection = glm::normalize(this->lookDirection);
auto cameraPosition = this->ccc->getCapsuleActor()->getInterpolatedTranslation(renderLerpInterval);
this->worldCamera->setPosition(cameraPosition);
this->worldCamera->setLookAt(cameraPosition + this->lookDirection);
}
I've done various things throughout the years to attempt to mask this stutter by smoothing the input, but it's never perfect and always introduces a noticeable latency in mouse movement (moving average filter, converting into a quaternion and using slerp between previous and current rotations, a low pass filter (although that might have been a different unrelated issue now that I think about it)).
Also just to double confirm that this wasn't unrelated to mouse input I implemented a simple rotation test using arrow keys as such (which is buttery smooth):
void Client::updatePitchYaw()
{
if (this->input.keyUp)
this->pitch += 0.75f;
if (this->input.keyDown)
this->pitch -= 0.75f;
if (this->input.keyRight)
this->yaw += 0.75f;
if (this->input.keyLeft)
this->yaw -= 0.75f;
// make sure that when pitch is out of bounds, screen doesn't get flipped
if (this->pitch > 89.0f)
this->pitch = 89.0f;
if (this->pitch < -89.0f)
this->pitch = -89.0f;
}
If you've made it this far THANK YOU, and I suppose my main question is: How can I achieve the sort of buttery smooth responsive camera rotation via mouse input that every other game I've ever played seems to be able to achieve (counter-strike, quake3, half-life, etc)? There MUST be something that I'm missing.
EDIT
Adding main gameloop code and mouse input update code (stripped down version of loop, but all relevant parts should be there) which should make it clear how I am getting the mouse position values:
/*
App::execute() invoked via main(), once invoked, this method controls the
application until it is terminated.
*/
void App::execute()
{
this->fixedLogicTime = 1 / this->config.LOGIC_TICK; // LOGIC_TICK = 120.0
this->currentTime = this->time();
while (true)
{
if (this->shouldClose || this->window->shouldClose())
break;
this->newTime = this->time();
this->frameTime = this->newTime - this->currentTime;
// UPDATE MOUSE INPUT HERE. THIS METHOD CALLS THE Window::setMouse() method
// which is included below this one
this->window->updateInputState();
if (this->frameTime >= (1 / this->config.MAX_RENDER_FPS)) // cap max fps
{
this->currentTime = this->newTime;
if (this->frameTime > 0.25)
this->frameTime = 0.25;
this->accumulator += this->frameTime;
while (this->accumulator >= this->fixedLogicTime)
{
this->activeScene->applyTransformations();
this->activeScene->processSensors();
this->activeScene->innerLoop((float)this->fixedLogicTime); // fixed logic updates
this->activeScene->updateAnimations(this->fixedLogicTime);
this->activeScene->stepPhysics((float)this->fixedLogicTime);
this->activeScene->postPhysics((float)this->fixedLogicTime);
this->accumulator -= this->fixedLogicTime;
}
float renderLerpInterval = (float)(this->accumulator / this->fixedLogicTime);
// THIS METHOD WOULD BE CALLING THE METHODS WHERE THE MOUSE INPUT VALUES ARE USED
// IE, MY PREVIOUSLY POSTED Client::updateRotation() METHOD
this->activeScene->outerLoop((float)this->frameTime, renderLerpInterval); // immediate logic updates
this->gpu->clearBuffers(0.0f, 0.0f, 0.0f, 1.0f);
this->activeScene->draw(renderLerpInterval);
}
}
}
void Window::setMouse()
{
double mXPos;
double mYPos;
glfwGetCursorPos(this->glfwWindow, &mXPos, &mYPos);
this->inputState.mouseXPos = (float)mXPos;
this->inputState.mouseYPos = (float)mYPos;
}
You get smooth rotation with key press, because you add (subtract) a fixed value from the previous/actual one and increase (decrease) the rotation by a fixed amount. Every frame shows you a rotation by a fixed angle (smooth).
If you want to achieve the same result as key presses, use the same method.
Divide the screen into cells (the cell size is determined by your scaling factor, e.g. how many key strokes it needs for 180 degrees, which is equal to the screen width or a mouse motion from left to right). Based on your mouse position or moved distance, calculate the cell your pointer resides within or how many cells it moved, which gives you in return the desired angle for the rotation (by a fixed amount, just like key strokes).
Therefore, small changes of the mouse pointer will have no effect, only if the moved distance is greater than a cell size, then a rotation will occur (moved cells multiplied by a scaling factor).
Well. I finally figured it out, and it is so soul crushingly simple I'm almost embarrassed to post this answer, BUT maybe it will help someone down the road...
It was the mouse :(
I have multiple machines that I work from. Recently I have been using my laptop with this mouse and this is where I started noticing the stutter again. However, before I jumped to the conclusion that it was something code related, I tested the exact same build on my standing desktop setup, which also uses an hp mouse similar to the one above but not exact, and the issue persisted. This acted as my confirmation that something wasn't right since it was happening on both systems.
This evening, thinking it was something performance related I jumped on my primary desktop (5900x, rtx3070, etc), which uses a razer lancehead mouse...and the rotation was perfectly smooth. I then grabbed both of the hp mice and tried each on this system, and wouldn't you know...it was a stuttery mess.
I'm willing to accept this as the problem to the issue, but I had used that hp mouse on my last primary desktop build for years and played countless games and never noticed any of this stutteriness, I mean it's a 1000dpi mouse which should be plenty sensitive enough. I'm wondering if there is something that changed with the windows 10 usb mouse drivers? That's really all I can think of as the razer mouse has it's own proprietary drivers.
This all makes me wonder how many people out there are using these cheap mice and experiencing this, but not knowing that it's an issue.
Your mouse input is incorrect.
Maybe you want to doublecheck that you get more serious values with another program like
<html>
<body>
<form name="Form1">
<textarea type="text" name="posx" />
</textarea>
</form>
<script type="text/javascript">
var start = Date.now();
document.onmousemove = function (event)
{
if (Date.now() - start<10) return;
start = Date.now();
document.Form1.posx.value += start + "\t"+ event.clientX +"\n" ;
}
</script>
</body>
</html>
I want to teach myself some basic physics programming by creating a simple 2d platformer with SDL 2. It seems I'm falling at the first hurdle though, because I can't get movement using both velocity and acceleration per time unit, rather than per frame, to work.
I start by calculating the time per frame in the usual way:
previous_time = current_time;
current_time = SDL_GetTicks();
delta_time = current_time - previous_time;
Then, after the movement flag is set to true by pressing a directional button, this is passed to a function to handle the movement.
//Pass the movement flag and the milliseconds per frame to the right movement function.
if ( player.get_x() <= 740 ) {
player.x_movement_right(delta_time, 1, moving_right);
}
The integer that's passed doesn't do anything yet. Anyway, the function then determines the acceleration based on if the movement flag is set to true, and what the current velocity is:
void Player::x_movement_right(float dt, int direction, bool moving_right) {
dt /= 1000;
if (moving_right == true && _x_velocity <= 200 ) {
_x_acceleration = 50;
}
else if ( moving_right == false && _x_velocity > 0 ) {
_x_acceleration = -50;
}
else {
_x_acceleration = 0;
}
_x_velocity += _x_acceleration * dt;
_x_position += _x_velocity * dt;
}
The same process occurs if the left movement flag is activated, with inverted values of course. Yet after compiling I hit the directional keys and nothing happens.
What I've already tried:
I removed the dt's at the bottom of the movement function. The player avatar moves with incredible speed, since the acceleration is now per frame, rather than per second.
Same thing when I don't divide dt at the beginning of the movement function, since it's now per millisecond rather than per second.
I tried rounding the velocity times dt at the bottom, since I suspected SDL might have trouble calculation positions with floating point numbers rather than integers. Still no movement.
Based on this I suspect it has something to do with the numbers being too small, but I can't quite wrap my head around what the problem is or how to solve it. So, does anyone know what undoubtedly obvious thing I'm missing? Thanks in advance!
There is no way to know with the information shown, but there are several points that may help you:
Are your _x_velocity and the like floating point types? In what units are you measuring distance? It may be that your increment has not enough resolution to be nonzero.
Have you printed the values of each variable or run the program in a debugger?
What do you mean by "SDL might have trouble calculation positions with floating point numbers"? If you are using SDL's basic 2D renderer, you just need to give it the type it needs in whatever units they ask. The conversions are up to you.
Overall, I'd recommend trying to code the simulation outside SDL or graphics in general. Getting acquainted with C++, debugging and floating-point is also a plus.
I've implemented a QwtPlot which scrolls across the screen as data is added in real-time. Based on user input, an image of the plot is occasionally rendered to a file using QwtPlotRenderer. However, because the axis scrolls during normal operation, the QwtScaleDiv tick marks can look a little wonky at render time (they are right-aligned):
Is there some easy way in which I can recalculate the division prior to rendering so that the first label is on the far left and the last one is on the far right?
This isn't as difficult as it looked at first. Bascially, all you need to do is temporarily replace the axisScaleDiv.
auto divX = this->axisScaleDiv(xBottom);
double ub = divX.upperBound();
double lb = divX.lowerBound();
double numTicks = 11.0; // 10 even divisions
// you can create minor/medium ticks if you want to, I didn't.
QList<double> majorTicks;
for (int i = 0; i < numTicks; ++i)
{
majorTicks.push_back(lb + i * ((ub - lb) / (numTicks - 1)));
}
// set the scale to the newly created division
QwtScaleDiv renderDivX(divX.lowerBound(), divX.upperBound(),
QList<double>(), QList<double>(), majorTicks);
this->setAxisScaleDiv(xBottom, renderDivX);
// DO PLOT RENDERING
QwtPlotRender renderer;
renderer.renderDocument(...);
// RESOTRE PREVIOUS STATE
this->setAxisScaleDiv(xBottom, divX);
this->setAxisScaleDiv(yLeft, divY);
// update the axes
this->updateAxes();
I'm drawing an object (say, a cube) in OpenGL that a user can rotate by clicking / dragging the mouse across the window. The cube is drawn like so:
void CubeDrawingArea::redraw()
{
Glib::RefPtr gl_drawable = get_gl_drawable();
gl_drawable->gl_begin(get_gl_context());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
{
glRotated(m_angle, m_rotAxis.x, m_rotAxis.y, m_rotAxis.z);
glCallList(m_cubeID);
}
glPopMatrix();
gl_drawable->swap_buffers();
gl_drawable->gl_end();
}
and rotated with this function:
bool CubeDrawingArea::on_motion_notify_event(GdkEventMotion* motion)
{
if (!m_leftButtonDown)
return true;
_3V cur_pos;
get_trackball_point((int) motion->x, (int) motion->y, cur_pos);
const double dx = cur_pos.x - m_lastTrackPoint.x;
const double dy = cur_pos.y - m_lastTrackPoint.y;
const double dz = cur_pos.z - m_lastTrackPoint.z;
if (dx || dy || dz)
{
// Update angle, axis of rotation, and redraw
m_angle = 90.0 * sqrt((dx * dx) + (dy * dy) + (dz * dz));
// Axis of rotation comes from cross product of last / cur vectors
m_rotAxis.x = (m_lastTrackPoint.y * cur_pos.z) - (m_lastTrackPoint.z * cur_pos.y);
m_rotAxis.y = (m_lastTrackPoint.z * cur_pos.x) - (m_lastTrackPoint.x * cur_pos.z);
m_rotAxis.z = (m_lastTrackPoint.x * cur_pos.y) - (m_lastTrackPoint.y * cur_pos.x);
redraw();
}
return true;
}
There is some GTK+ stuff in there, but it should be pretty obvious what it's for. The get_trackball_point() function projects the window coordinates X Y onto a hemisphere (the virtual "trackball") that is used as a reference point for rotating the object. Anyway, this more or less works, but after I'm done rotating, and I go to rotate again, the cube snaps back to the original position, obviously, since m_angle will be reset back to near 0 the next time I rotate. Is there anyway to avoid this and preserve the rotation?
Yeah, I ran into this problem too.
What you need to do is keep a rotation matrix around that "accumulates" the current state of rotation, and use it in addition to the rotation matrix that comes from the current dragging operation.
Say you have two matrices, lastRotMx and currRotMx. Make them members of CubeDrawingArea if you like.
You haven't shown us this, but I assume that m_lastTrackPoint is initialized whenever the mouse button goes down for dragging. When that happens, copy currRotMx into lastRotMx.
Then in on_motion_notify_event(), after you calculate m_rotAxis and m_angle, create a new rotation matrix draggingRotMx based on m_rotAxis and m_angle; then multiply lastRotMx by draggingRotMx and put the result in currRotMx.
Finally, in redraw(), instead of
glRotated(m_angle, m_rotAxis.x, m_rotAxis.y, m_rotAxis.z);
rotate by currRotMx.
Update: Or instead of all that... I haven't tested this, but I think it would work:
Make cur_pos a class member so it stays around, but it's initialized to zero, as is m_lastTrackPoint.
Then, whenever a new drag motion is started, before you initialize m_lastTrackPoint, let _3V dpos = cur_pos - m_lastTrackPoint (pseudocode).
Finally, when you do initialize m_lastTrackPoint based on the mouse event coords, subtract dpos from it.
That way, your cur_pos will already be offset from m_lastTrackPoint by an amount based on the accumulation of offsets from past arcball drags.
Probably error would accumulate as well, but it should be gradual enough so as not to be noticeable. But I'd want to test it to be sure... composed rotations are tricky enough that I don't trust them without seeing them.
P.S. your username is demotivating. Suggest picking another one.
P.P.S. For those who come later searching for answers to this question, the keywords to search on are "arcball rotation". An definitive article is Ken Shoemake's section in Graphical Gems IV. See also this arcball tutorial for JOGL.