How to keep mouse coordinates within window height/width limits - opengl

Suppose I have a 600 by 600 window created through glfwCreateWindow().
I have a few models rendered in the scene and to move around them in the 3D space I use a camera class which moves around with my cursor as well as AWSD/spacebar keys.
To make the movement "seamless" I use glfwSetInputMode(this->window, GLFW_CURSOR, GLFW_CURSOR_DISABLED).
Now then the problem is that I am also trying to implent a mouse picker of sorts (casting ray from cursor position to the scene).
So I implemented the mouse picker from a video I found on yt, but it doesnt work properly. The reason being is that my mouse X and Y coordinates get bigger than width and height if for example I rotate on spot (and I can increase it this way indefinitely).
I understand that this is happening because I have no cursor to be bound within resolution limits due to glfwSetInputMode(this->window, GLFW_CURSOR, GLFW_CURSOR_DISABLED). I am asking how I should correct this so that I can keep the seamlessness as well as be able to limit the cursor coordinates within width and height (otherwise the mouse picker function wont work because normalized mouse coordinates go over [1,1] which completely breaks it and so on).
I will be grateful for any answers.
EDIT:
#httpdigest's answer put into very elementary code:
//store offsetX and offsetY values (and dont forget to give them 0 as initial value)
overshootX = mouseX - offsetX;
overshootY = mouseY - offsetY;
if (overshootX > width) {
offsetX = offsetX + overshootX - width;
}
if (overshootX < 0) {
offsetX = offsetX + overshootX;
}
if (overshootY > height) {
offsetY = offsetY + overshootY - height;
}
if (overshootY < 0) {
offsetY = offsetY + overshootY;
}
float withinWindowCursorPosX = mouseX - offsetX;
float withinWindowCursorPosY = mouseY - offsetY;

One way of doing it is to store an offset (initially (0, 0)) by how much the cursor moved beyond the bounds of the window/viewport (separate for X and Y).
Essentially, everytime when you either read the current cursor position or are being told of an update by an event, you calculate overshoot = cursorPos - offset (separate for X and Y). If overshoot exceeds (width, height) you increment offset by the excess it exceeded (width, height). Likewise, if overshoot is less than (0, 0) you decrement offset by the negative overshoot.
That way, you have a "sliding" window/rectangle which gets slided around as you keep on moving the mouse cursor beyond either edge of the screen.
Now, whenever you want to obtain the corrected cursor position (within your window), you simply use withinWindowCursorPos = cursorPos - offset.
You can then use withinWindowCursorPos for your picking calculations that require a cursor within the window/viewport.

Related

How can I maintain points inside a rectangle that keeps its aspect ratio inside and changing outer container?

So my problem involves trying to keep points drawn into a rectangle at the same position of the rectangle while an outer container box is scaled to any size.
The rectangle containing the points will keep its aspect ratio while it grows and shrinks in the center of the outer box.
I'm able to keep the inner box's aspect ratio constant but am having problems drawing the points in the correct place when scaling the outer box. Here's an example of my problem.
I'd like the point to stay on the same spot the picture is no matter how the outer box is scaled. The coordinate system has 0,0 as the topleft of the outer box and the inner box is centered using offsets allowing the inner box to be big as possible while maintaining its aspect ratio, however I'm stuck on getting points I add to maintain their position in the box. Here's a look at what I think I should be doing:
void PointsHandler::updatePoints()
{
double imgRatio = boxSize.width() / boxSize.height();
double oldXOffset = (oldContainerSize.width() - oldBoxSize.width()) / 2;
double oldYOffset = (oldContainerSize.height() - oldBoxSize.height()) / 2;
double newXOffset = (containerSize.width() - boxSize.width()) / 2;
double newYOffset = (containerSize.height() - boxSize.height()) / 2;
for(int i = 0; i < points.size(); i++){
double newX = ((points[i].x() - oldXOffset) + newXOffset) * boxRatio;
double newY = ((points[i].y() - oldYOffset) + newYOffset) * boxRatio;
points.replace(i, Point(newX, newY));
}
}
The requested transformations are only translations and scale.
To preserve the original aspect ratio of the inner box, the scale factor must be the same for the x and the y axes. To choose which one to apply, the user should compare the ratio between the width and the height of the new outer box with the aspect ratio of the old inner box. If it's lower, the scale should be the ratio between the new width and the old one, otherwise the ratio between the heights.
To respect the correct order of the transformations, you need to first apply a translation of the points so that the old center of inner box coincides with the origin of the axes (the top left corner of the outer box, apparently), then scale the points and finally translate back to the new center of the outer box. That's not what the posted code attempts to do, because it seems that the scale is applied last.

SFML sf::View::move inconstancy

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);
}
}

How do I flip an image and point a gun at the mouse at the same time? (SFML)

