This assignment is asking me to draw a star function with four parameters.
"center point of the star
size of the star
color of the lines of the star
window used to draw the star"
This is the example picture given: http://i.stack.imgur.com/urvt2.jpg
The hint: given the center point, you can clone it and move it to create each of the 5 points (ex:p1 = cenpt.clone(), p1.move(0,-0.85*size))
I misinterpreted the prompt in the first attempt and so far, I've mostly hard coded.
import graphics
def main():
window= graphics.GraphWin("x", 600, 400)
center = graphics.Point(300, 200)
center.setFill("red")
center.draw(window)
p1 = center.clone()
p1.move(0,-110)
p1.setFill('red')
p1.draw(window)
p2 = p1.clone()
p2.move(150, 250)
p2.setFill('red')
p2.draw(window)
line1 = graphics.Line(p1,p2)
line1.setFill("black")
line1.draw(window)
window.getMouse()
main()
obviously this doesn't work for function purposes. How could I modify this to work for given parameters in a function?
If I were you, I'd define a function which given the coordinates and center points, would draw a point at the desired coordinates.
Something like this:
def makePoint (point, x, y):
newpoint = point.clone()
newpoint.move(x-300, y-200)
newpoint.setfill('red')
return newpoint
and then call that function once per point you want. If I wanted to use this to draw a point at coordinates 400, 500 I would call it like this: makePoint(center, 400, 500).draw(window)
You could also make one that just drew a new point at coordinates x and y instead of copying and moving a single point fairly easily, but it doesnt sound like thats what your instructor wants.
Related
The complex transformation of the line should map the line to a circle passing through the origin, also the complex transformation of the circle centered at 1,0,0 of radius 1 should map to a line but it is behaving weirdly.
from manim import *
#config['frame_height'] = 10.0
#config['frame_width'] = 10.0
class Method(Scene):
def construct(self):
text = Tex(r"Applying Complex Transformations")
self.play(Create(text))
class Complex(Scene):
def construct(self):
d=ComplexPlane()
k=d.copy()
self.play(Create(d))
self.add(k)
line = Line(start=[2,0,0],end=[2,5,0],stroke_width=3,color=RED)
circle = Circle().shift(RIGHT)
self.add(line)
c = Circle().shift(RIGHT)
self.play(c.animate.apply_complex_function(lambda z:z**2)) #works correctly
self.play(line.animate.apply_complex_function(lambda z:1/z),run_time=5) #behaving wierdly
self.play(circle.animate.apply_complex_function(lambda z:1/z),run_time=5) #behaving wierdly
applied to line
applied to circle
Well the function 1/z diverges at z=0, therefore, every point that starts at that position (or near it) will have problems and give you weird artifacts. I tried your code and the line seems fine to me, but the circle is the one that might do weird stuff, and it just so happens that the circle goes through (0,0) in the complex plane. Try Shifting it some more so that it doesn't happen.
circle = Circle().shift(RIGHT*1.5)
you can also of course, change the function so that it doesn't diverge at z=0. Something like 1/(z+2) should work
I have looked around for an answer to this but cannot seem to find a solution. I'd like the word function to create 10 identical circles with center points that have the same y coordinates but different x coordinates so that their spacing is 25 from one center point to the other. The functions that I have created are only drawing one iteration of the object and I cannot figure out how to fix this. Thank you for any help.
I have tried to create two separate functions. One defines the loop function that I would like to print 10 circles while appending a list of circles. The other function calls upon the draw function to draw all 13 circles.
def draw(window):
circles=[]
for i in range(10):
x=25
circle=Circle(Point(0+x,370),10)
circle.setFill("yellow")
circles.append(circle)
circle.draw(window)
return circles
def circleRow():
window=GraphWin("Window",400,400)
window.setBackground("red")
cicles1=draw(window)
circleRow()
I expected an output of 10 circles evenly spaced along the same y-coordinate but am only getting a single circle.
We could replace the (unused) iteration variable i with x instead and explicitly describe what range of values it should take on:
from graphics import *
def draw(window):
circles = []
for x in range(25, 275, 25):
circle = Circle(Point(x, 370), 10)
circle.setFill('yellow')
circle.draw(window)
circles.append(circle)
return circles
def circleRow():
window = GraphWin("Window", 400, 400)
window.setBackground('red')
circles = draw(window)
window.getMouse()
window.close()
circleRow()
There are various other valid ways to approach this.
I am trying to output a square, and am getting a rather distorted rhombus. Like so,
And though I can tell that this is in fact the cube I had intended, the cube is strangely distorted. In my own workings to create a simple 3D projection program, I found a similar problem when I lacked the offset of 2D points to the middle of the screen, however I know of no such way to inform OpenGL of this offset...
For anyone who may be wondering, my current camera object looks like [in python]:
class Camera:
def __init__(self,x,y,z,fov=45,zNear=0.1,zFar=50):
self.x,self.y,self.z = x,y,z
self.pitch,self.yaw,self.roll = 0,0,0
glMatrixMode(GL_PROJECTION)
gluPerspective(fov, 1, zNear, zFar)
def __goto__(self,x,y,z,pitch,yaw,roll):
print "loc:",x,y,z
print "ang:",pitch,yaw,roll
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glRotatef(pitch,1,0,0)
glRotatef(yaw,0,1,0)
glRotatef(roll,0,0,1)
glTranslatef(-x,-y,-z)
def __flushloc__(self):
self.__goto__(self.x,self.y,self.z,self.pitch,self.yaw,self.roll)
with a cube being rendered in the following manner:
class Cube:
def __init__(self,x,y,z,width):
self.vertices=[]
for x in [x,x+width]:
for y in [y,y+width]:
for z in [z,z+width]:
self.vertices.append((x,y,z))
self.faces = [
[0,1,3,2],
[4,5,7,6],
[0,2,6,4],
[1,3,7,5],
[0,1,5,4],
[2,3,7,6]]
def __render__(self):
glBegin(GL_QUADS)
for face in self.faces:
for vertex in face:
glVertex3fv(self.vertices[vertex])
glEnd()
Perhaps I should also mention that the given window is 400px by 400px, thence the aspect ratio is 1.
Two things:
I suspect the "distortion" you're seeing is likely a normal effect of the perspective projection matrix; a square in 3D space can render as a weird rhombus shape in 2D when perspective is applied. It's hard to tell in this case what the expected output should be, because you haven't included the coordinates of the camera and the cube. However, if you used an orthogonal projection (via gluOrtho2D), my guess is that it would come out to be a (rotated, translated, and squashed) parallelogram.
Second, I think you're only seeing a single face of your cube displayed. The picture might make more sense if you could see the other visible faces of the cube, but your vertices list is getting trashed because you've reused the x,y,z variables in Cube.__init__.
If you fix this name collision, does your render get better? You might try renaming one set of x,y,z to something else, like this:
def __init__(self,x,y,z,width):
self.vertices=[]
for xv in [x,x+width]:
for yv in [y,y+width]:
for zv in [z,z+width]:
self.vertices.append((xv,yv,zv))
I am trying to create a map editor. I intend the map to be an hexagonal grid where each hexagon is a tile of the map. A tile will be a graphical representation of that area (sea, meadow, desert, mountain, etc). The map is intended to be of any size. Let's freeze the requirements here for now :)
I want to use PyQt4 (take it as a design requirement). As I am just starting with Qt/PyQt, I am facing the problem of vastness: so big this Qt thing that I cannot grasp it all. And here I am, asking for your kind and most welcome experience.
After a bit of googling, I've decided to use the QGraphicalView/Scene approach. In fact, I was thinking about creating my own hexgrid class inheriting from QGraphicalView and creating my RegularPolygon class inheriting from QGraphicalPolygonItem.
Now they come the doubts and problems.
My main doubt is "Is my approach a correct one?" Think about the needs I have explained at the beginning of the post: hexagonal map, where each hexagon will be a tile of a given type (sea, desert, meadows, mountains, etc). I am concerned about performance once the editor works (scrolling will feel nice? and this kind of things).
And so far, the problem is about precision. I am drawing the hexgrid by creating and drawing all its hexagons (this even sounds bad to me... thinking about performance). I used some formulas to calculate the vertices of each hexagon and creating the polygon from there. I expect the sides of two consecutive hexagons to coincide exactly at the same location, but the rounding seems to be playing a bit with my desires, as sometimes the hexagon sides perfectly matches in the same location (good) and sometimes they do not match by what seems to be 1 pixel difference (bad). This gives a poor visual impression of the grid. Maybe I have not explained myself quite well... it's better if I give you the code and you run it by yourselves
So summarizing:
Do you think my approach will give future performance issues?
Why are not the hexagons placed exactly so that they share sides? How to avoid this problem?
The code:
#!/usr/bin/python
"""
Editor of the map.
"""
__meta__ = \
{
(0,0,1): (
[ "Creation" ],
[ ("Victor Garcia","vichor#xxxxxxx.xxx") ]
)
}
import sys, math
from PyQt4 import QtCore, QtGui
# ==============================================================================
class HexGrid(QtGui.QGraphicsView):
"""
Graphics view for an hex grid.
"""
# --------------------------------------------------------------------------
def __init__(self, rect=None, parent=None):
"""
Initializes an hex grid. This object will be a GraphicsView and it will
also handle its corresponding GraphicsScene.
rect -- rectangle for the graphics scene.
parent -- parent widget
"""
super(HexGrid,self).__init__(parent)
self.scene = QtGui.QGraphicsScene(self)
if rect != None:
if isinstance(rect, QtCore.QRectF): self.scene.setSceneRect(rect)
else: raise StandardError ('Parameter rect should be QtCore.QRectF')
self.setScene(self.scene)
# ==============================================================================
class QRegularPolygon(QtGui.QGraphicsPolygonItem):
"""
Regular polygon of N sides
"""
def __init__(self, sides, radius, center, angle = None, parent=None):
"""
Initializes an hexagon of the given radius.
sides -- sides of the regular polygon
radius -- radius of the external circle
center -- QPointF containing the center
angle -- offset angle in radians for the vertices
"""
super(QRegularPolygon,self).__init__(parent)
if sides < 3:
raise StandardError ('A regular polygon at least has 3 sides.')
self._sides = sides
self._radius = radius
if angle != None: self._angle = angle
else: self._angle = 0.0
self._center = center
points = list()
for s in range(self._sides):
angle = self._angle + (2*math.pi * s/self._sides)
x = center.x() + (radius * math.cos(angle))
y = center.y() + (radius * math.sin(angle))
points.append(QtCore.QPointF(x,y))
self.setPolygon( QtGui.QPolygonF(points) )
# ==============================================================================
def main():
"""
That's it: the main function
"""
app = QtGui.QApplication(sys.argv)
grid = HexGrid(QtCore.QRectF(0.0, 0.0, 500.0, 500.0))
radius = 50
sides = 6
apothem = radius * math.cos(math.pi/sides)
side = 2 * apothem * math.tan(math.pi/sides)
xinit = 50
yinit = 50
angle = math.pi/2
polygons = list()
for x in range(xinit,xinit+20):
timesx = x - xinit
xcenter = x + (2*apothem)*timesx
for y in range(yinit, yinit+20):
timesy = y - yinit
ycenter = y + ((2*radius)+side)*timesy
center1 = QtCore.QPointF(xcenter,ycenter)
center2 = QtCore.QPointF(xcenter+apothem,ycenter+radius+(side/2))
h1 = QRegularPolygon(sides, radius, center1, angle)
h2 = QRegularPolygon(sides, radius, center2, angle)
# adding polygons to a list to avoid losing them when outside the
# scope (loop?). Anyway, just in case
polygons.append(h1)
polygons.append(h2)
grid.scene.addItem(h1)
grid.scene.addItem(h2)
grid.show()
app.exec_()
# ==============================================================================
if __name__ == '__main__':
main()
and last but not least, sorry for the long post :)
Thanks
Victor
Personally, I'd define each hexagonal tile as a separate SVG image, and use QImage and QSvgRenderer classes to render them to QPixmaps (with an alpha channel) whenever the zoom level changes. I'd create a QGraphicsItem subclass for displaying each tile.
The trick is to pick the zoom level so that the width of the (upright) hexagon is a multiple of two, and the height a multiple of four, with width/height approximately sqrt(3/4). The hexagons are slightly squished in either direction, but for all hexagons at least eight pixels in diameter, the effect is inperceptible.
If the width of the hexagon is 2*w, and height 4*h, here's how to map the (upright) hexagons to Cartesian coordinates:
If each side of the hexagon is a, then h=a/2 and w=a*sqrt(3)/2, therefore w/h=sqrt(3).
For optimum display quality, pick integer w and h, so that their ratio is approximately sqrt(3) ≃ 1.732. This means your hexagons will be very slightly squished, but that's okay; it is not perceptible.
Because the coordinates are now always integers, you can safely (without display artefacts) use pre-rendered hexagon tiles, as long as they have an alpha channel, and perhaps a border to allow smoother alpha transitions. Each rectangular tile is then 2*w+2*b pixels wide and 4*h+2*b pixels tall, where b is the number of extra border (overlapping) pixels.
The extra border is needed to avoid perceptible seams (background color bleeding through) where pixels are only partially opaque in all overlapping tiles. The border allows you to better blend the tile into the neighboring tile; something the SVG renderer will do automatically if you include a small border region in your SVG tiles.
If you use typical screen coordinates where x grows right and y down, then the coordinates for hexagon X,Y relative to the 0,0 one are trivial:
y = 3*h*Y
if Y is even, then:
x = 2*w*X
else:
x = 2*w*X + w
Obviously, odd rows of hexagons are positioned half a hexagon to the right.
Subclassing QGraphicsItem and using a bounding polygon (for mouse and interaction tests) means Qt will do all the heavy work for you, when you wish to know which hexagonal tile the mouse is hovering on top of.
However, you can do the inverse mapping -- from screen coordinates back to hexagons -- yourself.
First, you calculate which rectangular grid cell (green grid lines in the image above) the coordinate pair is in:
u = int(x / w)
v = int(y / h)
Let's assume all coordinates are nonnegative. Otherwise, % must be read as "nonnegative remainder, when divided by". (That is, 0 <= a % b < b for all a, even negative a; b is always a positive integer here.)
If the origin is as shown in the above image, then two rows out of every three are trivial, except that every odd row of hexagons is shifted one grid cell right:
if v % 3 >= 1:
if v % 6 >= 4:
X = int((u - 1) / 2)
Y = int(v / 3)
else:
X = int(u / 2)
Y = int(v / 3)
Every third row contains rectangular grid cells with a diagonal boundary, but worry not: if the boundary is \ (wrt. above image), you only need to check if
(x % w) * h >= (y % h) * w
to find out if you are in the upper right triangular part. If the boundary is / wrt. above image, you only need to check if
(x % w) * h + (y % h) * w >= (w * h - (w + h) / 2)
to find out if you are in the lower right triangular part.
In each four-column and six-row section of rectangular grid cells, there are eight cases that need to be handled, using one of the above test clauses. (I'm too lazy to work the exact if clauses for you here; like I said, I'd let Qt do that for me.) This rectangular region repeats exactly for the entire hexagonal map; thus, a full coordinate conversion may need up to 9 if clauses (depending on how you write it), so it's a bit annoying to write.
If you wish to determine e.g. the mouse cursor location relative to the hexagon it is hovering over, first use the above to determine which hexagon the mouse hovers over, then substract the coordinates of that hexagon from the mouse coordinates to get the coordinates relative to the current hexagon.
Try with this main() function. I used the radius of the inscribed circle (ri) instead of the circumscribed circle that you used (radius). It looks a bit better now, but still not perfect. I think the way the oblique sides are drawn at the top and bottom of the hexagon are different.
def main():
"""
That's it: the main function
"""
app = QtGui.QApplication(sys.argv)
grid = HexGrid(QtCore.QRectF(0.0, 0.0, 500.0, 500.0))
radius = 50 # circumscribed circle radius
ri = int(radius / 2 * math.sqrt(3)) # inscribed circle radius
sides = 6
apothem = int(ri * math.cos(math.pi/sides))
side = int(2 * apothem * math.tan(math.pi/sides))
xinit = 50
yinit = 50
angle = math.pi/2
polygons = list()
for x in range(xinit,xinit+20):
timesx = x - xinit
xcenter = x + (2*apothem-1)*timesx
for y in range(yinit, yinit+20):
timesy = y - yinit
ycenter = y + ((2*ri)+side)*timesy
center1 = QtCore.QPointF(xcenter,ycenter)
center2 = QtCore.QPointF(xcenter+apothem,ycenter+ri+(side/2))
h1 = QRegularPolygon(sides, ri, center1, angle)
h2 = QRegularPolygon(sides, ri, center2, angle)
# adding polygons to a list to avoid losing them when outside the
# scope (loop?). Anyway, just in case
polygons.append(h1)
polygons.append(h2)
grid.scene.addItem(h1)
grid.scene.addItem(h2)
grid.show()
app.exec_()
There are multiple problems here. They aren't specifically related to Qt or to Python, but to general computer science.
You have floating point geometrical shapes that you want to display on a raster device, so somehow there has to be a floating point to integer conversion. It's not in your code, so it will happen at a lower level: in the graphics library, the display driver or whatever. Since you're not happy with the result, you have to handle this conversion yourself.
There's no right or wrong way to do this. For example, take your case of a hex tile that has a “radius” of 50. The hexagon is oriented so that the W vertex is at (-50,0) and the E vertex is at (50,0). Now the NE vertex of this hexagon is at approximately (25,0,43.3). The hexagon that's adjacent to this one in the N direction has its center at about y=86.6 and its top edge at 129.9. How would you like to pixellate this? If you round 43.3 down to 43, now you no longer have a mathematically exact regular hexagon. If you round 129.9 up to 130, your first hexagon is 86 pixels in total height but the one on top of it is 87. This is an issue that you must resolve based on the project's requirements.
And this is just one case (radius=50). If you allow the radius to be variable, can you come up with an algorithm to handle all cases? I couldn't. I think you need to use a fixed screen dimension for your hexagons, or at least reduce the possibilities to a small number.
Nowhere in your code do you determine the size of the display window, so I don't understand how you intend to handle scaling issues, or determine how many hexes are needed to show the full map.
As to your first question, I am certain that the performance will be poor. The constructor for QRegularPolygon is inside the loop that creates the hexes, so it gets called many times (800 in your example). It does two trig calculations for each vertex, so you perform 9600 trig calculations as you build your list of hexes. You don't need ANY of them. The calculations are the sine and cosine of 0 degrees, 60 degrees, 120 degrees and so on. Those are so easy you don't even need sin and cos.
The use of the trig functions exacerbates the floating point/integer problem, too. Look at this:
>> int(50.0*math.sin(math.pi/6))
24
We know it should be 25, but the computer figures it as int(24.999999999996) – I may have left out a few 9's.
If you calculate the vertex positions of just one hexagon, you can get all the other ones by a simple translation. See the useful Qt functions QPolygon->translate or QPolygon->translated.
It seems that you don't need a constructor that can handle any type of polygon when your design concept absolutely needs hexagons. Did you just copy it from somewhere? I think it's mostly clutter, which always opens the door to errors.
Do you really need polygons here? Later on, I suppose, the game will use raster images, so the polygons are just for display purposes.
You could just take a point cloud representing all corners of the polygon and draw lines beneath them. With this, you avoid problems of rounding / floating point arithmetics etc.
I am trying to write a python function that will copy a triangular area from anywhere on a picture to a new blank picture. I can copy a rectangular area from a picture to a new empty picture, but I just don't know how to copy a triangle. That's what I have, but it only copies a rectangular area. Sorry if it looks messy or over-complicated, but I'm just starting how to write in python.
def copyTriangle():
file=pickAFile()
oldPic=makePicture(file)
newPic=makeEmptyPicture(getWidth(oldPic),getHeight(oldPic))
xstart=getWidth(oldPic)/2
ystart=getHeight(oldPic)/2
for y in range(ystart,getHeight(oldPic)):
for x in range(xstart,getWidth(oldPic)):
oldPixel=getPixel(oldPic,x,y)
colour=getColor(oldPixel)
newPixel=getPixel(newPic,x,y)
setColor(newPixel,colour)
Function to copy a triangular area from one pic to another.
def selectTriangle(pic):
w= getWidth (pic)
h = getHeight(pic)
newPic = makeEmptyPicture(w,h)
x0=107#test point 0
y0=44
x1=52#test point 1
y1=177
x2=273 #test point 2
y2=216
#(y-y0)/(y1-y0)=(x-x0)/(x1-x0)
for y in range (0,h):
for x in range (0, w):
#finding pixels within the plotted lines between eat set of points
if (x>((y-y0)*(x1-x0)/(y1-y0)+x0) and x<((y-y0)*(x2-x0)/(y2-y0)+x0) and x>((y-y2)*(x1-x2)/(y1-y2)+x2)):
pxl = getPixel(pic, x, y)
newPxl= getPixel(newPic,x,y)
color = getColor(pxl)
setColor (newPxl, color)
return (newPic)
If you're willing to do it pixel by pixel as in your example, then just copy the pixels of the triangle. Mostly this depends on how you want to define the triangle.
The simplest triangle is to make your x range (inner loop) dependent on your y-value (outer loop), like:
for y in range(ystart, ystart+getHeight(oldPic)):
for x in range(xstart, xstart + int( getWidth(oldPic)*((y-ystart)/float(getHeight)):
More generally, you could still keep your same x and y loops, and then put the copying commands in an if block, where you check whether the point is in your triangle.
Beyond this, there are much more efficient ways of doing this, using masks, etc.
Note, also here I changed the y-range to range(ystart, ystart+getHeight(oldPic)), which I think is probably what you want for a height that doesn't depend on the starting position.