How to rotate points about a specific origin? - c++

I'm working on rotating the vertices of my object around a point located on the object that's not necessarily its center. I followed this tutorial pretty closely and got the vertices to keep their same proportions, i.e. the shape that they create does rotate about the given point, however the amount by which it rotates seems arbitrary. I'll explain in the code and the screenshots. I'm using SFML, but I'll explain the sf:: namespaces in the comments where they're used for those who need it. Anyways, here's my main file that shows the problem:
int _tmain(int argc, _TCHAR* argv[]){
sf::RenderWindow window(sf::VideoMode(500, 500, 32), "Animation");
//sf::vertexarray is a list of POINTS on the screen, their position is determined with a sf::vector
sf::VertexArray vertices;
//arrange 6 points in a shape
vertices.setPrimitiveType(sf::PrimitiveType::Points);
//bottom middle
vertices.append(sf::Vector2f(200, 200));
//left bottom edge
vertices.append(sf::Vertex(sf::Vector2f(195, 195)));
//left top edge
vertices.append(sf::Vertex(sf::Vector2f(195, 175)));
//top middle
vertices.append(sf::Vertex(sf::Vector2f(200, 170)));
//top right corner
vertices.append(sf::Vertex(sf::Vector2f(205, 175)));
//bottom right corner
vertices.append(sf::Vertex(sf::Vector2f(205, 195)));
//rotation is the shape's rotation... 0 means it's straight up, and it rotates clockwise with positive rotation
float rotation = 0;
//used later to pause the program
bool keypressed = false;
while(window.isOpen()){
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)){
window.close();
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)){
//this SHOULD rotate the shape by 10 degrees, however it rotates it by like 150-ish
//why does this not work as expected?
rotation = 10;
//this transformation part works fine, it simply moves the points to center them on the origin, rotates them using a rotation matrix, and moves
//them back by their offset
for(int i = 1; i < vertices.getVertexCount(); i++){
//translate current point so that the origin is centered
vertices[i].position -= vertices[0].position;
//rotate points
//I'm guessing this is the part where the rotation value is screwing up?
//because rotation is simply theta in a regular trig function, so the shape should only rotate 10 degrees
float newPosX = vertices[i].position.x * cosf(rotation) + vertices[i].position.y * sinf(rotation);
float newPosY = -vertices[i].position.x * sinf(rotation) + vertices[i].position.y * cosf(rotation);
//translate points back
vertices[i].position.x = newPosX + vertices[0].position.x;
vertices[i].position.y = newPosY + vertices[0].position.y;
}
keypressed = true;
}
//draw
window.clear();
window.draw(vertices);
window.display();
if(keypressed == true){
//breakpoint here so the points only rotate once
system("pause");
}
}
return 0;
}
Also, here are the screenshots showing what I mean. Sorry it's a bit small. The left side shows the shape created at the start of the program, with the green point being the origin. The right side shows the shape after the rotation for loop is called, with the red points showing where the shape actually rotates to (definitely not 10 degrees) versus the blue dots, which are roughly where I expected the shape to be at, around 10 degrees.
tl;dr: Using a rotation matrix, the points being rotated keep their proportions, but the amount by which they are rotating is totally arbitrary. Any suggestions/improvements?

Using the SFML, you first create a transformation :
sf::Transform rotation;
rotation.rotate(10, centerOfRotationX, centerOfRotationY);
Then you apply this transformation to the position of each vertex :
sf::Vector2f positionAfterRotation = rotation.transformPoint(positionBeforeRotation);
Sources : sf::Transform::rotate and sf::Transform::transformPoint.

Related

Why is the screen space coordinate system for my sfml rendered app inverted?

