Moving a circle in an elliptical pattern in tkinter - python

Okay so I'd like to make a circle move in an elliptical pattern with a button in tkinter. I previously made it move back and forth 10px at a time but I have no idea how to make it go in an ellipse
My back and forth code looks like this:
from Tkinter import *
def ball(gd, hb):
global x1, y1
x1, y1 = x1+gd, y1+hb
can1.coords(oval1,x1, y1, x1+30, y1+30)
def move():
global direction
if x1 + 30 == 250:
direction = -1
elif x1 == 0:
direction = 1
ball(direction*10, 0)
x1 , y1, direction = 0, 125, 1
root = Tk()
can1 = Canvas(root,height = 250, width =250, bg = 'black')
oval1= can1.create_oval(x1,y1,x1+30,y1+30, width=2, fill='orange')
can1.pack()
Button(root, text ='Move Ball', command = move).pack()
root.mainloop()
Any ideas would help me, I just need to be pointed in the right direction

Option 1
Okay so if you have meant that you just want to have an elliptical shape all the time you can use x1,y1,x1+60,y1+30 instead of x1+30,y1+30.
Option 2
If you have meant to reshape the ball like the ball bounces you have to think about some factors.
When should it happen. => Direction change
What do I have to modify => based on that: how a oval shape is defined you can easily say that if the ball touches the max width ( in your case x = 250) that you have to stretch out the opposite coordinates (oval1,x1, y1, x1+60, y1+30), with y1 ignored because you are just moving on the x-axis.
How do I stretch => See my Image
It also shows that you may use the sinus to achieve what you want. Like adding him on top of the desired coordinates. So everywhere the "Point" should move faster adding the sinus on top might does the trick.
That was my idea. Just a quick one. Maybe tomorrow I will find a better one. If you have questions just comment.

Related

Scaling QPolygon on its origin

