Related
two scenarios
I have an x axis size, a y axis size, a render distance, a list of grid position numbers and a central grid position.
I am trying to create a list of all the grid positions within the render distance of a central grid position.
The size of the x and y axis may be different independently. Optimally this algorithm would not attempt to get positions where the render distance extends over the side of the x or y axis.
Thanks.
I'm writing this to help you answer your own question in the style that I would go about it. As with anything in coding, what you need to do is be able to break down a big problem into multiple smaller ones.
Design two functions to convert to and from (x, y) coordinates (optional, it'll make your life easier, but won't be as efficient, personally I would avoid this for a bit of a challenge).
Given n, size and distance, calculate up, down, left and right. If the size is different for different axis, then just provide the function with the correct one.
eg.
def right(n, size, distance):
return n + size * distance
def down(n, size, distance):
return n - distance
Given size, make sure the above functions don't go off the edge of the grid. Converting the points to (x, y) coordinates for this part may help.
Now you have the sides of the square, run the functions again to get the corners. For example, to get the top right corner, you could do right(up(*args)) or up(right(*args))
With the corners, you can now calculate what's in your square. Converting the points to (x, y) coordinates will make it easier.
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.
I have a chain of squares represented in pygame. I have some code that lets me rotate parts of the chain, as follows.
#!/usr/bin/python
import pygame
def draw(square):
(x,y) = square
pygame.draw.rect(screen, black, (100+x*20,100+y*20,20,20), 1)
def rotate(chain, index, direction):
(pivotx, pivoty) = chain[index]
if (direction == 1):
newchain = chain[:index]+[(y-pivoty+pivotx, (x-pivotx)+pivoty) for (x,y) in chain[index:]]
else:
newchain = chain[:index]+[(y-pivoty+pivotx, -(x-pivotx)+pivoty) for (x,y) in chain[index:]]
return newchain
pygame.init()
size = [600, 600]
screen = pygame.display.set_mode(size)
white = (255,255,255)
black = (0,0,0)
n = 20
chain = [(i,0) for i in xrange(n)]
screen.fill(white)
for square in chain:
draw(square)
pygame.display.flip()
raw_input("Press Enter to continue...")
newchain = rotate(chain, 5, 1)
print chain
print newchain
screen.fill(white)
for square in newchain:
draw(square)
pygame.display.flip()
raw_input("Press Enter to continue...")
screen.fill(white)
newchain = rotate(newchain, 10,0)
for square in newchain:
draw(square)
pygame.display.flip()
raw_input("Press Enter to continue...")
pygame.quit()
The function rotate takes an index of a square in the chain and rotates the whole chain after that square by 90 degrees, pivoting around the initial square. The problem is that this is meant to mimic a physical toy so it is not allowed to collide with itself. I can check to see if two squares are on top of each other after a rotation but how can I make sure they wouldn't collide temporarily during a rotation?
It sounds like you already know how to know if they're overlapping once you do the rotation, unless I am misunderstanding. If that's the case, then it would be relatively easy to define a function to answer that question given a potential rotation in the chain by adding a check to the end of your comprehension:
if (direction == 1):
newchain = chain[:index]+[(y-pivoty+pivotx, (x-pivotx)+pivoty) for (x,y) in chain[index:] if not overlapping(x, y, pivotx, pivoty)]
else:
newchain = chain[:index]+[(y-pivoty+pivotx, -(x-pivotx)+pivoty) for (x,y) in chain[index:] if not overlapping(x, y, pivotx, pivoty)]
And of course relying on some kind of:
def overlapping(x, y, px, py):
if (some logic that determins if this is bad):
raise Exception('Overlapping')
return True
You would need to do something useful with the exception, but at least this would check each square as you process it, and break out immediately instead of waiting until after the whole rotation to verify that it's good.
I would use the pygame functions to do that.
1. make your surfaces to sprites.
2. rotate them with pygame.transform.rotate.
3. check collision with pygame functions.
4. this function works perfect for me ;).
def collision(sprite, group):
rectCol = pygame.sprite.spritecollide(sprite, group, False)
return [s for s in rectCol if pygame.sprite.collide_mask(sprite, s)]
sprite is one of your squares.
group is all the other squares.
the function returns a list with all squares witch collide with your square.
What you have to do is check a collision between the two quarter-circles that two sides of the rotating rect inscribe. To check collisions between a quarter-circle and a rectangle you can try adapting this code.
The 2 squares will overlap in transit if:
one is stationary, the other is moving
the center of one starts to the left of the other, and ends up to the right (cross product will be of use here)
their distances to the pivot square are within a range (determined by the corners furthest and closest to the pivot square).
Above I gave an idea how to quickly check 2 given squares.
If you sort the squares by their distance to the pivot square, you will not have to check all pairs, only the pairs that are within a distance (thus avoiding O(N^2)).
One way you can do it is to model the squares after circles and use the relationship
d=sqrt((x2-x1)^2+(y2-y1)^2)
(x1,y1), (x2,y2) being the center of the squares.
where d is the minimum distance between their centres. Then you compare it to r1+r2, where r1 and r2 are the radius of the circles residing in the squares. If d is less than r1+r2, reverse their unit velocity vector, or make them rotate the other way.
You can increase the accuracy of the model by testing the vertices of one square, against the diagonals of another square. For example (please take a graph paper to see this), say we have a square A , vertices being [(2,0),(0,2),(2,4),(4,2)], and another (square B) being [(2,2),(5,2),(5,5),(2,5)], now take any one square (we'll take B) and take any one of it's vertices, say, (2,2). Test if the x-coords (2) lies between the x-coordinate of the diagonally aligned vertices of A, say (2,4) and (2,0). Apparently it does! Then we check it against another diagonal, (0,2) and (4,2). It does too! So, square B has collided with square A and the rotation vector or the velocity vector has to be reversed. You may also check with the y-coords.
You'll have to loop through each square to check if they collide with others. However, you don't have to check all squares as you will only need to concern yourself with squares with min distance of d is less than r1+r2 relative to each other, so you will just need one loop to check if their distances are less than the sum of radius, and another loop to check if their vertices has entered the body of the square. I normally do that in my games which stimulate random motion of particles (e.g. brownian motion)
This problem has been generically solved many, many times. The simplest explanation is that you use an increasing level of detail.
For the shapes themselves you must create either bounding boxes or bounding circles which are large enough to contain the outer-most points in the shape. A bounding circle is nice because it is the smallest shape which will always fully cover the object. It also doesn't need to be regenerated after a rotation because it always describes the maximum space possible for your shape.
For a bounding circle the next operation is to measure the distance between each bounding circle and discard the ones that cannot possibly overlap. This is fairly inexpensive. Note too, that you can discard reflections. I.e. if you tested that shape a cannot overlap shape b, don't now test if shape b overlaps shape a.
Once you have the shapes which "may" overlap, you must use a precise algorithm to see if any point in one shape overlaps any point in the other shape. There are a variety of ways to do this. Some of them are geometric (GJK Algorithm) while others use things like z-buffers or pixel masks. For these latter kind you can draw one shape to a test buffer, then start to draw the second shape. If you check the buffer before you plot a pixel and see there is a pixel already there, you know there is an intersection (collision).
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.