I am learning C++ and I thought I'd make the original asteroids game with a fresh coat of paint using the SFML graphics library. However, for my player sprite, while the origin is at the top left corner of the screen, to the right of it is the negative x axis and downwards is negative y axis (opposite of what it's supposed to be in both cases). Also, no matter what object or rotation, invoking the setRotation function always rotates any object about the top left corner of the screen even if, for that object, I have set the origin to the object's center.
#include<SFML\Graphics.hpp>
using namespace sf;
const int W{ 1200 }, H{ 800 };
const float degToRad = 0.017453f;
int main() {
float x{ -600 }, y{ -400 };
float dx{}, dy{}, angle{};
bool thrust;
RenderWindow app(VideoMode(W, H), "Asteroids!");
app.setFramerateLimit(60);
Texture t1, t2;
t1.loadFromFile("images/spaceship.png");
t2.loadFromFile("images/background.jpg");
Sprite sPlayer(t1), sBackground(t2);
sPlayer.setTextureRect(IntRect(40, 0, 40, 40));
sPlayer.setOrigin(-600, -400);
while (app.isOpen())
{
app.clear();
app.draw(sPlayer);
app.display();
}
return 0;
}
The above code draws the player (spaceship.png) to the center of my rendered window (app) but notice how I have had to put in negative coordinates. Also, if I further put in the code for taking keyboard inputs and call the setRotation function, instead of rotating my sPlayer sprite about its center (i.e. (-600,-400)), it rotates the sprite about the top left corner of the screen which is (0,0). I can't find any explanation for this in the SFML online documentation. What should I do?
As I mentioned I have tried reading the documentation. I've watched online tutorials but to no avail.
Origin is the point on sprite where you "hold" it.
Position is the point on screen where you put Origin of the sprite.
In short, you take your sprite by Origin and put it so Origin is on Position.
By default, both Origin and Position are (0, 0), so top left of your sprite is put at top left of the screen. What you did was to say "take this point on sprite, which is way to the upper-left that actual visible part of the sprite is and put it to the top left of the screen". This had an effect of moving your sprite to the bottom right.
You probably want something like:
// This is will make sure that Origin, i.e. point which defines rotation and other transformations center is at center of the ship
sPlayer.setOrigin(sprite_width / 2, sprite_height / 2);
// This will put Origin (center of the ship) at center of the screen
sPlayer.setPosition(screen_width / 2, screen_height / 2);

Drawing a sprite on the circumference of a circle based on the position of other objects

I'm making a sniper shooter arcade style game in Gamemaker Studio 2 and I want the position of targets outside of the viewport to be pointed to by chevrons that move along the circumference of the scope when it moves. I am using trig techniques to determine the coordinates but the chevron is jumping around and doesn't seem to be pointing to the target. I have the code broken into two: the code to determine the coordinates in the step event of the enemies class (the objects that will be pointed to) and a draw event in the same class. Additionally, when I try to rotate the chevron so it also points to the enemy, it doesn't draw at all.
Here's the coordinate algorithm and the code to draw the chevrons, respectively
//determine the angle the target makes with the player
delta_x = abs(ObjectPlayer.x - x); //x axis displacement
delta_y = abs(ObjectPlayer.y - y); //y axis displacement
angle = arctan2(delta_y,delta_x); //angle in radians
angle *= 180/pi //angle in radians
//Determine the direction based on the larger dimension and
largest_distance = max(x,y);
plusOrMinus = (largest_distance == x)?
sign(ObjectPlayer.x-x) : sign(ObjectPlayer.y-y);
//define the chevron coordinates
chevron_x = ObjectPlayer.x + plusOrMinus*(cos(angle) + 20);
chevron_y = ObjectPlayer.y + plusOrMinus*(sign(angle) + 20);
The drawing code
if(object_exists(ObjectEnemy)){
draw_text(ObjectPlayer.x, ObjectPlayer.y-10,string(angle));
draw_sprite(Spr_Chevron,-1,chevron_x,chevron_y);
//sSpr_Chevron.image_angle = angle;
}
Your current code is slightly more complex that it needs to be for this, if you want to draw chevrons pointing towards all enemies, you might as well do that on spot in Draw. And use degree-based functions if you're going to need degrees for drawing anyway
var px = ObjectPlayer.x;
var py = ObjectPlayer.y;
with (ObjectEnemy) {
var angle = point_direction(px, py, x, y);
var chevron_x = px + lengthdir_x(20, angle);
var chevron_y = py + lengthdir_y(20, angle);
draw_sprite_ext(Spr_Chevron, -1, chevron_x, chevron_y, 1, 1, angle, c_white, 1);
}
(also see: an almost-decade old blog post of mine about doing this while clamping to screen edges instead)
Specific problems with your existing code are:
Using a single-axis plusOrMinus with two axes
Adding 20 to sine/cosine instead of multiplying them by it
Trying to apply an angle to sSpr_Chevron (?) instead of using draw_sprite_ext to draw a rotated sprite.
Calculating largest_distance based on executing instance's X/Y instead of delta X/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.