I'm trying to scale a QPolygonF that is on a QGraphicsScene's QGraphicsView on its origin.
However, even after translating the polygon (poly_2) to its origin (using QPolygon.translate() and the center coordinates of the polygon received via boundingRect (x+width)/2 and (y+height)/2), the new polygon is still placed on the wrong location.
The blue polygon should be scaled according to the origin of poly_2 (please see the image below, black is the original polygon, blue polygon is the result of the code below, and the orange polygon is representing the intended outcome)
I thought that the issue might be that coordinates are from global and should be local, yet this does solve the issue unfortunately.
Here's the code:
import PyQt5
from PyQt5 import QtCore
import sys
import PyQt5
from PyQt5.QtCore import *#QPointF, QRectF
from PyQt5.QtGui import *#QPainterPath, QPolygonF, QBrush,QPen,QFont,QColor, QTransform
from PyQt5.QtWidgets import *#QApplication, QGraphicsScene, QGraphicsView, QGraphicsSimpleTextItem
poly_2_coords= [PyQt5.QtCore.QPointF(532.35, 274.98), PyQt5.QtCore.QPointF(525.67, 281.66), PyQt5.QtCore.QPointF(518.4, 292.58), PyQt5.QtCore.QPointF(507.72, 315.49), PyQt5.QtCore.QPointF(501.22, 326.04), PyQt5.QtCore.QPointF(497.16, 328.47), PyQt5.QtCore.QPointF(495.53, 331.71), PyQt5.QtCore.QPointF(488.24, 339.02), PyQt5.QtCore.QPointF(480.94, 349.56), PyQt5.QtCore.QPointF(476.09, 360.1), PyQt5.QtCore.QPointF(476.89, 378.76), PyQt5.QtCore.QPointF(492.3, 393.35), PyQt5.QtCore.QPointF(501.22, 398.21), PyQt5.QtCore.QPointF(527.17, 398.21), PyQt5.QtCore.QPointF(535.28, 390.1), PyQt5.QtCore.QPointF(540.96, 373.89), PyQt5.QtCore.QPointF(539.64, 356.93), PyQt5.QtCore.QPointF(541.46, 329.0), PyQt5.QtCore.QPointF(543.39, 313.87), PyQt5.QtCore.QPointF(545.83, 300.89), PyQt5.QtCore.QPointF(545.83, 276.56), PyQt5.QtCore.QPointF(543.39, 267.64), PyQt5.QtCore.QPointF(537.81, 268.91)]
def main():
app = QApplication(sys.argv)
scene = QGraphicsScene()
view = QGraphicsView(scene)
pen = QPen(QColor(0, 20, 255))
scene.addPolygon(QPolygonF(poly_2_coords))
poly_2 = QPolygonF(poly_2_coords)
trans = QTransform().scale(1.5,1.5)
#poly_22 = trans.mapToPolygon(QRect(int(poly_2.boundingRect().x()),int(poly_2.boundingRect().y()),int(poly_2.boundingRect().width()),int(poly_2.boundingRect().height())))
#trans.mapToPolygon()
#scene.addPolygon(QPolygonF(poly_22),QPen(QColor(0, 20, 255)))
poly_2.translate((poly_2.boundingRect().x()+poly_2.boundingRect().width())/2,(poly_2.boundingRect().y()+poly_2.boundingRect().height())/2)
print(f'poly_2.boundingRect().x() {poly_2.boundingRect().x()}+poly_2.boundingRect().width(){poly_2.boundingRect().width()}')
trans = QTransform().scale(1.4,1.4)
#poly_2.setTransformOriginPoint()
poly_22 = trans.map(poly_2)
scene.addPolygon(poly_22,QPen(QColor(0, 20, 255)))
view.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Edit: I've tried saving the polygon as a QGraphicsItem, and set its transformation origin point according the bbox's middle X,Y and then mapped from Global to Scene, yet no luck: the new polygon is still drawn to the wrong place.
poly_2 = QPolygonF(poly_2_coords)
poly = scene.addPolygon(poly_2)
point = QPoint((poly_2.boundingRect().x()+poly_2.boundingRect().width())/2,(poly_2.boundingRect().y()+poly_2.boundingRect().height())/2)
poly.setTransformOriginPoint(point)
poly.setScale(3)
If replacing point to equal only X,Y of the bounding rectangle, the result seems to be closer to what I need. However, in this case the origin point is obviously wrong. Is this just random luck that this answer seems to be closer to what I need?
Before considering the problem of the translation, there is a more important aspect that has to be considered: if you want to create a transformation based on the center of a polygon, you must find that center. That point is called centroid, the geometric center of any polygon.
While there are simple formulas for all basic geometric shapes, finding the centroid of a (possibly irregular) polygon with an arbitrary number of vertices is a bit more complex.
Using the arithmetic mean of vertices is not a viable option, as even in a simple square you might have multiple points on a single side, which would move the computed "center" towards those points.
The formula can be found in the Wikipedia article linked above, while a valid python implementation is available in this answer.
I modified the formula of that answer in order to accept a sequence of QPoints, while improving readability and performance, but the concept remains the same:
def centroid(points):
if len(points) < 3:
raise ValueError('At least 3 points are required')
# https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
# https://en.wikipedia.org/wiki/Shoelace_formula
# computation uses concatenated pairs from the sequence, with the
# last point paired to the first one:
# (p[0], p[1]), (p[1], p[2]) [...] (p[n], p[0])
area = cx = cy = 0
p1 = points[0]
for p2 in points[1:] + [p1]:
shoelace = p1.x() * p2.y() - p2.x() * p1.y()
area += shoelace
cx += (p1.x() + p2.x()) * shoelace
cy += (p1.y() + p2.y()) * shoelace
p1 = p2
A = 0.5 * area
factor = 1 / (6 * A)
return cx * factor, cy * factor
Then, you have two options, depending on what you want to do with the resulting item.
Scale the item
In this case, you create a QGraphicsPolygonItem like the original one, then set its transform origin point using the formula above, and scale it:
poly_2 = QtGui.QPolygonF(poly_2_coords)
item2 = scene.addPolygon(poly_2, QtGui.QPen(QtGui.QColor(0, 20, 255)))
item2.setTransformOriginPoint(*centroid(poly_2_coords))
item2.setScale(1.5)
Use a QTransform
With Qt transformations some special care must be taken, as scaling always uses 0, 0 as origin point.
To scale around a specified point, you must first translate the matrix to that point, then apply the scale, and finally restore the matrix translation to its origin:
poly_2 = QtGui.QPolygonF(poly_2_coords)
cx, cy = centroid(poly_2_coords)
trans = QtGui.QTransform()
trans.translate(cx, cy)
trans.scale(1.5, 1.5)
trans.translate(-cx, -cy)
poly_2_scaled = trans.map(poly_2)
scene.addPolygon(poly_2_scaled, QtGui.QPen(QtGui.QColor(0, 20, 255)))
This is exactly what QGraphicsItems do when using the basic setScale() and setRotation() transformations.
Shape origin point and item position
Remember that QGraphicsItems are always created with their position at 0, 0.
This might not seem obvious especially for basic shapes: when you create a QGraphicsRectItem giving its x, y, width, height, the position will still be 0, 0. When dealing with complex geometry management, it's usually better to create basic shapes with the origin/reference at 0, 0 and then move the item at x, y.
For complex polygons like yours, a possibility could be to translate the centroid of the polygon at 0, 0, and then move it at the actual centroid coordinates:
item = scene.addPolygon(polygon.translated(-cx, -cy))
item.setPos(cx, cy)
item.setScale(1.5)
This might make things easier for development (the mapped points will always be consistent with the item position), and the fact that you don't need to change the transform origin point anymore makes reverse mapping even simpler.

