I'm struggling to make a simple game with an world map on which the player will move around, using a click-and-go-along-a-line method. For this I'm trying to make a simple function to calculate a list of XY of pixels for the player to go through.
Most of it works fine, but I'm having issues with it multiplying one of the results by 2 whenever destination X and Y are not equal. I'm having a really tough time troubleshooting this one, and if somebody would have an idea how to improve it or fix it - I'd be very grateful.
import math
def linePath(start, finish):
if start[0] == finish[0]:
a = 1
else:
a = (finish[1] - start[1]) / float((finish[0] - start[0]))
b = start[1] - (a * start[0])
if abs(a) >= 1:
rng = xrange(start[1], finish[1] + 1) or reversed(xrange(finish[1], start[1] + 1))
else:
rng = xrange(start[0], finish[0] + 1) or reversed(xrange(finish[0], start[0] + 1))
for i in rng:
if abs(a) >= 1:
y = i
x = int(math.ceil((y - b)/ a))
else:
x, y = i, start[1] + int(math.ceil(a * i + b))
if start[0] != finish[0]:
yield x, y
else:
yield start[0], y
start = (10, 10)
destination = (15, 15)
print list(linePath(start, destination))
#Bugs: when start[0] > start[1] and start > destination (eg. destination = (16, 15))
#when start[0] < start[1] and start < destination (eg. destination = (5, 6))
Luckily, Jack Elton Bresenham solved this problem 1962 for you.
Here's an implementation of the Bresenham's line algorithm, found at Roguebashin:
def get_line(x1, y1, x2, y2):
points = []
issteep = abs(y2-y1) > abs(x2-x1)
if issteep:
x1, y1 = y1, x1
x2, y2 = y2, x2
rev = False
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
rev = True
deltax = x2 - x1
deltay = abs(y2-y1)
error = int(deltax / 2)
y = y1
ystep = None
if y1 < y2:
ystep = 1
else:
ystep = -1
for x in range(x1, x2 + 1):
if issteep:
points.append((y, x))
else:
points.append((x, y))
error -= deltay
if error < 0:
y += ystep
error += deltax
# Reverse the list if the coordinates were reversed
if rev:
points.reverse()
return points
start = (10, 10)
destination = (16, 15)
print list(get_line(start[0], start[1], destination[0], destination[1]))
Output:
[(10, 10), (11, 11), (12, 12), (13, 12), (14, 13), (15, 14), (16, 15)]
Related
So I have a line that rotates constantly and I want to have another line to be perpendicular to the rotating line. The problem is that it will occasionally point the perpendicular (yellow) line away from my rotating line (white). The math is done inside the function and the output looks like this. I would like the two lines to intersect sooner or later if lengthened. Any help would be greatly appreciated!
import cv2
import numpy as np
def perpendicular_finder(line, point):
x1, y1 = line[0]
x2, y2 = line[1]
x3, y3 = point
if ((y2 - y1) ^ 2 + (x2 - x1) ^ 2) !=0:
k = ((y2 - y1) * (x3 - x1) - (x2 - x1) * (y3 - y1)) / ((y2 - y1) ^ 2 + (x2 - x1) ^ 2)
x4 = x3 - k * (y2 - y1)
y4 = y3 + k * (x2 - x1)
x4 = np.int(x4)
y4 = np.int(y4)
return x4,y4
ballCircle = (200, 200)
purBall = (540,300)
cueX1 = 200
cueY1 = 200
cueX2 = 400
cueY2 = 400
count = 0
while True:
if count < 400:
cueX2 -= 1
elif count < 800:
cueY2 -= 1
elif count < 1200:
cueX2 += 1
else:
cueY2 += 1
if count == 1600:
count = 0
else:
count += 1
blank = np.zeros((500, 900, 3), np.uint8) # Create canvas the size of table
kek = perpendicular_finder((ballCircle, (cueX2,cueY2)), purBall)
cv2.line(blank, purBall, kek, (0, 255, 255), 1) # good path
cv2.circle(blank, ballCircle, 10, (255, 255, 255), -1) # Ball
cv2.circle(blank, purBall, 10, (0, 255, 255), -1) # Purple Ball
cv2.arrowedLine(blank, (cueX1, cueY1), (cueX2, cueY2), (255, 255, 255), 3) # Cue
cv2.imshow("kk", blank)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
Edit 1: This is what user MBo recommended.
Try this function for calculation of projection point
def proj(x1, y1, x2, y2, xp, yp):
x12 = x2 - x1
y12 = y2 - y1
dotp = x12 * (xp - x1) + y12 * (yp - y1)
dot12 = x12 * x12 + y12 * y12
if dot12:
coeff = dotp / dot12
lx = x1 + x12 * coeff
ly = y1 + y12 * coeff
return lx, ly
else:
return None
Seems you have occasionally used xor operator here: ^ 2 instead of squaring **2
Note that your function looses right direction, while mine projects point correctly for any angle (grey line is backward continuation of arrow)
ideone
How it looks
How to obtain pixel value of a gray scale image using the Bresenham line algorithm given a line at different angles as shown in this code fraction
I hope and you can help me
import cv2
import numpy as np
img= cv2.imread("0.L3Luzr_Esq.tif")
def bresenham(origin, dest):
# debug code
print origin
print dest
# end debug code
x0 = origin[0]; y0 = origin[1]
x1 = dest[0]; y1 = dest[1]
steep = abs(y1 - y0) > abs(x1 - x0)
backward = x0 > x1
if steep:
x0, y0 = y0, x0
x1, y1 = y1, x1
if backward:
x0, x1 = x1, x0
y0, y1 = y1, y0
dx = x1 - x0
dy = abs(y1 - y0)
error = dx / 2
y = y0
if y0 < y1: ystep = 1
else: ystep = -1
result = []
for x in range(x0, x1):
if steep: result.append((y, x))
else: result.append((x, y))
error -= dy
if error < 0:
y += ystep
error += dx
return result
if backward: return result.reverse()
else: return result
print result
return result
I'm given a list with coordinates of n points, let's say:
points = [(1, 2), (2, 3), (3, 4)]
And I need to check if all of them lie on the same line. I also decided to consider 3 cases to avoid dividing by zero when x1 == x2.
So here's my code in Python:
# 2 points always lie on a line
if n <= 2:
print("yes")
else:
# leave only unique points
points = list(set(points))
x1, y1 = points[0]
x2, y2 = points[1]
# if two points have the same x coordinate
# then they lie on a vertical line and
# all other points have to have the same x coordinate too
if x2 == x1:
for i in range(2, len(points)):
if points[i][0] != x1:
print("no")
break
else: print("yes")
# same with a horizontal line
elif y2 == y1:
for i in range(2, len(points)):
if points[i][1] != y1:
print("no")
break
else: print("yes")
else:
a = (y2-y1)/(x2-x1)
b = y2 - a * x2
for i in range(2, len(points)):
x, y = points[i]
if (y != a * x + b):
print("no")
break
else: print("yes")
It seems that I have a mistake somewhere in the code but I don't really understand what it is.
Using cross product of vectors eliminates the complexity of having to deal with special cases where division by zero might happen. Three points are collinear if the cross product of the vectors formed by the two vectors defined by the 3 points is equal to zero:
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def cross(self, other):
return self.x * other.y - self.y * other.x
def are_collinear(three_points):
a, b, c = three_points
# better use math.isclose than == to check for floats
return math.isclose((b-a).cross(c-a), 0.0)
points = [Point(1, 2), Point(2, 3), Point(3, 4)]
print(are_collinear(points))
# True
points = [Point(1, 2), Point(3, 3), Point(3, 4)]
print(are_collinear(points))
# False
From any point in the list (e.g. first one) if all other points have the same slope with that one, then they are on the same line.
def sameLine(points):
x0,y0 = points[0]
points = [ (x,y) for x,y in points if x != x0 or y != y0 ] # Other points
slopes = [ (y-y0)/(x-x0) if x!=x0 else None for x,y in points ] # None for vertical Line
return all( s == slopes[0] for s in slopes)
I have a travel time map, I want to get the integer points along the shortest path from source to receiver.
My present solution is that I make a runge-kutta integration from the receiver location and get a series of float points. Then I sample every 5 or some number of points and assume it a straight line between in order to use the Bresenham's line algorithm. With this approach, I will get the integer points.
However, it's not enough fast. Because I need to calculate a lot of receivers' shortest path, the sum of time will be very large.
I used line_profiler to analysis the time-consuming, which shows the major part of time is for function ruge-kutta and its calling function get_velocity
codes are below
def optimal_path_2d(gradx_interp,
grady_interp,
starting_point,
dx,
N=100):
"""
Find the optimal path from starting_point to the zero contour
of travel_time. dx is the grid spacing
Solve the equation x_t = - grad t / | grad t |
"""
def get_velocity(position):
""" return normalized velocity at pos """
x, y = position
vel = np.array([gradx_interp(y, x)[0][0], grady_interp(y, x)[0][0]])
return vel / np.linalg.norm(vel)
def runge_kutta(pos, ds):
""" Fourth order Runge Kutta point update """
k1 = ds * get_velocity(pos)
k2 = ds * get_velocity(pos - k1 / 2.0)
k3 = ds * get_velocity(pos - k2 / 2.0)
k4 = ds * get_velocity(pos - k3)
return pos - (k1 + 2 * k2 + 2 * k3 + k4) / 6.0
x = runge_kutta(starting_point, dx)
xl, yl = [], []
for i in range(N):
xl.append(x[0])
yl.append(x[1])
x = runge_kutta(x, dx)
distance = ((x[0] - xl[-1])**2 +
(x[1] - yl[-1])**2)**0.5
if distance < dx*0.9:
break
return yl, xl
def get_curve(x_curve, y_curve, num_interval):
"""Curve Algorithm based on Bresenham's Line Algorithm
Produces a list of tuples
"""
num = len(x_curve)
if num < num_interval:
print("num_interval is too large.")
ret_set = set()
x0 = x_curve[0]
y0 = y_curve[0]
for i in range(num_interval, num, num_interval):
x1 = x_curve[i]
y1 = y_curve[i]
points_on_line = get_line((x0, y0), (x1, y1))
ret_set.update(points_on_line)
x0 = x1
y0 = y1
if num % num_interval != 0:
n = int(num/num_interval)*num_interval
x0 = x_curve[n]
y0 = y_curve[n]
x1 = x_curve[-1]
y1 = y_curve[-1]
points_on_line = get_line((x0, y0), (x1, y1))
ret_set.update(points_on_line)
return list(ret_set)
def get_line(start, end):
"""modifed version of Bresenham's Line Algorithm
Produces a list of tuples from start and end
>>> points1 = get_line((0, 0), (3, 4))
>>> points2 = get_line((3, 4), (0, 0))
>>> assert(set(points1) == set(points2))
>>> print points1
[(0, 0), (1, 1), (1, 2), (2, 3), (3, 4)]
>>> print points2
[(3, 4), (2, 3), (1, 2), (1, 1), (0, 0)]
"""
# Setup initial conditions
x1, y1 = (int(x) for x in start)
x2, y2 = (int(x) for x in end)
dx = x2 - x1
dy = y2 - y1
# Determine how steep the line is
is_steep = abs(dy) > abs(dx)
# Rotate line
if is_steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
# Swap start and end points if necessary and store swap state
swapped = False
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
swapped = True
# Recalculate differentials
dx = x2 - x1
dy = y2 - y1
# Calculate error
error = int(dx / 2.0)
ystep = 1 if y1 < y2 else -1
# Iterate over bounding box generating points between start and end
y = y1
points = []
for x in range(x1, x2 + 1):
coord = (y, x) if is_steep else (x, y)
points.append(coord)
error -= abs(dy)
if error < 0:
y += ystep
error += dx
# Reverse the list if the coordinates were swapped
if swapped:
points.reverse()
return points
nx = 100
ny = 100
num_interval = 5
loc_src = (10, 10)
loc_rec = (70, 90)
coordx = np.arange(nx)
coordy = np.arange(ny)
X, Y = np.meshgrid(coordx, coords)
travel_time = (X-loc_src[0])**2/5 + (Y-loc_src[1])**2/10 # for simplicity
grad_t_y, grad_t_x = np.gradient(travel_time, dx)
if isinstance(travel_time, np.ma.MaskedArray):
grad_t_y[grad_t_y.mask] = 0.0
grad_t_y = grad_t_y.data
grad_t_x[grad_t_x.mask] = 0.0
grad_t_x = grad_t_x.data
gradx_interp = RectBivariateSpline(coordy, coordx, grad_t_x)
grady_interp = RectBivariateSpline(coordy, coordx, grad_t_y)
yl, xl = optimal_path(gradx_interp, grady_interp, loc_rec, dx)
grid_indx = get_curve(xl, yl, num_interval)
I hear that Cython will be faster, then I learn a little recently and try it. the result is only 2 faster than codes above because I'm really new to Cython. The code below is incomplete, and I just wrote it for testing.
import numpy as np
from numpy.core.umath_tests import inner1d
def func(X_interp, Y_interp):
def get_velocity(double x, double y ):
""" return normalized velocity at pos """
cdef double vel[2], norm
a = X_interp(y, x)
vel[0] = a[0][0]
b = Y_interp(y, x)
vel[1] = b[0][0]
# norm = (vel[0]**2 + vel[1]**2)**0.5
# vel[0] = vel[0]/norm
# vel[1] = vel[1]/norm
return vel
def runge_kutta(double x, double y, double ds):
""" Fourth order Runge Kutta point update """
cdef double k1[2], k2[2], k3[2], k4[2], r[2], pos[2]
pos[0] = x; pos[1] = y
k1 = get_velocity(pos[0], pos[1])
k2 = get_velocity(pos[0] - k1[0]/2.0*ds,pos[1] - k1[1]/2.0*ds)
k3 = get_velocity(pos[0] - k2[0]/2.0*ds,pos[1] - k2[1]/2.0*ds)
k4 = get_velocity(pos[0] - k3[0]/2.0*ds,pos[1] - k3[1]/2.0*ds)
cdef size_t i
for i in range(2):
r[i] = pos[i] - ds * (k1[i] + 2*k2[i] + 2*k3[i] + k4[i])/6.0
return r
for i in range(50):
runge_kutta(0, 0, 1.)
# print(runge_kutta(0, 0, 1.))
I have these coordinates:
coord = [(10,10), (13,10), (13,13)]
Now i need new coordinates.
The way between two coordinates is always one.
For example:
(10,10)
(11,10)
(12,10)
(13,10)
(13,11)
(13,12)
(13,13)
Any ideas?
#
I found the solution.
for n in range(len(coord)-1):
lengthx = coord[n+1][0] - coord[n][0]
lengthy = coord[n+1][1] - coord[n][1]
length = (lengthx**2 + lengthy**2)**.5
for m in range(length):
print coord[n][0]+lengthx/length*m, coord[n][1]+lengthy/length*m
A simple variation on Bresenham's line algorithm will achieve what you want using integer arithmetic only (so it should be noticeably faster):
def steps(path):
if len(path) > 0:
for i in range(1, len(path)):
for step in steps_between(path[i - 1], path[i]):
yield step
yield path[-1]
def steps_between(start, end):
x0, y0 = start
x1, y1 = end
steep = abs(y1 - y0) > abs(x1 - x0)
if steep:
x0, y0 = y0, x0
x1, y1 = y1, x1
if y0 > y1:
x0, x1 = x1, x0
y0, y1 = y1, y0
if y0 < y1:
ystep = 1
else:
ystep = -1
deltax = x1 - x0
deltay = abs(y1 - y0)
error = -deltax / 2
y = y0
for x in range(x0, x1):
if steep:
yield (y, x)
else:
yield (x, y)
error += deltay
if error > 0:
y += ystep
error -= deltax
if steep:
yield (y, x)
else:
yield (x, y)
coords = [(10, 10), (13, 10), (13, 13)]
print "\n".join(str(step) for step in steps(coords))
The above prints:
(10, 10)
(11, 10)
(12, 10)
(13, 10)
(13, 11)
(13, 12)
(13, 13)
Of course, Bresenham works as expected when both x and y change between two points on the path:
coords = [(10, 10), (13, 12), (15, 13)]
print "\n".join(str(step) for step in steps(coords))
That prints:
(10, 10)
(11, 10)
(11, 11)
(12, 11)
(12, 12)
(13, 12)
(14, 12)
(14, 13)
(15, 13)