I'm trying to create a traffic simulator using python (+ pygame and math libs).
I'm stuck at car movements, I've managed to create a fixed horizontal and vertical path using a list of coordinates, using nested for loops.
Now, since I'm in a crossroads I now have to make the 1/4 of a circle to make a left or right turn.
The car has to make a straight path for n pixels (and it's ok) then have to make the 1/4 following a circular path and then proceed towards a straight line again.
I have a list of coordinates, however, I don't know how to make it turn around the center of the circumference (which I know the coordinates of) while also turning the image relative to the center!
I'll give you an idea of the problem using an image
The red paths "1" are done, the problem is the blue tract "2", "O" coordinates are known, there are 4 "O" in total (one for each path).
I would suggest to use the equation of a circle which is (x – h)^2 + (y – k)^2 = r^2 where (h,k) are the coordinates of the cercle's center and r the radius.
The idea in your case could be to draw all discrete points (x,y) that verify this equation,
here is an example (for points where x and y are positive regarding the origin, if we follow your example):
import numpy as np
import matplotlib.pyplot as plt
center = (10,10)
radius = 5
th = 0.01
for x in np.arange(center[0],center[0]+radius+th, th):
for y in np.arange(center[1], center[1]+radius+th, th):
if abs((x-center[0])**2 + (y-center[1])**2 - radius**2) < th:
plt.scatter(x,y)
plt.show()
You can then control the number of points with the th, and even draw lines instead of just points to speed up the process
I am performing motion tracking of an object, and I am trying to identify the front and back of the object. The object is asymmetrical, which means that the centroid of the contour is closer to the front than the back. Using this information, I am approaching this as follows:
Draw contours of object
Find centroid
centroidx, centroidy = int(moments['m10']/moments['m00']), int(moments['m10']/moments['m00'])
Draw bounding ellipse
cv2.fitEllipse(contour)
Calculate major axis length as follows (and as shown in the figure)
MAx, MAy = int(0.5 * ellipseMajorAxisx*math.sin(ellipseAngle)), int(0.5 * ellipseMajorAxisy*math.cos(ellipseAngle))
Calculate beginning and ending x, y coordinates of the major axis
MAxtop, MAytop = int(ellipseCentrex + MAx), int(ellipseCentrey + MAy)
MAxbot, MAybot = int(ellipseCentrex - MAx), int(ellipseCentrey - MAy)
Identify which of the points is closer to the centroid of the contour
distancetop = math.sqrt((centroidx - MAxtop)**2 + (centroidy - MAytop)**2)
distancebot = math.sqrt((centroidx - MAxbot)**2 + (centroidy - MAybot)**2)
min(distancetop, distancebot)
The problem I am encountering is, while I get the "front" end of the ellipse correct most of the time, occasionally the point is a little bit away. As far as I have observed, this seems to be happening such that the x value is correct, but y value is different (in effect, I think this represents the major axis of an ellipse that is perpendicular to mine). I am not sure if this is an issue with opencv's calculation of angles or (more than likely) my calculations are incorrect. I do realize this is a complicated example, hope my figures help!
EDIT: When I get the wrong point, it is not from a perpendicular ellipse, but of a mirror image of my ellipse. And it happens with the x values too, not just y.
After following ssm's suggestion below, I am getting the desired point most of the time. The point still goes wrong occasionally, but "snaps back" into place soon after. For example, this is a few frames when this happens:
By the way, the above images are after "correcting" for angle by using this code:
if angle > 90:
angle = 180 - angle
If I do not do the correction, I get the wrong point at other times, as shown below for the same frames.
So it looks like I get it right for some angles with angle correction and the other angles without correction. How do I get all the right points in both conditions?
(White dot inside the ellipse is the centroid of the contour, whereas the dot on or outside the ellipse is the point I am getting)
I think your only problem is MAytop. You can consider doing the following:
if ycen<yc:
# switch MAytop and MAybot
temp = MAytop
MAytop = MAybot
MAybot = temp
You may have to do a similar check on the x - scale
I have some points that are located in the same place, with WGS84 latlngs, and I want to 'jitter' them randomly so that they don't overlap.
Right now I'm using this crude method, which jitters them within a square:
r['latitude'] = float(r['latitude']) + random.uniform(-0.0005, 0.0005)
r['longitude'] = float(r['longitude']) + random.uniform(-0.0005, 0.0005)
How could I adapt this to jitter them randomly within a circle?
I guess I want a product x*y = 0.001 where x and y are random values. But I have absolutely no idea how to generate this!
(I realise that really I should use something like this to account for the curvature of the earth's surface, but in practice a simple circle is probably fine :) )
One simple way to generate random samples within a circle is to just generate square samples as you are, and then reject the ones that fall outside the circle.
The basic idea is, you generate a vector with x = radius of circle y = 0.
You then rotate the vector by a random angle between 0 and 360, or 0 to 2 pi radians.
You then apply this displacement vector and you have your random jitter in a circle.
An example from one of my scripts:
def get_randrad(pos, radius):
radius = random() * radius
angle = random() * 2 * pi
return (int(pos[0] + radius * cos(angle)),
int(pos[1] + radius * sin(angle)))
pos beeing the target location and radius beeing the "jitter" range.
As pjs pointed out, add
radius *= math.sqrt(random())
for uniform distribution
Merely culling results that fall outside your circle will be sufficient.
If you don't want to throw out some percentage of random results, you could choose a random angle and distance, to ensure all your values fall within the radius of your circle. It's important to note, with this solution, that the precision of the methods you use to extrapolate an angle into a vector will skew your distribution to be more concentrated in the center.
If you make a vector out of your x,y values, and then do something like randomize the length of said vector to fall within your circle, your distribution will no longer be uniform, so I would steer clear of that approach, if uniformity is your biggest concern.
The culling approach is the most evenly distributed, of the three I mentioned, although the random angle/length approach is usually fine, except in cases involving very fine precision and granularity.
I am trying to estimate the value of pi using a monte carlo simulation. I need to use two unit circles that are a user input distance from the origin. I understand how this problem works with a single circle, I just don't understand how I am meant to use two circles. Here is what I have got so far (this is the modified code I used for a previous problem the used one circle with radius 2.
import random
import math
import sys
def main():
numDarts=int(sys.argv[1])
distance=float(sys.argv[2])
print(montePi(numDarts,distance))
def montePi(numDarts,distance):
if distance>=1:
return(0)
inCircle=0
for I in range(numDarts):
x=(2*(random.random()))-2
y=random.random()
d=math.sqrt(x**2+y**2)
if d<=2 and d>=-2:
inCircle=inCircle+1
pi=inCircle/numDarts*4
return pi
main()
I need to change this code to work with 2 unit circles, but I do not understand how to use trigonometry to do this, or am I overthinking the problem? Either way help will be appreciated as I continue trying to figure this out.
What I do know is that I need to change the X coordinate, as well as the equation that determines "d" (d=math.sqrt(x*2+y*2)), im just not sure how.
These are my instructions-
Write a program called mcintersection.py that uses the Monte Carlo method to
estimate the area of this shape (and prints the result). Your program should take
two command-line parameters: distance and numDarts. The distance parameter
specifies how far away the circles are from the origin on the x-axis. So if distance
is 0, then both circles are centered on the origin, and completely overlap. If
distance is 0.5 then one circle is centered at (-0.5, 0) and the other at (0.5, 0). If
distance is 1 or greater, then the circles do not overlap at all! In that last case, your
program can simply output 0. The numDarts parameter should specify the number
of random points to pick in the Monte Carlo process.
In this case, the rectangle should be 2 units tall (with the top at y = 1 and the
bottom at y = -1). You could also safely make the rectangle 2 units wide, but this
will generally be much bigger than necessary. Instead, you should figure out
exactly how wide the shape is, based on the distance parameter. That way you can
use as skinny a rectangle as possible.
If I understand the problem correctly, you have two unit circles centered at (distance, 0) and (-distance, 0) (that is, one is slightly to the right of the origin and one is slightly to the left). You're trying to determine if a given point, (x, y) is within both circles.
The simplest approach might be to simply compute the distance between the point and the center of each of the circles. You've already done this in your previous code, just repeat the computation twice, once with the offset distance inverted, then use and to see if your point is in both circles.
But a more elegant solution would be to notice how your two circles intersect each other exactly on the y-axis. To the right of the axis, the left circle is completely contained within the right one. To the left of the y-axis, the right circle is entirely within the left circle. And since the shape is symmetrical, the two halves are of exactly equal size.
This means you can limit your darts to only hitting on one side of the axis, and then get away with just a single distance test:
def circle_intersection_area(num_darts, distance):
if distance >= 1:
return 0
in_circle = 0
width = 1-distance # this is enough to cover half of the target
for i in range(num_darts):
x = random.random()*width # random value from 0 to 1-distance
y = random.random()*2 - 1 # random value from -1 to 1
d = math.sqrt((x+distance)**2 + y**2) # distance from (-distance, 0)
if d <= 1:
in_circle += 1
sample_area = width * 2
target_area = sample_area * (in_circle / num_darts)
return target_area * 2 # double, since we were only testing half the target
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.