Moving the cursor in relation to a certain fix-point in Python

is there any possibility to move the cursor in Python with % starting from a certain coordinate like (1,1)?
I am using pyautogui atm to automate and I thought it would be quite convenient if this is independent from the monitor size making it universal.
Thanks for your help in advance!
It's possible indirectly. As detailed at the top of the Mouse Control Functions page of the documentation, you can get the screen size using the size() function (which returns a tuple of (X, Y)). You can then do the math to figure out how many screen pixels equal the percentage you're looking for, then call moveTo() to go there.
# script to move mouse 50% to the right and down
import pyautogui as pag
percentage = 0.5
cur_X, cur_Y = pag.position() # current X and Y coordinates
size_X, size_Y = pag.size() # screen size
goto_X = (size_X - cur_X) * percentage + cur_X # current location plus half
goto_Y = (size_Y - cur_Y) * percentage + cur_Y # the distance to the edge
pag.moveTo(goto_X, goto_Y, 1) # move to new position, taking 1 second

python gluLookAt for first person camera

i'm new to openGL and i'm trying to move the camera as a first person shooter game. i want to use gluLookAt for movement and looking around the scene, but i can't figure out the camera part
gl.glMatrixMode(gl.GL_MODELVIEW)
gl.glLoadIdentity()
glu.gluLookAt(current_player.position[0], current_player.position[1] ,
current_player.position[2], look_at_position[0], look_at_position[1], 0,
0, 1 ,0)
the look_at_position is the mouse position but i can't calculate the last value so i put temporarily as 0
i just want to know how to move the player and the camera using the glLookAt.
Works the same as glm::lookAt(). First argument is the position you are viewing from (you are correct), then the position you are looking at, and then the up vector (also correct). Here's what I invoke:
//this code is in the mouse callback, both yaw and pitch are mouse inputs
glm::vec3 front;
glm::vec3 right;
front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
front.y = sin(glm::radians(pitch));
front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
cameraFront = glm::normalize(front);
front.x = cos(glm::radians(yaw));
front.z = sin(glm::radians(yaw));
movementFront = glm::normalize(front);
//this is in int main()
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);

Rotate Rect along line

