So I've been trying to figure out how to find the distance between two objects on a canvas and I've exhausted most relevant links on Google with little success.
I'm trying to make it so that it calculates the distance between the drawn ovals and the line on the canvas.
from __future__ import division
from Tkinter import *
import tkMessageBox
class MyApp(object):
def __init__(self):
self.root = Tk()
self.root.wm_title("Escape")
self.canvas = Canvas(self.root, width=800, height=800, bg='white')
self.canvas.pack()
self.canvas.create_line(100, 100, 200, 200, fill='black')
self.canvas.bind("<B1-Motion>", self.tracer)
self.root.mainloop()
def tracer(self, e):
self.canvas.create_oval(e.x-5, e.y-5, e.x+5, e.y+5, fill='blue', outline='blue')
rx = "%d" % (e.x)
ry = "%d" % (e.y)
print rx, ry
MyApp()
Two circles:
dist = math.sqrt((circle1.x-circle2.x)**2 + (circle1.y-circle2.y)**2) - circle1.r - circle2.r
Kind of obvious, their Euclid distance is calculated using the Pythagorean theorem.
Point/segment:
a = segment[1].y - segment[0].y
b = segment[0].x - segment[1].x
c = - segment[0].x * a - segment[0].y * b
dx = segment[1].x - segment[0].x
dy = segment[1].y - segment[0].y
nc0 = - segment[0].x * dx - segment[0].y * dy
nc1 = - segment[1].x * dx - segment[1].y * dy
if ((dx * x + dy * y + nc0) < 0) dist = math.sqrt((x-segment[0].x)**2 + (y-segment[0].y)**2)
elif((dx * x + dy * y + nc1) < 0) dist = math.sqrt((x-segment[1].x)**2 + (y-segment[1].y)**2)
else dist = (a*x + b*y + c) / math.sqrt(a**2 + b**2)
Circle/segment - same as point/segment, just substract circle's radius
Polygon/polygon - Loop through each vertex of polygon 1 and segment of polygon 2, then loop through each vertex of polygon 2 and segment of polygon 1, then find the smallest.
Don't use magic numbers inside your code. That radius of 5 isn't good.
dist = math.sqrt((oval.x-point.x)**2 + (oval.y-point.y)**2)
not really sure if this answers your question but this is the distance formula
there are several problems with the original question.
at what point are you trying to find the distance?
you are not saving the oval positions so it will be very hard to look up later
why are you trying to get the distance?/what are you planning on doing with it?
are you trying to find distance to edge or distance to center?
in general I would guess you want to get it in the def trace function
step 1. Point of interest is (e.x,e.y)
step 2. Find the closest point on the line (line = (100,100) to (200,200) ) (this is probably a question in its own right. http://nic-gamedev.blogspot.com/2011/11/using-vector-mathematics-and-bit-of_08.html )
step 3. Apply distance method to point of interest and closest point on line
def dist(pt1,pt2):
return ((pt1.x-pt2.x)**2 + (pt1.y-pt2.y)**2)**.5
on another side note ... your ovals look an awful lot like circles ...
Related
I want to finish my tower defense game as fast as I can.
I have trouble with if the tower is on the path with a given path list.
It will never work even is I try as hard as I can.
I already tried a lot of times to solve that problem like tech with tim's tower defense youtube tutorial. It made almost perfect sense why it was not working.
But no matter how hard I try, It seems to never work properly.
Find the link to the tutorial here.
WARNING:The video is pretty long.
x, y = tower.x, tower.y
for n, point in enumerate(path):
point_x, point_y = point[0], point[1]
dis_x = abs(point_x - x)
dis_y = abs(point_y - y)
dis = math.sqrt((dis_x - x)**2 + (dis_y - y)**2)
print(dis)
if dis < 130:
return False
return True
You might be thinking 'Why did I do this' so I changed it a bit:
import numpy as np
closest = []
x, y = tower.x, tower.y
for n, point in enumerate(path):
point_x, point_y = point[0], point[1]
dis_x = abs(point_x - x)
dis_y = abs(point_y - y)
dis = math.sqrt((dis_x - x)**2 + (dis_y - y)**2)
print(dis)
if len(closest) <= 2:
if dis < 130:
closest.append(point)
p1 = np.array([x, y])
p2 = np.array(closest[0])
p3 = np.array(closest[1])
dis = np.cross(p2-p1,p3-p1)/np.linalg.norm(p2-p1)
if dis < 90:
return False
return True
I did not recive any error messages, but you can still place towers on some spots on the path,
and you can't place towers on some points not on the path, and I was expecting it to be pretty neat.
As #ImperishableNight stated, the issue is that your function only compares each point on the path and checks if the distance is less than a certain threshold (130 pixels in your case). But this is not enough, since we are not only interested in the end points in each segment of the path, but also all of the points in between. For that, we need to calculate the distance between a point and a line segment.
I have written and commented on the following code using a simple Point class to replace whatever pygame provides. I break the problem up into a bunch of tiny functions to solve your problem. Please let me know if any of this is unclear.
import math
import random
class Point:
def __init__(self, x=0, y=0):
"""
A really simple Point class.
Replaces whatever pygame provides for this example.
"""
self.x = x
self.y = y
def __repr__(self):
"""
A convenient representation of a Point class.
So we see the x and y values when printing these objects.
"""
return "({0}, {1})".format(self.x, self.y)
def gen_rand_point(width, height):
"""
Creates random x and y values for a Point object
"""
rand_x = random.randint(0, width)
rand_y = random.randint(0, height)
point = Point(x=rand_x, y=rand_y)
return point
def gen_rand_point_list(width, height, num_points):
"""
Creates a list of random points using the previous function.
"""
points = []
for i in range(num_points):
point = gen_rand_point(width, height)
points.append(point)
return points
def points_to_segments(points, loop=False):
"""
Converts a list of points into a list of segments.
Offsets the point list and zips it to create "segments".
A segment is just a tuple containing two Point objects.
"""
starts = points
ends = points[1:] + [points[0]]
segments = list(zip(starts, ends))
if loop:
return segments
else:
return segments[:-1]
def calc_sqr_dist(point_a, point_b):
"""
Calculates the square distance between two points.
Can be useful to save a wasteful math.sqrt call.
"""
delta_x = point_b.x - point_a.x
delta_y = point_b.y - point_a.y
sqr_dist = (delta_x ** 2) + (delta_y ** 2)
return sqr_dist
def calc_dist(point_a, point_b):
"""
Calculates the distance between two points.
When you need a wasteful math.sqrt call.
"""
sqr_dist = calc_sqr_dist(point_a, point_b)
dist = math.sqrt(sqr_dist)
return dist
def calc_dot_product(segment_a, segment_b):
"""
Calculates the dot product of two segments.
Info about what the dot product represents can be found here:
https://math.stackexchange.com/q/805954
"""
a0, a1 = segment_a
b0, b1 = segment_b
ax = a1.x - a0.x
ay = a1.y - a0.y
bx = b1.x - b0.x
by = b1.y - b0.y
dot = (ax * bx) + (ay * by)
return dot
def calc_point_segment_dist(point, segment):
"""
Gets the distance between a point and a line segment.
Some explanation can be found here:
https://stackoverflow.com/a/1501725/2588654
"""
start, end = segment
sqr_dist = calc_sqr_dist(start, end)
#what if the segment's start and end are the same?
if sqr_dist == 0:
dist = calc_dist(point, start)
return dist
#what if it is not that easy?
else:
segment_a = (start, point)
segment_b = (start, end)#really is just segment...
dot = calc_dot_product(segment_a, segment_b)
t = float(dot) / sqr_dist
clamped_t = max(0, min(1, t))#clamps t to be just within the segment
#the interpolation is basically like a lerp (linear interpolation)
projection = Point(
x = start.x + (t * (end.x - start.x)),
y = start.y + (t * (end.y - start.y)),
)
dist = calc_dist(point, projection)
return dist
def calc_point_path_dist(point, path):
"""
Gets the distances between the point and each segment.
Then returns the minimum distance of all of these distances.
"""
dists = [calc_point_segment_dist(point, segment) for segment in path]
min_dist = min(dists)
return min_dist
if __name__ == "__main__":
"""
A fun example!
"""
width = 800
height = 600
num_path_points = 5
tower_range = 50
tower = gen_rand_point(width, height)
path_points = gen_rand_point_list(width, height, num_path_points)
path = points_to_segments(path_points)
dist = calc_point_path_dist(tower, path)
in_range = dist <= tower_range
print(dist, in_range)
In short, I want to make a radar of sorts, using specifically the graphics.py library, that detects if pre-drawn Shape exists at a Point.
Below I've drafted up a code that will show me all the points within a radar (and return it as an array), but I want to be able to take a point/coordinate and check if it is inside a Circle or Rectangle etc without knowing anything else (i.e only x and y parameters, no knowledge of other existing Shapes). Something like:
if Point(x,y).getObject() is None:
return False
or even
if Point(x,y).getObject().config["fill"] == "#f00":
return True
Here's what I've done so far:
from graphics import *
def getAllPointsInRadius(circle, win):
cx = int(circle.getCenter().getX())
cy = int(circle.getCenter().getY())
r = circle.getRadius()# + 2
points = []
print cx, cy
for i in range(cx-r, cx+r):
for j in range(cy-r, cy+r):
x = i-circle.getRadius()
y = j - circle.getRadius()
if ((i - cx) * (i - cx) + (j - cy) * (j - cy) <= r * r):
points.append(Point(i,j)) #if within, append as Point
p = Point(i, j)
p.setFill('#551A8B')
p.draw(win) #illustrate all points inside radar
else:
p = Point(i, j)
p.setFill('#0000ff')
p.draw(win) #points outside
return points
win = GraphWin('Radar', width = 500, height = 500)
win.setCoords(0, 0, 30, 30)
win.setBackground('black')
#object to be detected
objectCircle = Circle(Point(11, 12), 2)
objectCircle.setFill('#f00')
objectCircle.draw(win)
#radius within objects may be detected
radiusCircle = Circle(Point(10, 10), 3)
radiusCircle.setOutline('#fff')
radiusCircle.draw(win)
print getAllPointsInRadius(radiusCircle, win)
#print Point(11, 12).config["outline"]
win.getMouse()
Addendum: I think maybe by making Point(x,y) I'm actually creating a point and not returning the coordinates. I'm not sure how to do it otherwise, though.
I'm writing code to make a machine orient a cutting tool in angle of a given vector. I need to rotate the 3D vector coordinates about the Y axis, I've got the equation figured out, but the code just breaks when I use it. Let me show you.
class Vector:
def __init__(self, i, j, k):
self.i = i
self.j = j
self.k = k
v0 = Vector(0.7844645405527362, 0.19611613513818404, 0.5883484054145521)
def findB():
if v0.i != 0:
angle = atan(v0.i/v0.k)
else:
angle = 0
print(angle, degrees(angle)) --> 0.92729... 53.13010...
v1 = Vector(v0.i, v0.j, v0.k)
# this formula rotates the vector about the j axis (comparable to y axis rotation)
v1.i = v0.i * cos(angle) - v0.k * sin(angle)
v1.k = v0.k * cos(angle) + v0.i * sin(angle)
return angle, v1
machine.b, v1 = findB()
print(v1.i, v1.j, v1.k) --> 1.1102230246251565e-16 0.19611613513818404 0.9805806756909203
I've tested that formula on a calculator and it rotates the coordinates correctly, I even hard coded the values to be:
v1.i = 0.784 * cos(0.927) - 0.588 * sin(0.927)
v1.k = 0.588 * cos(0.927) + 0.784 * sin(0.927)
Hard coding the values produces the correct result, but as soon as I use 'angle', v0.i and v0.k as the variables it gets v1.i incorrect. v1.i should be zero (or nearly zero from rounding). I've tested the same formula on a calculator and it works fine. Does anyone know what's going on?
I have a a program where circles can bounce into one another. I followed the directions from here for rotating the vectors and scaling the magnitudes based on the collision angle: http://www.vobarian.com/collisions/2dcollisions2.pdf
I wrote this code in python (the 0 index indicates the x coordinate):
norm_vect = [(object2.pos[0] - object1.pos[0]), (object2.pos[1] - object1.pos[1])]
unit = sqrt((norm_vect[0]**2) + (norm_vect[1]**2))
unit_vect = [float(norm_vect[0]) / unit, float(norm_vect[1]) /unit]
tan_vect = [-unit_vect[1], unit_vect[0]]
vel1 = object1.vel
vel2 = object2.vel
vel1_norm = vel1[0] * unit_vect[0] + vel1[1] * unit_vect[1]
vel1_tan = vel1[0] * tan_vect[0] + vel1[1] * tan_vect[1]
vel2_norm = vel2[0] * unit_vect[0] + vel2[1] * unit_vect[1]
vel2_tan = vel2[0] * tan_vect[0] + vel2[1] * tan_vect[1]
new_vel1_norm = (vel1_norm * (object1.mass - object2.mass) + 2 * object2.mass * vel2_norm) / (object1.mass + object2.mass)
new_vel2_norm = (vel2_norm * (object2.mass - object1.mass) + 2 * object1.mass * vel1_norm) / (object1.mass + object2.mass)
new_norm_vect1 = [new_vel1_norm * float(unit_vect[0]), new_vel1_norm * float(unit_vect[1])]
new_norm_vect2 = [new_vel2_norm * float(unit_vect[0]), new_vel2_norm * float(unit_vect[1])]
new_tan_vect1 = [new_vel1_norm * float(tan_vect[0]), new_vel1_norm * float(tan_vect[1])]
new_tan_vect2 = [new_vel2_norm * float(tan_vect[0]), new_vel2_norm * float(tan_vect[1])]
# Now update the object's velocity
object1.vel = [new_norm_vect1[0] + new_tan_vect1[0], + new_norm_vect1[1] + new_tan_vect1[1]]
object2.vel = [new_norm_vect2[0] + new_tan_vect2[0], + new_norm_vect2[1] + new_tan_vect2[1]]
The problem is that it works sometimes, but not othertimes. Can anyone tell me why? It seems like if the balls collide at the right angle then their exit trajectories swap or something. I wrote this in codeskulptor browser: http://www.codeskulptor.org/#user39_8q0Xdp3Y4s_2.py
Can anyone point out where I went wrong?
EDIT: Could it be the way that I process the collision? Here is the steps:
1) Draw the balls on the screen
2) Create set of unique pairs of collidable objects
3) For each ball, move the ball's position 1 frame forward according to the velocity:
->1) Check to see if the ball is hitting a wall
->2) For each pairset, if the ball in question is a member of the pair:
-->1) If distance between centers is less than sum of radii:
-->1) Calculate rebound trajectories
---2) Find N such that position + rebound trajectory *N is out of collision zone
The online simulation is really cool! I did not study your complete code in detail, just the snippet you posted in your question. From a quick glance, you correctly compute the tangential and normal unit vectors, the old normal and tangential velocities, and the new normal velocity. But after that, you seem to get lost a bit. As explained in the document about the collisions, the tangential velocities do not change during the collision, so there is no need to calculate new_tan_vect1/2. I also don't understand why you are calculating new_norm_vect1, the normal vector does not change during the collision.
Some other small remarks:
why do you use float() all over your code? This is normally not needed. If the reason for this is to get correct results for division, you should really add a from __future__ import division at the top of your code, since you seem to be using Python2. See this old question for more info.
What you call norm_vect is actually the un-normalized normal vector, and what you call unit_vect is actually the normalized normal unit vector. I would just call both norm_vect, to make the difference between normal and tangential more clear. A unit vector is any vector with length 1, so using that for the normal vector is a bit misleading.
If you are planning to do more of these kind of simulations, you should consider learning about numpy. This allows you to write vectorized calculations, instead of writing out all the equations for x and y by hand. E.g. norm_vect = pos2 - pos1; norm_vect /= np.linalg.norm(norm_vect) or object1.vel = norm_vect * new_vel1_norm + tang_vect * vel1_tang.
I would write your snippet should more or less like this (untested code):
from __future__ import division # move this to the top of your program
# calculate normal and tangential unit vectors
norm_vect = [(object2.pos[0] - object1.pos[0]),
(object2.pos[1] - object1.pos[1])] # stil un-normalized!
norm_length = sqrt((norm_vect[0]**2) + (norm_vect[1]**2))
norm_vect = [norm_vect[0] / norm_length,
norm_vect[1] / norm_length] # do normalization
tang_vect = [-norm_vect[1], norm_vect[0]] # rotate norm_vect by 90 degrees
# normal and tangential velocities before collision
vel1 = object1.vel
vel2 = object2.vel
vel1_norm = vel1[0] * norm_vect[0] + vel1[1] * norm_vect[1]
vel1_tang = vel1[0] * tang_vect[0] + vel1[1] * tang_vect[1]
vel2_norm = vel2[0] * norm_vect[0] + vel2[1] * norm_vect[1]
vel2_tang = vel2[0] * tang_vect[0] + vel2[1] * tang_vect[1]
# calculate velocities after collision
new_vel1_norm = (vel1_norm * (object1.mass - object2.mass)
+ 2 * object2.mass * vel2_norm) / (object1.mass + object2.mass)
new_vel2_norm = (vel2_norm * (object2.mass - object1.mass)
+ 2 * object1.mass * vel1_norm) / (object1.mass + object2.mass)
# no need to calculate new_vel_tang, since it does not change
# Now update the object's velocity
object1.vel = [norm_vect[0] * new_vel1_norm + tang_vect[0] * vel1_tang,
norm_vect[1] * new_vel1_norm + tang_vect[1] * vel1_tang]
object2.vel = [norm_vect[0] * new_vel2_norm + tang_vect[0] * vel2_tang,
norm_vect[1] * new_vel2_norm + tang_vect[1] * vel2_tang]
I renamed some of the variables to make it more clear.
community.
I know that there are many answers here, manuals, tutorials and references over the internets and amny more about this question. Also I know that knowledge of linear algebra is required.
But when I think about time to figuring out all the theory and solving exercises in practice - my head is blowing off and I can't do the simplest things :(
Please, if you know a little fast solution how to make rotation of text over its center before rendering it - tell me, pleeease.
For now I have:
#...
cr.move_to(*text_center)
myX, myY = text_center[0] - (width / 2), text_center[1] + (height / 2)
cr.save()
cr.translate(myX, myY)
cr.rotate(radians(text_angle))
cr.show_text(letter)
cr.restore()
#...
But my letter isn't rotating around itself. It's just like falling down to the right side :(
I know that my code isn't right. Maybe I miss transformation but I don't know how to make it right.
UPDATE: Unfortunately, text are not affected by translations, so
cr.translate(10000, 10000)
cr.rotate(radians(15))
cr.show_text("hello")
will be exactly the same as
cr.rotate(radians(15))
cr.show_text("hello")
And I don't know how to make text rotation over its center without making new surface or something (like new layer in graphic processor) :(
At least on the version of cairo available on my machine (1.8.8), the following approach works for me:
def text(ctx, string, pos, theta = 0.0, face = 'Georgia', font_size = 18):
ctx.save()
# build up an appropriate font
ctx.select_font_face(face , cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(font_size)
fascent, fdescent, fheight, fxadvance, fyadvance = ctx.font_extents()
x_off, y_off, tw, th = ctx.text_extents(string)[:4]
nx = -tw/2.0
ny = fheight/2
ctx.translate(pos[0], pos[1])
ctx.rotate(theta)
ctx.translate(nx, ny)
ctx.move_to(0,0)
ctx.show_text(string)
ctx.restore()
Which can be used in the following way:
width = 500
height = 500
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height)
ctx = cairo.Context(surface)
ctx.set_source_rgb(1,1,1)
rect(ctx, (0,0), (width, height), stroke=False)
ctx.set_source_rgb(0,0,0)
for i in xrange(5):
for j in xrange(5):
x = 100 * i + 20
y = 100 * j + 20
theta = math.pi*0.25*(5*i+j)
text(ctx, 'hello world', (x, y), theta, font_size=15)
surface.write_to_png('text-demo.png')
OK so cairo allows for text move_to and rotate. This means that what you want is to figure out (x,y) for move_to (T), such that when you rotate (R), the center point of your text is at your desired location, c=(cx,cy):
So you have to solve the equation Mv = c, where v is the text center relative to the text origin:
M = T*R
T = (1 0 x)
(0 1 y)
(0 0 1)
R = (cos r -sin r 0)
(sin r cos r 0)
(0 0 1)
v = (w/2, h', 1)
c = (cx, cy, 1)
h' = h/2 - (h - y_bearing)
Sanity checks:
when r is 0 (no rotation), you get x=cx-w/2, y=cy-h', which you know
is the correct answer
when r=-90 (text sideways, with "up" towards the right), you get what you expect,
ie x = cx - h' and y = cy + w/2
For python code, you will have to rewrite the above equation so you end up with A*t=b, where t=(x,y), and you will compute t = inv(A)*b. Then, you will simply do
cr.move_to(x, y)
cr.rotate(r)
cr.show_text(yourtext)
Note that the coordinate system in cairo has +y going down so there will be a couple signs to fix, and maybe y_bearing is not correct, but you get the idea.
Class function based on above input with multi-line text support.
def text(self, text, x, y, rotation=0, fontName="Arial", fontSize=10, verticalPadding=0):
rotation = rotation * math.pi / 180
self.ctx.select_font_face(fontName, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
self.ctx.set_font_size(fontSize)
fascent, fdescent, fheight, fxadvance, fyadvance = self.ctx.font_extents()
self.ctx.save()
self.ctx.translate(x, y)
self.ctx.rotate(rotation)
lines = text.split("\n")
for i in xrange(len(lines)):
line = lines[i]
xoff, yoff, textWidth, textHeight = self.ctx.text_extents(line)[:4]
offx = -textWidth / 2.0
offy = (fheight / 2.0) + (fheight + verticalPadding) * i
self.ctx.move_to(offx, offy)
self.ctx.show_text(line)
self.ctx.restore()
Should
myX, myY = text_center[0] + (height / 2), text_center[1] - (width / 2)
be
myX, myY = text_center[0] - (width / 2), text_center[1] + (height / 2)
?
That might explain why it's falling down to the right side.