I'm making a shooter where the player can shoot at the mouse, so I got the guns to point at the mouse, but I can't figure out how to correctly flip the image once it's turned left. I mean, I did flip the image but it's not aligned right. I'm not really sure how to explain it, here's my code.
void PortalGun::update(Vector2i mPos) {
float pi = 3.14159265359;
float rotation = atan2(sprite.getGlobalBounds().top - mPos.y,sprite.getGlobalBounds().left - mPos.x) * 180 / pi;
int x = player->sprite.getGlobalBounds().left + 16;
int y = player->sprite.getGlobalBounds().top;
if (rotation > -90 && rotation < 90) {
player->dir = -1;
sprite.setTextureRect(IntRect(0, 32, 64, -32));
} else {
player->dir = 1;
sprite.setTextureRect(IntRect(0, 0, 64, 32));
}
sprite.setPosition(x, y + 15);
sprite.setRotation(rotation + 170);
}
When the mouse is to the left of the gun, it flips the image but keeps rotating upwards so the mouse is 20 ish pixels higher. I can't just change the position when rotating, so what do I do? Sorry for sounding a bit cryptic, it's a bit hard to explain.
First of all, you should set your sprite's origin to the point where you'd like to rotate the gun (typically the handle or mounting point). To do this, use sf::Sprite::setOrigin(). The passed coordinates are relative to the top left corner of the sprite.
To get or set your sprite's position in the world (where your origin is), you can use sf::Sprite::getPosition() and sf::Sprite::setPosition().
Your rotation can stay as it is. It will rotate around your set origin.
To mirror your sprite, just scale (sf::Sprite::setScale()) it using a negative factor: sprite.setScale(-1, 1); The negative factor will mirror/flip the image at the set origin, without forcing you to update the texture coordinates.

Isometric Collision - 'Diamond' shape detection

