I am trying to rotate a bmp image using EasyBMP. when the angle is between 0 and 90 or 270 and 360 the rotation is fine. but when between 180 and 270 the boundary rectangle is stretched and for angle between 90 and 180 I get segmentation fault. I am convinced that the problem arises from
int width = image.TellWidth();
int height = image.TellHeight();
float sine= sin(angle);
float cosine=cos(angle);
float x1=-height*sine;
float y1=height*cosine;
float x2=width*cosine-height*sine;
float y2=height*cosine+width*sine;
float x3=width*cosine;
float y3=width*sine;
float minx=min(0,min(x1,min(x2,x3)));
float miny=min(0,min(y1,min(y2,y3)));
float maxx=max(x1,max(x2,x3));
float maxy=max(y1,max(y2,y3));
int outWidth;
int outHeight;
outWidth=(int)ceil(fabs(maxx)-minx);
outHeight=(int)ceil(fabs(maxy)-miny);
output.SetSize(outHeight,outWidth);
for(int x=0; x<outWidth; x++)
{
for(int y=0; y<outHeight; y++)
{
int srcX=(int)((x+minx)*cosine+(y+miny)*sine);
int srcY=(int)((y+miny)*cosine-(x+minx)*sine);
if(srcX>=0 &&srcX<width && srcY>=0 && srcY<height)
{
output.SetPixel(x,y,image.GetPixel(srcX,srcY));
}
}
}
The following is how I solved this. The TL;DR: the rotation transform goes around 0, 0, so if your image coordinates set 0,0 to bottom left, you need to translate the image to be centered on 0,0 first. Also, sin and cos expect radians, not degrees, so remember to convert first
The long way:
I started by creating a simple program that has easily verified answers, to find out where things are going wrong.
The first thing I noticed was that 90.0f wouldn't produce any output. That seemed weird, so I broke in at the "output image size" printf and realized that the output height was being calculated as -87. Clearly that's not right, so let's see why that might happen.
Going up a bit, outHeight=(int)ceil(fabs(maxy)-miny); so let's figure out how we're ending up with a negative output height when subtracting maxy and miny. It appears maxy is -0.896... and miny is 88.503... However, the absolute value of maxy is taken before subtracting miny, meaning we're ending up with 0.896 - 88.503. Whoa, that's not good! Let's try doing the subtraction then taking the absolute value.
Recompiling with both width and height as such:
outWidth=(int)ceil(fabs(maxx-minx));
outHeight=(int)ceil(fabs(maxy-miny));
Gets us much better values. Now outWidth and outHeight are 2 and 90, respectively. This is massively improved, but the height should be 100. We'll address that later.
To figure out where the math is going wrong, I reorganize the terms to go together: x with x, y with y. Next I adjusted spacing and added parenthesis to make it more readable and ensure order of operations (sure beats trying to look at an OoO table ;) ). Since it's clear you're breaking out the rotation matrix multiplication, I'm going to name your variables something a bit more intuitive than x1, x2, etc. From now on, x1 is topLeftTransformedX, x2 is topRightTransformedX, x3 will exist as bottomLeftTransformedX (always 0), and x4 will be bottomRightTransformedX, same for Y. Longer, but much easier to know what you're dealing with.
Using this, at this point, I see the same thing you do... then I remembered something, based on the numbers seen from this cleaner code (same math as yours, but still easier to debug).
Suddenly, my math for X looks like this:
// x = x cos - y sin
float topLeftTransformedX = (-midX * cosine) - (midY * sine);
float topRightTransformedX = (midX * cosine) - (midY * sine);
float bottomLeftTransformedX = (-midX * cosine) - (-midY * sine);
float bottomRightTransformedX = (midX * cosine) - (-midY * sine);
The rotation matrix rotates around the center point. You have to translate the image to be centered around that for a proper rotation.
Then, when trying to figure out why this would be giving the values it is, i recalled something else - angle needs to be in radians.
Suddenly, it almost all works. There's still some more to do, but this should get you 95% of the way there or more. Hope it helps!
// bmprotate.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <math.h>
#define min(x,y) x < y ? x : y
#define max(x,y) x > y ? x : y
#define PI 3.14159
void rotate(int width, int height, float angleInDeg)
{
float angle = angleInDeg * (PI/180.0f);
float midX = ((float)width) / 2.0f;
float midY = ((float)height) / 2.0f;
float sine = sin(angle);
float cosine = cos(angle);
// x = x cos - y sin
float topLeftTransformedX = (-midX * cosine) - (midY * sine);
float topRightTransformedX = (midX * cosine) - (midY * sine);
float bottomLeftTransformedX = (-midX * cosine) - (-midY * sine);
float bottomRightTransformedX = (midX * cosine) - (-midY * sine);
float minx = min( topLeftTransformedX, min(topRightTransformedX, min(bottomLeftTransformedX, bottomRightTransformedX)) );
float maxx = max( topLeftTransformedX, max(topRightTransformedX, max(bottomLeftTransformedX, bottomRightTransformedX)) );
// y = x sin + y cos
float topLeftTransformedY = (-midX * sine) + (midY * cosine);
float topRightTransformedY = (midX * sine) + (midY * cosine);
float bottomLeftTransformedY = (-midX * sine) + (-midY * cosine);
float bottomRightTransformedY = (midX * sine) + (-midY * cosine);
float miny = min( topLeftTransformedY, min(topRightTransformedY, min(bottomLeftTransformedY, bottomRightTransformedY)) );
float maxy = max( topLeftTransformedY, max(topRightTransformedY, max(bottomLeftTransformedY, bottomRightTransformedY)) );
int outWidth;
int outHeight;
printf("(%f,%f) , (%f,%f) , (%f,%f) , (%f,%f)\n",
topLeftTransformedX, topLeftTransformedY,
topRightTransformedX, topRightTransformedY,
bottomLeftTransformedX, bottomLeftTransformedY,
bottomRightTransformedX, bottomRightTransformedY);
outWidth = (int) ceil( fabs(maxx) + fabs(minx));
outHeight = (int) ceil( fabs(maxy) + fabs(miny) );
printf("output image size: (%d,%d)\n",outWidth,outHeight);
for(int x=0; x<outWidth; x++)
{
for(int y=0; y<outHeight; y++)
{
int srcX=(int)((x+minx)*cosine+(y+miny)*sine);
int srcY=(int)((y+miny)*cosine-(x+minx)*sine);
if(srcX >=0 && srcX < width && srcY >= 0 && srcY < height)
{
printf("(x,y) = (%d,%d)\n",srcX, srcY);
}
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
rotate(100,2,90.0f);
for (int i = 0; i < 360; i++)
{
}
return 0;
}
Related
I have four lines held in a Rectangle object. I'm trying to rotate the entire box by any angle, but I'm getting weird results.
Here's my current code:
void Line::rotate(int x_anchor, int y_anchor, double angle) {
// Change the coordinate system
int xOffset = m_pt1.x() - x_anchor;
int yOffset = m_pt1.y() - y_anchor;
// Move to 0, 0
int xTemp = m_pt2.x() - xOffset;
int yTemp = m_pt2.y() - yOffset;
// Rotate
double tCos = cos(angle);
double tSin = sin(angle);
double xNew = (xTemp * tCos) - (yTemp * tSin);
double yNew = (xTemp * tSin) + (yTemp * tCos);
// Make new
m_pt2 = Point(xNew + xOffset, yNew + yOffset);
}
What I'm trying to do is move the origin, then move the line down to that orgin, rotate it, then put it back. By doing this, if I do something like:
void Rectangle::rotate(int x_anchor, int y_anchor, double angle) {
m_t.rotate(x_anchor, y_anchor, angle);
m_r.rotate(x_anchor, y_anchor, angle);
m_b.rotate(x_anchor, y_anchor, angle);
m_l.rotate(x_anchor, y_anchor, angle);
}
The box should rotate together. However, this doesn't even work for a line, so I'm not sure where I've gone wrong on my formula. This thread is what I'm referencing for the formula.
Thanks.
EDIT:
I've modified my code according to FalconUA's suggestion:
void Line::rotate(int x_anchor, int y_anchor, double angle) {
/* Change the coordinate system */
// Start point
int xStartOffset = m_pt1.x() - x_anchor;
int yStartOffset = m_pt1.y() - y_anchor;
// End point
int xEndOffset = m_pt2.x() - x_anchor;
int yEndOffset = m_pt2.y() - y_anchor;
/* Move to 0, 0 */
// Start point
int xStartTemp = m_pt2.x() - xStartOffset;
int yStartTemp = m_pt2.y() - yStartOffset;
// End point
int xEndTemp = m_pt2.x() - xEndOffset;
int yEndTemp = m_pt2.y() - yEndOffset;
// Precalculate sin and cos
double tCos = cos(angle);
double tSin = sin(angle);
/* Rotate */
// Start point
double xStartNew = (xStartTemp * tCos) - (yStartTemp * tSin);
double yStartNew = (xStartTemp * tSin) + (yStartTemp * tCos);
// End point
double xEndNew = (xEndTemp * tCos) - (yEndTemp * tSin);
double yEndNew = (xEndTemp * tSin) + (yEndTemp * tCos);
// Make new points
m_pt1 = Point(xStartNew + xStartOffset, yStartNew + yStartOffset);
m_pt2 = Point(xEndNew + xEndOffset, yEndNew + yEndOffset);
}
However, still not quite getting what I should.
Given:
Rectangle r(5, 5, 10, 10);
Which outputs:
xxxxxx
x x
x x
x x
x x
xxxxxx
And then if I rotate by 90 (PI / 2) degrees, which is done by this:
// Use the bottom left corner as the common point
rotate(m_l.getEnd().x(), m_l.getEnd().y(), PI / 2);
I get
x x
x x
x x
x x
x x
x
x
x
x
x x
x
x
x
x
x
Seems like it happens because you're rotating lines relatively to the first point of the line. So, rotate not the line relatively to the first point, but rotate two points separately.
Update: if you have an anchor (xa, ya), and you want to rotate the point (x, y) around it. Your point can be represented as (xa + u, ya + v), where (u, v) = (x - xa, y - ya). So all you have to do is rotate teh vector (u, v) by using the formula with sin and cos that you've used above and the resulting point will be (xa + u_rotated, ya + v_rotated).
void Line::rotate(int x_anchor, int y_anchor, double angle) {
// the vector to rotate
int rotvec_x1 = m_pt1.x() - x_anchor;
int rotvec_y1 = m_pt1.y() - y_anchor;
// the vector to rotate
int rotvec_x2 = m_pt2.x() - x_anchor;
int rotvec_y2 = m_pt2.y() - y_anchor;
// pre-calculation for sin and cos
double tCos = cos(angle);
double tSin = sin(angle);
// rotating first vector
double rotvec_x1_new = (rotvec_x1 * tCos) - (rotvec_y1 * tSin);
double rotvec_y1_new = (rotvec_x1 * tSin) + (rotvec_y1 * tCos);
// rotating second vector
double rotvec_x2_new = (rotvec_x2 * tCos) - (rotvec_y2 * tSin);
double rotvec_y2_new = (rotvec_x2 * tSin) + (rotvec_y2 * tCos);
// Make new
m_pt1 = Point(x_anchor + rotvec_x1_new, y_anchor + rotvec_y1_new);
m_pt2 = Point(x_anchor + rotvec_x2_new, y_anchor + rotvec_y2_new);
}
I copied this ellipse code directly from the opengl textbook:
void ellipseMidpoint (int xCenter, int yCenter, int Rx, int Ry)
{
int Rx2 = Rx * Rx;
int Ry2 = Ry * Ry;
int twoRx2 = 2 * Rx2;
int twoRy2 = 2 * Ry2;
int p;
int x = 0;
int y = Ry;
int px = 0;
int py = twoRx2 * y;
//initial points in both quadrants
ellipsePlotPoints (xCenter, yCenter, x, y);
//Region 1
p = round (Ry2 - (Rx2 * Ry) + (0.25 * Rx2));
while (px < py) {
x++;
px += twoRy2;
if (p < 0)
p += Ry2 + px;
else {
y--;
py -= twoRx2;
p += Ry2 + px - py;
}
ellipsePlotPoints (xCenter, yCenter, x, y);
}
//Region 2
p = round (Ry2 * (x+0.5) * (x+0.5) + Rx2 * (y-1) * (y-1) - Rx2 * Ry2);
while (y > 0) {
y--;
py -= twoRx2;
if (p > 0)
p += Rx2 - py;
else {
x++;
px += twoRy2;
p += Rx2 - py + px;
}
ellipsePlotPoints (xCenter, yCenter, x, y);
}
}
void ellipsePlotPoints (int xCenter, int yCenter, int x, int y)
{
setPixel (xCenter + x, yCenter + y);
setPixel (xCenter - x, yCenter + y);
setPixel (xCenter + x, yCenter - y);
setPixel (xCenter - x, yCenter - y);
}
void setPixel (GLint xPos, GLint yPos)
{
glBegin (GL_POINTS);
glVertex2i(xPos, yPos);
glEnd();
}
The smaller ellipses seem to be fine but the larger ones are pointy and sort of flat at the ends.
Any ideas why?
Here is a current screenshot:
I think you're encountering overflow. I played with your code. While I never saw exactly the same "lemon" type shapes from your pictures, things definitely fell apart at large sizes, and it was caused by overflowing the range of the int variables used in the code.
For example, look at one of the first assignments:
int py = twoRx2 * y;
If you substitute, this becomes:
int py = 2 * Rx * Rx * Ry;
If you use a value of 1000 each for Rx and Ry, this is 2,000,000,000. Which is very close to the 2^31 - 1 top of the range of a 32-bit int.
If you want to use this algorithm for larger sizes, you could use 64-bit integer variables. Depending on your system, the type would be long or long long. Or more robustly, int64_t after including <stdint.h>.
Now, if all you want to do is draw an ellipsis with OpenGL, there are much better ways. The Bresenham type algorithms used in your code are ideal if you need to draw a curve pixel by pixel. But OpenGL is a higher level API, which knows how to render more complex primitives than just pixels. For a curve, you will most typically use a connected set of line segments to approximate the curve. OpenGL will then take care of turning those line segments into pixels.
The simplest way to draw an ellipsis is to directly apply the parametric representation. With phi an angle between 0 and PI, and using the naming from your code, the points on the ellipsis are:
x = xCenter + Rx * cos(phi)
y = yCenter + Ry * sin(phi)
You can use an increment for phi that meets your precision requirements, and the code will look something to generate an ellipsis approximated by DIV_COUNT points will look something like this:
float angInc = 2.0f * m_PI / (float)DIV_COUNT;
float ang = 0.0f;
glBegin(GL_LINE_LOOP);
for (int iDiv = 0; iDiv < DIV_COUNT; ++iDiv) {
ang += angInc;
float x = xCenter + Rx * cos(ang);
float y = yCenter + Ry * sin(ang);
glVertex2f(x, y);
glEnd();
If you care about efficiency, you can avoid calculating the trigonometric functions for each point, and apply an incremental rotation to calculate each point from the previous one:
float angInc = 2.0f * M_PI / (float)DIV_COUNT;
float cosInc = cos(angInc);
float sinInc = sin(angInc);
float cosAng = 1.0f;
float sinAng = 0.0f
glBegin(GL_LINE_LOOP);
for (int iDiv = 0; iDiv < DIV_COUNT; ++iDiv) {
float newCosAng = cosInc * cosAng - sinInc * sinAng;
sinAng = sinInc * cosAng + cosInc * sinAng;
cosAng = newCosAng;
float x = xCenter + Rx * cosAng;
float y = yCenter + Ry * sinAng;
glVertex2f(x, y);
glEnd();
This code is of course just for illustrating the math, and to get you started. In reality, you should use current OpenGL rendering methods, which includes vertex buffers, etc.
I am learning Qt from TrollTech's Qt Tutorial these days, and I'am confused about the source code of calculating the position of bullet in this page:
QRect CannonField::shotRect() const
{
const double gravity = 4;
double time = timerCount / 20.0;
double velocity = shootForce;
double radians = shootAngle * 3.14159265 / 180;
double velx = velocity * cos(radians);
double vely = velocity * sin(radians);
double x0 = (barrelRect.right() + 5) * cos(radians);
double y0 = (barrelRect.right() + 5) * sin(radians);
double x = x0 + velx * time;
double y = y0 + vely * time - 0.5 * gravity * time * time;
QRect result(0, 0, 6, 6);
result.moveCenter(QPoint(qRound(x), height() - 1 - qRound(y)));
return result;
}
In the third-last line:
result.moveCenter(QPoint(qRound(x), height() - 1 - qRound(y)));
I think that - 1 is nonsense, isn't it?
You have a widget:
If the height of widget is height, then y == 0 line is on the top of the widget and bottom line has y == height - 1 coordinate. So, if you want to show a point on the bottom line of the widget, you should set it y coordinate to height - 1.
Apparently, they use bottom of the widget as a ground level, so the bullet can be only above or on this level.
I am trying to implement a bilinear interpolation function, but for some reason I am getting bad output. I cant seem to figure out what's wrong, any help getting on the right track will be appreciated.
double lerp(double c1, double c2, double v1, double v2, double x)
{
if( (v1==v2) ) return c1;
double inc = ((c2-c1)/(v2 - v1)) * (x - v1);
double val = c1 + inc;
return val;
};
void bilinearInterpolate(int width, int height)
{
// if the current size is the same, do nothing
if(width == GetWidth() && height == GetHeight())
return;
//Create a new image
std::unique_ptr<Image2D> image(new Image2D(width, height));
// x and y ratios
double rx = (double)(GetWidth()) / (double)(image->GetWidth()); // oldWidth / newWidth
double ry = (double)(GetHeight()) / (double)(image->GetHeight()); // oldWidth / newWidth
// loop through destination image
for(int y=0; y<height; ++y)
{
for(int x=0; x<width; ++x)
{
double sx = x * rx;
double sy = y * ry;
uint xl = std::floor(sx);
uint xr = std::floor(sx + 1);
uint yt = std::floor(sy);
uint yb = std::floor(sy + 1);
for (uint d = 0; d < image->GetDepth(); ++d)
{
uchar tl = GetData(xl, yt, d);
uchar tr = GetData(xr, yt, d);
uchar bl = GetData(xl, yb, d);
uchar br = GetData(xr, yb, d);
double t = lerp(tl, tr, xl, xr, sx);
double b = lerp(bl, br, xl, xr, sx);
double m = lerp(t, b, yt, yb, sy);
uchar val = std::floor(m + 0.5);
image->SetData(x,y,d,val);
}
}
}
//Cleanup
mWidth = width; mHeight = height;
std::swap(image->mData, mData);
}
Input Image (4 pixels wide and high)
My Output
Expected Output (Photoshop's Bilinear Interpolation)
Photoshop's algorithm assumes that each source pixel's color is in the center of the pixel, while your algorithm assumes that the color is in its topleft. This causes your results to be shifted half a pixel up and left compared to Photoshop.
Another way to look at it is that your algorithm maps the x coordinate range (0, srcWidth) to (0, dstWidth), while Photoshop maps (-0.5, srcWidth-0.5) to (-0.5, dstWidth-0.5), and the same in y coordinate.
Instead of:
double sx = x * rx;
double sy = y * ry;
You can use:
double sx = (x + 0.5) * rx - 0.5;
double sy = (y + 0.5) * ry - 0.5;
to get similar results. Note that this can give you a negative value for sx and sy.
i am working on an implementation of the Separting Axis Theorem for use in 2D games. It kind of works but just kind of.
I use it like this:
bool penetration = sat(c1, c2) && sat(c2, c1);
Where c1 and c2 are of type Convex, defined as:
class Convex
{
public:
float tx, ty;
public:
std::vector<Point> p;
void translate(float x, float y) {
tx = x;
ty = y;
}
};
(Point is a structure of float x, float y)
The points are typed in clockwise.
My current code (ignore Qt debug):
bool sat(Convex c1, Convex c2, QPainter *debug)
{
//Debug
QColor col[] = {QColor(255, 0, 0), QColor(0, 255, 0), QColor(0, 0, 255), QColor(0, 0, 0)};
bool ret = true;
int c1_faces = c1.p.size();
int c2_faces = c2.p.size();
//For every face in c1
for(int i = 0; i < c1_faces; i++)
{
//Grab a face (face x, face y)
float fx = c1.p[i].x - c1.p[(i + 1) % c1_faces].x;
float fy = c1.p[i].y - c1.p[(i + 1) % c1_faces].y;
//Create a perpendicular axis to project on (axis x, axis y)
float ax = -fy, ay = fx;
//Normalize the axis
float len_v = sqrt(ax * ax + ay * ay);
ax /= len_v;
ay /= len_v;
//Debug graphics (ignore)
debug->setPen(col[i]);
//Draw the face
debug->drawLine(QLineF(c1.tx + c1.p[i].x, c1.ty + c1.p[i].y, c1.p[(i + 1) % c1_faces].x + c1.tx, c1.p[(i + 1) % c1_faces].y + c1.ty));
//Draw the axis
debug->save();
debug->translate(c1.p[i].x, c1.p[i].y);
debug->drawLine(QLineF(c1.tx, c1.ty, ax * 100 + c1.tx, ay * 100 + c1.ty));
debug->drawEllipse(QPointF(ax * 100 + c1.tx, ay * 100 + c1.ty), 10, 10);
debug->restore();
//Carve out the min and max values
float c1_min = FLT_MAX, c1_max = FLT_MIN;
float c2_min = FLT_MAX, c2_max = FLT_MIN;
//Project every point in c1 on the axis and store min and max
for(int j = 0; j < c1_faces; j++)
{
float c1_proj = (ax * (c1.p[j].x + c1.tx) + ay * (c1.p[j].y + c1.ty)) / (ax * ax + ay * ay);
c1_min = min(c1_proj, c1_min);
c1_max = max(c1_proj, c1_max);
}
//Project every point in c2 on the axis and store min and max
for(int j = 0; j < c2_faces; j++)
{
float c2_proj = (ax * (c2.p[j].x + c2.tx) + ay * (c2.p[j].y + c2.ty)) / (ax * ax + ay * ay);
c2_min = min(c2_proj, c2_min);
c2_max = max(c2_proj, c2_max);
}
//Return if the projections do not overlap
if(!(c1_max >= c2_min && c1_min <= c2_max))
ret = false; //return false;
}
return ret; //return true;
}
What am i doing wrong? It registers collision perfectly but is over sensitive on one edge (in my test using a triangle and a diamond):
//Triangle
push_back(Point(0, -150));
push_back(Point(0, 50));
push_back(Point(-100, 100));
//Diamond
push_back(Point(0, -100));
push_back(Point(100, 0));
push_back(Point(0, 100));
push_back(Point(-100, 0));
I am getting this mega-adhd over this, please help me out :)
http://u8999827.fsdata.se/sat.png
OK, I was wrong the first time. Looking at your picture of a failure case it is obvious a separating axis exists and is one of the normals (the normal to the long edge of the triangle). The projection is correct, however, your bounds are not.
I think the error is here:
float c1_min = FLT_MAX, c1_max = FLT_MIN;
float c2_min = FLT_MAX, c2_max = FLT_MIN;
FLT_MIN is the smallest normal positive number representable by a float, not the most negative number. In fact you need:
float c1_min = FLT_MAX, c1_max = -FLT_MAX;
float c2_min = FLT_MAX, c2_max = -FLT_MAX;
or even better for C++
float c1_min = std::numeric_limits<float>::max(), c1_max = -c1_min;
float c2_min = std::numeric_limits<float>::max(), c2_max = -c2_min;
because you're probably seeing negative projections onto the axis.