First project in qt.
I'm having trouble translating/rotating a rect along a line. Basically i would want to align the rect with the position of the line. When i change position of the circle the rect should translate along the line. See images below.
What i have at the moment
w_len = len(str(weight)) / 3 * r + r / 3
weight_v = Vector(r if w_len <= r else w_len, r)
weight_rectangle = QRectF(*(mid - weight_v), *(2 * weight_v))
painter.drawRect(weight_rectangle)
*mid is just a vector with coordinates at half of the link , weight_v is a vector based on the text size.
Any pointers , should i look at adding a translate to the painter ? Whenever i try to add translation to the painter it breaks the other shapes as well.
t = QTransform()
t.translate(-5 ,-5)
t.rotate(90)
painter.setTransform(t)
painter.drawRect(weight_rectangle)
painter.resetTransform()
Update:
With below answer i was able to fix the rotation. Many thanks, looks like my text is not displaying correctly.
I have the following code:
painter.translate(center_of_rec_x, center_of_rec_y);
painter.rotate(- link_paint.angle());
rx = -(weight_v[0] * 0.5)
ry = -(weight_v[1] )
new_rec = QRect(rx , ry, weight_v[0], 2 * weight_v[1])
painter.drawRect(QRect(rx , ry, weight_v[0] , 2 * weight_v[1] ))
painter.drawText(new_rec, Qt.AlignCenter, str(weight))
Update2:
All is fine , was a mistake in my code. I was taking the wrong link angle.
Thx.
Rotation is always done according to the origin point (0, 0), so you need to translate to the origin point of the rotation and then apply it.
Also, when applying any temporary change to the painter, save() and restore() should be used: in this way the current state of the painter is stored, and that state will be restored afterwards (including any transformation applied in the meantime). Painter states can be nested, and one could save multiple times to apply multiple "layers" of painter state modifications. Just remember that the all states must be restored to the base status before releasing (ending) the painter.
Since you didn't provide an MRE, I created a small widget to show how this works:
class AngledRect(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.setMinimumSize(200, 200)
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
contents = self.contentsRect()
# draw a line from the top left to the bottom right of the widget
line = QtCore.QLineF(contents.topLeft(), contents.bottomRight())
qp.drawLine(line)
# save the current state of the painter
qp.save()
# translate to the center of the painting rectangle
qp.translate(contents.center())
# apply an inverted rotation, since the line angle is counterclockwise
qp.rotate(-line.angle())
# create a rectangle that is centered at the origin point
rect = QtCore.QRect(-40, -10, 80, 20)
qp.setPen(QtCore.Qt.white)
qp.setBrush(QtCore.Qt.black)
qp.drawRect(rect)
qp.drawText(rect, QtCore.Qt.AlignCenter, '{:.05f}'.format(line.angle()))
qp.restore()
# ... other painting...
For simple transformations, using translate and rotate is usually enough, but the above is almost identical to:
transform = QtGui.QTransform()
transform.translate(contents.center().x(), contents.center().y())
transform.rotate(-line.angle())
qp.save()
qp.setTransform(transform)
# ...

Move turtle slightly closer to random coordinate on each update

I'm doing a homework and I want to know how can I move turtle to a random location a small step each time. Like can I use turtle.goto() in a slow motion?
Someone said I should use turtle.setheading() and turtle.forward() but I'm confused on how to use setheading() when the destination is random.
I'm hoping the turtle could move half radius (which is 3.5) each time I update the program to that random spot.
You use the term half radius twice in your question's title and text, but never really explain it. For purposes of your question, we're just talking about some arbitrary small distance -- correct?
I would avoid import time and time.sleep() as they work against an event-driven world like turtle. Instead, I would use turtle's own ontimer() method to keep things in synch:
from turtle import Screen, Turtle
from random import randrange
HALF_RADIUS = 3.5 # unexplained constant
DELAY = 1000 # milliseconds
WIDTH, HEIGHT = 640, 480
CURSOR_SIZE = 20
def forward_slowly(distance):
if distance > 0:
turtle.forward(min(distance, HALF_RADIUS))
remaining = max(distance - HALF_RADIUS, 0)
screen.ontimer(lambda d=remaining: forward_slowly(d), DELAY)
else:
screen.ontimer(move_target, DELAY)
def move_target():
x = randrange(CURSOR_SIZE - WIDTH//2, WIDTH//2 - CURSOR_SIZE)
y = randrange(CURSOR_SIZE - HEIGHT//2, HEIGHT//2 - CURSOR_SIZE)
target.goto(x, y)
target.pendown()
turtle.setheading(turtle.towards(target))
forward_slowly(turtle.distance(target))
screen = Screen()
screen.setup(WIDTH, HEIGHT)
turtle = Turtle('turtle')
turtle.speed('slowest')
turtle.width(3)
target = Turtle('turtle')
target.speed('fastest')
target.color('red')
target.penup()
move_target()
screen.exitonclick()
(Any resemblence to a Pepé Le Pew cartoon is purely coincidental.)
Do you mean that you want to move a small step, stop, and repeat? If so, you can ‘import time’ and add ‘time.sleep(0.1)’ after each ‘forward’

Categories