Zooming towards center of Camera on 2d Plane - c++

Once again, camera zooming on a 2D-Plane. I searched a lot and know that there are similar questions, but I am obviously way too stupid to apply what I was able to find.
Basically I multiply the distance of all elements to the origin by mouseDelta, which is a double between 0.5 and 1. works fine for all elements, but since the anchor of the camera (camX, camY) are the upper left corner of the camera, the objects in the focus of the cam change their position in relation to the focus. I want to scroll "towards" the focus. Here is what I got, but it behaves really weird:
camX and camY, as mentioned, are the coordinates for the upper left of the cam.
mouseDelta is the zoom-level thats stored globally and is changed by each wheel-event.
screenX is the width of the screen/window (fullscreen anyways)
screenY is the height of the screen/window
if (newEvent.type == sf::Event::MouseWheelMoved) //zoom
{
mouseDelta += ((double)newEvent.mouseWheel.delta)/20;
if (mouseDelta > 1) { mouseDelta = 1; }
else if (mouseDelta < 0.5) { mouseDelta = 0.5; }
//resize graphics
for (int i = 0; i < core->universe->world->nodes.size(); i++) {
core->universe->world->nodes.at(i).pic->setSize(mouseDelta);
}
for (int i = 0; i < core->universe->world->links.size(); i++) {
core->universe->world->links.at(i).pic->setSize(mouseDelta);
}
camX = (camX + screenX/2) - (camX + screenX/2)*mouseDelta;
camY = (camY + screenY/2) - (camY + screenY/2)*mouseDelta;
}

Related

How to account for spacing between tiles in a tile sheet

My tile-sheet has tiles that are 64x64, however between each tile there is a 10px gap and i need to account for that gap when setting the texture rectangle in the image in order to draw that tile
I tried simply adding the space upon setting the texture rectangle but the image still looks distorted
for (auto y = 0u; y < map.getTileCount().y; ++y)
{
for (auto x = 0u; x < map.getTileCount().x; ++x)
{
auto posX = static_cast<float>(x * map.getTileSize().x);
auto posY = static_cast<float>(y * map.getTileSize().y);
sf::Vector2f position(posX, posY);
tileSprite.setPosition(position);
auto tileID = tiles[y * map.getTileCount().x + x].ID; //the id of the current tile
if (tileID == 0)
{
continue; //empty tile
}
auto i = 0;
while (tileID < tileSets[i].getFirstGID())
{
++i;
}
auto relativeID = tileID - tileSets[i].getFirstGID();
auto tileX = relativeID % tileSets[i].getColumnCount();
auto tileY = relativeID / tileSets[i].getColumnCount();
textureRect.left = tileX * tileSets[i].getTileSize().x; //i am guessing this is where
// i should account for the spacing
textureRect.top = tileY * tileSets[i].getTileSize().y;
tileSprite.setTexture(mTextureHolder.get(Textures::SpriteSheet));
tileSprite.setTextureRect(textureRect);
mMapTexture.draw(tileSprite);
}
}
The code itself is working and its drawing the tiles in the correct sizes, if i use a normal 64x64 tileset without any spacing the final image looks right however with spacing included the tiles are cut out.
How do i add the gap between the tiles when setting the texture rectangle?
this is how it looks:
this is how it should look:
(NOTE: The "how it should look" image is from the Tiled editor )
Removing the spaces with a python script i found and gimp fixed the problem, however if anyone knows how to account for the spacing feel free to answer as i might need it someday

cocos2dx inverse scaling a layer