My project uses an isometric perspective for the time being I am showing the co-ordinates in grid-format above them for debugging. However, when it comes to collision/grid-locking of the player, I have an issue.
Due to the nature of sprite drawing, my maths is creating some issues with the 'triangular' corner empty areas of the textures. I think that the issue is something like below (blue is what I think is the way my tiles are being detected, whereas the red is how they ideally should be detected for accurate roaming movement on the tiles:
As you can see, the boolean that checks the tile I am stood on (which takes the pixel central to the player's feet, the player will later be a car and take a pixel based on the direction of movement) is returning false and denying movement in several scenarios, as well as letting the player move in some places that shouldn't be allowed.
I think that it's because the cutoff areas of each texture are (I think) being considered part of the grid area, so when the player is in one of these corner areas it is not truly checking the correct tile, and so returning the wrong results.
The code I'm using for creating the grid is this:
int VisualComponent::TileConversion(Tile* tileToConvert, bool xOrY)
{
int X = (tileToConvert->x - tileToConvert->y) * 64; //change 64 to TILE_WIDTH_HALF
int Y = (tileToConvert->x + tileToConvert->y) * 25;
/*int X = (tileToConvert->x * 128 / 2) + (tileToConvert->y * 128 / 2) + 100;
int Y = (tileToConvert->y * 50 / 2) - (tileToConvert->x * 50 / 2) + 100;*/
if (xOrY)
{
return X;
}
else
{
return Y;
}
}
and the code for checking the player's movement is:
bool Clsentity::CheckMovementTile(int xpos, int ypos, ClsMapData* mapData) //check if the movement will end on a legitimate road tile UNOPTIMISED AS RUNS EVERY FRAME FOR EVERY TILE
{
int x = xpos + 7; //get the center bottom pixel as this is more suitable than the first on an iso grid (more realistic 'foot' placement)
int y = ypos + 45;
int mapX = (x / 64 + y / 25) / 2; //64 is TILE-WIDTH HALF and 25 is TILE HEIGHT
int mapY = (y / 25 - (x / 64)) / 2;
for (int i = 0; i < mapData->tilesList.size(); i++) //for each tile of the map
{
if (mapData->tilesList[i]->x == mapX && mapData->tilesList[i]->y == mapY) //if there is an existing tile that will be entered
{
if (mapData->tilesList[i]->movementTile)
{
HAPI->DebugText(std::to_string(mapX) + " is the x and the y is " + std::to_string(mapY));
return true;
}
}
}
return false;
}​
I'm a little stuck on progression until having this fixed in the game loop aspect of things. If anyone thinks they either know the issue from this or might be able to help it'd be great and I would appreciate it. For reference also, my tile textures are 128x64 pixels and the math behind drawing them to screen treats them as 128x50 (to cleanly link together).
Rather than writing specific routines for rendering and click mapping, seriously consider thinking of these as two views on the data, which can be transformed in terms of matrix transformations of a coordinate space. You can have two coordinate spaces - one is a nice rectangular grid that you use for positioning and logic. The other is the isometric view that you use for display and input.
If you're not familiar with linear algebra, it'll take a little bit to wrap your head around it, but once you do, it makes everything trivial.
So, how does that work? Your isometric view is merely a rotation of a bog standard grid view, right? Well, close. Isometric view also changes the dimensions if you're starting with a square grid. Anyhow: can we just do a simple coordinate transformation?
Logical coordinate system -> display system (e.g. for rendering)
Texture point => Rotate 45 degrees => Scale by sqrt(2) because a 45 degree rotation changes the dimension of the block by sqrt(1 * 1 + 1 * 1)
Display system -> logical coordinate system (e.g. for mapping clicks into logical space)
Click point => descale by sqrt(2) to unsquish => unrotate by 45 degrees
Why?
If you can do coordinate transformations, then you'd be dealing with a pretty bog-standard rectangular grid for everything else you write, which will make your any other logic MUCH simpler. Your calculations there won't involve computing angles or slopes. E.g. now your "can I move 'down'" logic is much simpler.
Let's say you have 64 x 64 tiles, for simplicity. Now transforming a screen space click to a logical tile is simply:
(int, int) whichTile(clickX, clickY) {
logicalX, logicalY = transform(clickX, clickY)
return (logicalX / 64, logicalY / 64)
}
You can do checks like see if x0,y0 and x1,y1 are on the same tile, in the logical space by someting as simple as:
bool isSameTile(x0, y0, x1, y1) {
return floor(x0/64) == floor(x1/64) && floor(y0/64) == floor(y1/64)
}
Everything gets much simpler once you define the transforms and work in the logical space.
http://en.wikipedia.org/wiki/Rotation_matrix
http://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation
http://www.alcove-games.com/advanced-tutorials/isometric-tile-picking/
If you don't want to deal with some matrix library, you can do the equivalent math pretty straightforwardly, but if you separate concerns of logic management from display / input through these transformations, I suspect you'll have a much easier time of it.

C++ Zoom into the centre of the screen in 2D coordinates

I'm having difficulty working out the correct calculations in order to zoom into the centre of the screen in 2D coordinates whilst keeping everything in the correct scale.
I have a vector which I use to handle moving around my map editor as follows:
scroll = sf::Vector2<float>(-640.0f, -360.0f);
It's set at -640.0f, -360.0f to make 0,0 the centre of the screen on initialising (based on my window being 1280x720).
My zoom value ranges from 0.1f to 2.0f and it's increased or decreased in 0.05 increments:
zoomScale = zoomScale + 0.05;
When drawing elements on to the screen they are drawn using the following code:
sf::Rect<float> dRect;
dRect.left = (mapSeg[i]->position.x - scroll.x) * (layerScales[l] * zoomScale);
dRect.top = (mapSeg[i]->position.y - scroll.y) * (layerScales[l] * zoomScale);
dRect.width = (float)segDef[mapSeg[i]->segmentIndex]->width;
dRect.height = (float)segDef[mapSeg[i]->segmentIndex]->height;
sf::Sprite segSprite;
segSprite.setTexture(segDef[mapSeg[i]->segmentIndex]->tex);
segSprite.setPosition(dRect.left, dRect.top);
segSprite.setScale((layerScales[l] * zoomScale), (layerScales[l] * zoomScale));
segSprite.setOrigin(segDef[mapSeg[i]->segmentIndex]->width / 2, segDef[mapSeg[i]->segmentIndex]->height / 2);
segSprite.setRotation(mapSeg[i]->rotation);
Window.draw(segSprite);
layerScales is a value used to scale up layers of segments for parallax scrolling.
This seems to work fine when zooming in and out but the centre point seems to shift (an element that I know should always be at 0,0 will be located at different co-ordinates as soon as I zoom). I use the following to calculate what the position as at the mouse to test this as follows:
mosPosX = ((float)input.mousePos.x + scroll.x) / zoomScale)
mosPosY = ((float)input.mousePos.y + scroll.y) / zoomScale)
I'm sure there's a calculation I should be doing to the 'scroll' vector to take into account this zoom but I can't seem to get it to work right.
I tried implementing something like below but it didn't produce the correct results:
scroll.x = (scroll.x - (SCREEN_WIDTH / 2)) * zoomScale - (scroll.x - (SCREEN_WIDTH / 2));
scroll.y = (scroll.y - (SCREEN_HEIGHT / 2)) * zoomScale - (scroll.y - (SCREEN_HEIGHT / 2));
Any ideas what I'm doing wrong?
I will do this the easy way (not most efficient but works fine) and only for single axis (second is the same)
it is better to have offset unscaled:
scaledpos = (unscaledpos*zoomscale)+scrolloffset
know center point should not move after scale change (0 means before 1 means after):
scaledpos0 == scaledpos1
so do this:
scaledpos0 = (midpointpos*zoomscale0)+scrolloffset0; // old scale
scaledpos1 = (midpointpos*zoomscale1)+scrolloffset0; // change zoom only
scrolloffset1+=scaledpos0-scaledpos1; // correct offset so midpoint stays where is ... i usualy use mouse coordinate instead of midpoint so i zoom where the mouse is
when you can not change the scaling equation then just do the same with yours
scaledpos0 = (midpointpos+scrolloffset0)*zoomscale0;
scaledpos1 = (midpointpos+scrolloffset0)*zoomscale1;
scrolloffset1+=(scaledpos0-scaledpos1)/zoomscale1;
Hope I did no silly error in there (writing from memory). For more info see
Zooming graphics based on current mouse position