opengl - Rotating around a sphere using vectors and NOT glulookat

I'm having an issue with drawing a model and rotating it using the mouse,
I'm pretty sure there's a problem with the mathematics but not sure .
The object just rotates in a weird way.
I want the object to start rotating each click from its current spot and not reset because of the
vectors are now changed and the calculation starts all over again.
void DrawHandler::drawModel(Model * model){
unsigned int l_index;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW); // Modeling transformation
glLoadIdentity();
Point tempCross;
crossProduct(tempCross,model->getBeginRotate(),model->getCurrRotate());
float tempInner= innerProduct(model->getBeginRotate(),model->getCurrRotate());
float tempNormA =normProduct(model->getBeginRotate());
float tempNormB=normProduct(model->getCurrRotate());
glTranslatef(0.0,0.0,-250.0);
glRotatef(acos (tempInner/(tempNormA*tempNormB)) * 180.0 / M_PI,tempCross.getX(),tempCross.getY(),tempCross.getZ());
glColor3d(1,1,1);
glBegin(GL_TRIANGLES);
for (l_index=0;l_index < model->getTrianglesDequeSize() ;l_index++)
{
Triangle t = model->getTriangleByPosition(l_index);
Vertex a1 = model->getVertexByPosition(t.getA());
Vertex a2 = model->getVertexByPosition(t.getB());
Vertex a3 = model->getVertexByPosition(t.getC());
glVertex3f( a1.getX(),a1.getY(),a1.getZ());
glVertex3f( a2.getX(),a2.getY(),a2.getZ());
glVertex3f( a3.getX(),a3.getY(),a3.getZ());
}
glEnd();
}
This is the mouse function which saves the beginning vector of the rotating formula
void Controller::mouse(int btn, int state, int x, int y)
{
x=x-WINSIZEX/2;
y=y-WINSIZEY/2;
if (btn==GLUT_LEFT_BUTTON){
switch(state){
case(GLUT_DOWN):
if(!_rotating){
_model->setBeginRotate(Point(float(x),float(y),
(-float(x)*x - y*y + SPHERERADIUS*SPHERERADIUS < 0)? 0:float(sqrt(-float(x)*x - y*y + SPHERERADIUS*SPHERERADIUS))));
_rotating=true;
}
break;
case(GLUT_UP):
_rotating=false;
break;
}
}
}
and finally the following function which holds the current vector.
(the beginning vector is where the mouse was clicked at
and the curr vector is where the mouse position at the moment )
void Controller::getMousePosition(int x,int y){
x=x-WINSIZEX/2;
y=y-WINSIZEY/2;
if(_rotating){
_model->setCurrRotate(Point(float(x),float(y),
(-float(x)*x - y*y + SPHERERADIUS*SPHERERADIUS < 0)? 0:float(sqrt(-float(x)*x - y*y + SPHERERADIUS*SPHERERADIUS))));
}
}
where sphereradius is the sphere radius O_O of 70 degress
is any calculation wrong ? cant seem to find the problem...
thanks
Why so complicated? Either you change the view matrix or you change the model matrix of your focused object. If you choose to change the model matrix and your object is centered in (0,0,0) of the world coordinate system, computing the rotation around a sphere illusion is trivial - you just rotate into the opposite direction. If you want to change the view matrix (which is actually done when you change the position of the camera) you have to approximate the surface points on the chosen sphere. Therefore, you could introduce two parameters specifying two angles. Everytime you click move your mouse, you update the params and compute the new locations on the sphere. There are some useful equations in [http://en.wikipedia.org/wiki/Sphere].
Without knowing what library (or libraries) you're using your code is rather difficult to read. It seems you're setting up your camera at (0, 0, -250), looking towards the origin, then rotating around the origin by the angle between two vectors, model->getCurrRotate() and model->getBeginRotate().
The problem seems to be that in "mouse down" events you explicitly set BeginRotate to the point on the sphere under the mouse, then in "mouse move" events you set CurrRotate to the point under the mouse, so every time you click somewhere else, you lose the previous state of rotation because BeginRotate and CurrRotate are simply overwritten.
Combining multiple rotations around arbitrary different axes is not a trivially simple task. The proper way to do it is to use quaternions. You may find this primer on quaternions and other 3D math concepts useful.
You might also want a more robust algorithm for converting screen coordinates to model coordinates on the sphere. The one you are using is assuming the sphere appears 70 pixels in radius on the screen and that the projection matrix is orthographic.

Rotating CCSprite thats an equilateral triangle

I have an CCSprite thats an equilateral triangle. I want to rotate the triangle in 60 degree increments holding its position.
The sprite is 126x110 (not square) setting the sprites rotation property in 60 degree increments changes the position of the sprite. How can i keep the sprite appear stationary at each rotation?
A bit more about this the center of the rectangle IS NOT the center of the sprite. So there is some adjustment needed when the rotation is needed to keep the center of the triangle appearing centered.
I think i came up with a long answer that needs to be approved..
// collect points.
self.point1 = CGPointMake(CGRectGetWidth(self.tile.boundingBox)/2, 0);
self.point2 = CGPointMake(CGRectGetWidth(self.tile.boundingBox), CGRectGetHeight(self.tile.boundingBox));
self.point3 = CGPointMake(0, CGRectGetHeight(self.tile.boundingBox));
// calculcate the mid point.
float midPointX = floor((self.point1.x + self.point2.x + self.point3.x)/3.0);
float midPointY = floor((self.point1.y + self.point2.y + self.point3.y)/3.0);
// stash the center of the triangle
self.triangleCenter = CGPointMake(midPointX, midPointY);
Then figure out new location of the center point based on rotation.. And animate there. (Sorry about hard coded center of the screen this was a rough test).
-(void) rotateToAngleAboutCenter:(float)angle {
// stash old values.
CGPoint oldPosition = self.tile.position;
float oldRotation = self.tile.rotation;
// reset the rotation
self.tile.rotation = 0;
// this is hard coded here currently the center of the screen.
self.tile.position = ccp(512, 384);
// figure out where our center point will be when we are rotated about the center.
CGPoint point = ccpRotateByAngle(self.triangleCenter, [self.tile anchorPointInPoints],-CC_DEGREES_TO_RADIANS(angle));
// convert the ppoint to local space.
point = [self.tile convertToWorldSpace:point];
point = [self convertToNodeSpace:point];
// reset the rotation and position.
self.tile.rotation = oldRotation;
self.tile.position = oldPosition;
// animate to new rotation/position.
CCMoveTo* moveTo = [CCMoveTo actionWithDuration:.25 position:point];
CCRotateTo* rotateTo = [CCRotateTo actionWithDuration:.25 angle:angle];
[self.tile runAction:moveTo];
[self.tile runAction:rotateTo];
}