I have a CCLayer which holds all my game objects and i have implemented scaling and scrolling. To make sure that you can't scroll out of bounds i am calculating a rect that represents the screen and use it to check if the rect is within bounds. The problem is that my calculations are wrong. The layer is scaled by a scaleFactor like so:
world->setScale(scaleFactor);
and then i calculate the scroll rect:
float scrollWidth = winSize.width * ( 1 / scaleFactor); // winSize is the size of the screen (1280x
float scrollHeight = winSize.height * ( 1 / scaleFactor);
if(scrollRect.l < 0) scrollRect.l = 0;
if(scrollRect.l + scrollWidth > levelWidth) scrollRect.l -= (scrollRect.l + scrollWidth - levelWidth);
scrollRect.r = scrollRect.l + scrollWidth;
world->setPosition(-scrollRect.l, -scrollRect.b);
(the scale factor value is between 1.0 and 0.5)
This sort of works but only when the layer is zoomed out to the max or zoomed in to the minimum but when scaleFactor isn't MAX/MIN it is wrong (there is some left space).
What am i doing wrong? I've also tried changing the anchor points (they are currently set to 0,0) but without any success.
You can do this whatever your scale factor...
here _tileMap is your world
//Code for getting difference b/w two position
CCTouch *fingerOne = (CCTouch*)touchArray->objectAtIndex(0);
CCPoint newTouchLocation = fingerOne->getLocationInView();
newTouchLocation = CCDirector::sharedDirector()->convertToGL(newTouchLocation);
newTouchLocation=_tileMap->convertToNodeSpace(newTouchLocation);
CCPoint oldTouchLocation = fingerOne->getPreviousLocationInView();
oldTouchLocation = CCDirector::sharedDirector()->convertToGL(oldTouchLocation);
oldTouchLocation = _tileMap->convertToNodeSpace(oldTouchLocation);
//get the difference in the finger touches when the player was dragging
CCPoint difference = ccpSub(newTouchLocation, oldTouchLocation);
CCPoint ASD=ccpAdd(_tileMap->getPosition(), ccpMult(difference, _tileMap->getScale()));
CCPoint bottomLeft =ASD;
// Bounding Box....
if (bottomLeft.x >0) {
bottomLeft.x = 0;
}
if (bottomLeft.y>0) {
bottomLeft.y = 0;
}
if (bottomLeft.x < -(mapWidth*_tileMap->getScale() - _screenSize.width)) {
bottomLeft.x = -(mapWidth*_tileMap->getScale()- _screenSize.width);
}
if (bottomLeft.y <-(mapHieght*_tileMap->getScale() - _screenSize.height)) {
bottomLeft.y = - (mapHieght*_tileMap->getScale() - _screenSize.height);
}
_tileMap->setPosition(bottomLeft);
I hope this may help you..

raytracing: why is my sphere rendered as an oval?

I am to write a raytracer, however I already seem to hit my first big problem. For whatever reason, my sphere (which - since I only begin - I simply color white when a ray hits) is rendered as an oval.
Furthermore, it seems that the distortion is getting worse, the farther I am moving the sphere's center away from x = 0 and y = 0
Here's the intersection and main-loop code:
double const Sphere::getIntersection(Ray const& ray) const
{
double t;
double A = 1;
double B = 2*( ray.dir[0]*(ray.origin[0] - center_[0]) + ray.dir[1] * (ray.origin[1] - center_[1]) + ray.dir[2] * (ray.origin[2] - center_[2]));
double C = pow(ray.origin[0]-center_[0], 2) + pow(ray.origin[1]-center_[1], 2) + pow(ray.origin[2] - center_[2], 2) - radius_pow2_;
double discr = B*B - 4*C;
if(discr > 0)
{
t = (-B - sqrt(discr))/2;
if(t <= 0)
{
t = (-B + sqrt(discr))/2;
}
}
else t = 0;
return t;
}
Sphere blub = Sphere(math3d::point(300., 300., -500.), 200.);
Ray mu = Ray();
// for all pixels of window
for (std::size_t y = 0; y < window.height(); ++y) {
for (std::size_t x = 0; x < window.width(); ++x) {
Pixel p(x, y);
mu = Ray(math3d::point(0., 0., 0.), math3d::vector(float(x), float(y), -300.));
if (blub.getIntersection(mu) == 0. ) {
p.color = Color(0.0, 0.0, 0.0);
} else {
p.color = Color(1., 1., 1.);
}
}
}
What I also do not understand is why my "oval" isn't centered on the picture. I have a window of 600 x 600 pixels, so putting the sphere's center at 300 x 300 should afaik put the sphere in the center of the window as well.
my specific solution
(Thanks to Thomas for pushing me to the right direction!)
As Thomas rightly said, my questions where two distinct problems. Considering projecting the sphere in the center, I did as he suggested and changed the origin and projections of the rays.
To get the perspective right, I did not realize I already had to calculate the focal length from the dimensions.
focal_length = sqrt(width^2 + height^2) / ( 2*tan( 45/2 ) )
The result:
This is normal for linear perspective projections, and exacerbated by wide camera angles; see http://en.wikipedia.org/wiki/Perspective_projection_distortion. Most games use something like 90 degrees in the horizontal direction, 45 on either side. But by casting rays up across 600 pixels in the x direction but 300 in the z direction, yours is significantly wider, 126 degrees to be precise.
The reason why your sphere doesn't appear centered is that you're casting rays from the bottom left corner of the screen:
mu = Ray(math3d::point(0.,0.,0.),math3d::vector(float(x),float(y),-300.));
That should be something like:
mu = Ray(math3d::point(width/2,height/2,0.),math3d::vector(float(x-width/2),float(y-height/2),-300.));

SDL - drawing 'negative' circles (Fog of War)

I have this 800x600square I want to draw to the screen. I want to 'cut' circles in it (where alpha would be 0). Basically I'm drawing this whole rectangle over a map so in these 'circles' I drew, you can see the map, otherwise you see the grey square
So, I assume you're trying to add fog of war to one of you game?
I had a small demo I made for a local University a few weeks ago to show A* pathfinding, so I thought I could add fog of war to it for you. Here's the results:
Initial map
First, you start with a complete map, totally visible
Fog
Then, I added a surface to cover the entire screen (take note that my map is smaller than the screen, so for this case I just added fog of war on the screen, but if you have scrolling, make sure it covers each map pixel 1:1)
mFogOfWar = SDL_CreateRGBSurface(SDL_HWSURFACE, in_Width, in_Height, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
SDL_Rect screenRect = {0, 0, in_Width, in_Height};
SDL_FillRect(mFogOfWar, &screenRect, 0xFF202020);
Then, you need to draw it... I added this call after drawing the game objects and before drawing the UI
DrawSurface(mFogOfWar, 0, 0);
Where
void RenderingManager::DrawSurface(SDL_Surface* in_Surface, int in_X, int in_Y)
{
SDL_Rect Dest = { in_X, in_Y, 0, 0 };
SDL_BlitSurface(in_Surface, NULL, mScreen, &Dest);
}
Which should give you the following result:
"Punch Surface"
I then created a 32 bits .png that looks like this (checkerboard shows alpha)
When rendering my main character, I added this call:
gRenderingManager.RemoveFogOfWar(int(mX) + SPRITE_X_OFFSET, int(mY) + SPRITE_Y_OFFSET);
The offset is only there to center the punch with the sprite, basically, what I'm passing to RemoveFogOfWar is the center of my sprite.
Remove Fog Of War
Now the meat of the fog of war. I did two versions, one where Fog of War is removed permanently and one where the fog of war is reset. My fog of war reset relies on my punch surface to have a contour where the alpha is reset to 0 and the fact that my character moves of less pixels than the contour contains per frame, otherwise I would keep the Rect where my punch was applied and I would refill it before drawing again the new punch.
Since I couldn't find a "multiply" blend with SDL, I decided to write a simple function that iterates on the punch surface and updates the alpha on the fog of war surface. The most important part is to make sure you stay within the bounds of your surfaces, so it takes up most of the code... there might be some crop functions but I didn't bother checking:
void RenderingManager::RemoveFogOfWar(int in_X, int in_Y)
{
const int halfWidth = mFogOfWarPunch->w / 2;
const int halfHeight = mFogOfWarPunch->h / 2;
SDL_Rect sourceRect = { 0, 0, mFogOfWarPunch->w, mFogOfWarPunch->h };
SDL_Rect destRect = { in_X - halfWidth, in_Y - halfHeight, mFogOfWarPunch->w, mFogOfWarPunch->h };
// Make sure our rects stays within bounds
if(destRect.x < 0)
{
sourceRect.x -= destRect.x; // remove the pixels outside of the surface
sourceRect.w -= sourceRect.x; // shrink to the surface, not to offset fog
destRect.x = 0;
destRect.w -= sourceRect.x; // shrink the width to stay within bounds
}
if(destRect.y < 0)
{
sourceRect.y -= destRect.y; // remove the pixels outside
sourceRect.h -= sourceRect.y; // shrink to the surface, not to offset fog
destRect.y = 0;
destRect.h -= sourceRect.y; // shrink the height to stay within bounds
}
int xDistanceFromEdge = (destRect.x + destRect.w) - mFogOfWar->w;
if(xDistanceFromEdge > 0) // we're busting
{
sourceRect.w -= xDistanceFromEdge;
destRect.w -= xDistanceFromEdge;
}
int yDistanceFromEdge = (destRect.y + destRect.h) - mFogOfWar->h;
if(yDistanceFromEdge > 0) // we're busting
{
sourceRect.h -= yDistanceFromEdge;
destRect.h -= yDistanceFromEdge;
}
SDL_LockSurface(mFogOfWar);
Uint32* destPixels = (Uint32*)mFogOfWar->pixels;
Uint32* srcPixels = (Uint32*)mFogOfWarPunch->pixels;
static bool keepFogRemoved = false;
for(int x = 0; x < destRect.w; ++x)
{
for(int y = 0; y < destRect.h; ++y)
{
Uint32* destPixel = destPixels + (y + destRect.y) * mFogOfWar->w + destRect.x + x;
Uint32* srcPixel = srcPixels + (y + sourceRect.y) * mFogOfWarPunch->w + sourceRect.x + x;
unsigned char* destAlpha = (unsigned char*)destPixel + 3; // fetch alpha channel
unsigned char* srcAlpha = (unsigned char*)srcPixel + 3; // fetch alpha channel
if(keepFogRemoved == true && *srcAlpha > 0)
{
continue; // skip this pixel
}
*destAlpha = *srcAlpha;
}
}
SDL_UnlockSurface(mFogOfWar);
}
Which then gave me this with keepFogRemoved = false even after the character had moved around
And this with keepFogRemoved = true
Validation
The important part is really to make sure you don't write outside of your pixel buffer, so watch out with negative offsets or offsets that would bring you out of the width or height. To validate my code, I added a simple call to RemoveFogOfWar when the mouse is clicked and tried corners and edges to make sure I didn't have a "off by one" problem
case SDL_MOUSEBUTTONDOWN:
{
if(Event.button.button == SDL_BUTTON_LEFT)
{
gRenderingManager.RemoveFogOfWar(Event.button.x, Event.button.y);
}
break;
}
Notes
Obviously, you don't need a 32 bits texture for the "punch", but it was the clearest way I could think of to show you how to do it. It could be done using as little as 1 bit per pixel (on / off). You can also add some gradient, and change the
if(keepFogRemoved == true && *srcAlpha > 0)
{
continue; // skip this pixel
}
To something like
if(*srcAlpha > *destAlpha)
{
continue;
}
To keep a smooth blend like this:
3 State Fog of War
I thought I should add this... I added a way to create a 3 state fog of war: visible, seen and fogged.
To do this, I simply keep the SDL_Rect of where I last "punched" the fog of war, and if the alpha is lower than a certain value, I clamp it at that value.
So, by simply adding
for(int x = 0; x < mLastFogOfWarPunchPosition.w; ++x)
{
for(int y = 0; y < mLastFogOfWarPunchPosition.h; ++y)
{
Uint32* destPixel = destPixels + (y + mLastFogOfWarPunchPosition.y) * mFogOfWar->w + mLastFogOfWarPunchPosition.x + x;
unsigned char* destAlpha = (unsigned char*)destPixel + 3;
if(*destAlpha < 0x60)
{
*destAlpha = 0x60;
}
}
}
mLastFogOfWarPunchPosition = destRect;
right before the loop where the fog of war is "punched", I get a fog of war similar to what you could have in games like StarCraft:
Now, since the "seen" fog of war is semi transparent, you will need to tweak your rendering method to properly clip "enemies" that would be in the fog, so you don't see them but you still see the terrain.
Hope this helps!

Bullet algorithm having trouble with rotation on the X

Here is what I'm trying to do. I'm trying to make a bullet out of the center of the screen. I have an x and y rotation angle. The problem is the Y (which is modified by rotation on the x) is really not working as intended. Here is what I have.
float yrotrad, xrotrad;
yrotrad = (Camera.roty / 180.0f * 3.141592654f);
xrotrad = (Camera.rotx / 180.0f * 3.141592654f);
Vertex3f Pos;
// get camera position
pls.x = Camera.x;
pls.y = Camera.y;
pls.z = Camera.z;
for(float i = 0; i < 60; i++)
{
//add the rotation vector
pls.x += float(sin(yrotrad)) ;
pls.z -= float(cos(yrotrad)) ;
pls.y += float(sin(twopi - xrotrad));
//translate camera coords to cube coords
Pos.x = ceil(pls.x / 3);
Pos.y = ceil((pls.y) / 3);
Pos.z = ceil(pls.z / 3);
if(!CubeIsEmpty(Pos.x,Pos.y,Pos.z)) //remove first cube that made contact
{
delete GetCube(Pos.x,Pos.y,Pos.z);
SetCube(0,Pos.x,Pos.y,Pos.z);
return;
}
}
This is almost identical to how I move the player, I add the directional vector to the camera then find which cube the player is on. If I remove the pls.y += float(sin(twopi - xrotrad)); then I clearly see that on the X and Z, everything is pointing as it should. When I add pls.y += float(sin(twopi - xrotrad)); then it almost works, but not quite, what I observed from rendering out spheres of the trajector is that the furthur up or down I look, the more offset it becomes rather than stay alligned to the camera's center. What am I doing wrong?
Thanks
What basically happens is very difficult to explain, I'd expect the bullet at time 0 to always be at the center of the screen, but it behaves oddly. If i'm looking straight at the horizon to +- 20 degrees upward its fine but then it starts not following any more.
I set up my matrix like this:
void CCubeGame::SetCameraMatrix()
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(Camera.rotx,1,0,0);
glRotatef(Camera.roty,0,1,0);
glRotatef(Camera.rotz,0,0,1);
glTranslatef(-Camera.x , -Camera.y,-Camera.z );
}
and change the angle like this:
void CCubeGame::MouseMove(int x, int y)
{
if(!isTrapped)
return;
int diffx = x-lastMouse.x;
int diffy = y-lastMouse.y;
lastMouse.x = x;
lastMouse.y = y;
Camera.rotx += (float) diffy * 0.2;
Camera.roty += (float) diffx * 0.2;
if(Camera.rotx > 90)
{
Camera.rotx = 90;
}
if(Camera.rotx < -90)
{
Camera.rotx = -90;
}
if(isTrapped)
if (fabs(ScreenDimensions.x/2 - x) > 1 || fabs(ScreenDimensions.y/2 - y) > 1) {
resetPointer();
}
}
You need to scale X and Z by cos(xradrot). (In other words, multiply by cos(xradrot)).
Imagine you're pointing straight down the Z axis but looking straight up. You don't want the bullet to shoot down the Z axis at all, this is why you need to scale it. (It's basically the same thing that you're doing between X and Z, but now doing it on the XZ vector and Y.)
pls.x += float(sin(yrotrad)*cos(xrotrad)) ;
pls.z -= float(cos(yrotrad)*cos(xrotrad)) ;
pls.y += float(sin(twopi - xrotrad));