Efficiently get lattice points on line in square - python

A lattice point is a point with integer coordinates.
The line is the perpendicular bisect between two lattice points A and B. (Every point on that line is equidistant from points A and B.)
How can I efficiently compute the lattice points on that perpendicular bisect line within the square 0,0 → N,N?
Here is a square, with some example points A and B ↓
The point M is the midpoint between A and B.
My thinking has taken me thus far:
The points LA, LB and RA, RB are a square you can easily compute to the left and right sides of the line AB.
The midpoint LM between A and LB, and the midpoint RM A and RB is also on the perpendicular bisect line.
So how can you use this information to very quickly compute the lattice points on the perpendicular bisect line between two points?
this isn't homework, its just hobby coding

I might be over-thinking this, going by matovitch's latest code draft (which I've only had a brief glance at), but anyway...
Let A = (A.x, A.y), B = (B.x, B.y), where (A.x, A.y, B.x, B.y) are integers.
Then line p, the perpendicular bisector of AB, passes through
M = (M.x, M.y) = ((A.x + B.x)/2, (A.y + B.y)/2)
The product of the slopes of AB and p is -1, thus the slope of p is
-(B.x - A.x) / (B.y - A.y)
and hence in point-slope form the equation of p is
(y - M.y) / (x - M.x) = (A.x - B.x) / (B.y - A.y)
Rearranging,
y*(B.y - A.y) + x*(B.x - A.x) = M.y * (B.y - A.y) + M.x * (B.x - A.x)
= ((B.y + A.y) * (B.y - A.y) + (B.x + A.x) * (B.x - A.x)) / 2
= (B.y^2 - A.y^2 + B.x^2 - A.x^2) / 2
Clearly, for any lattice point (x, y), y*(B.y - A.y) + x*(B.x - A.x) must be an integer. So the line p will only pass through lattice points if (B.y^2 - A.y^2 + B.x^2 - A.x^2) is even.
Now (B.y^2 - A.y^2 + B.x^2 - A.x^2) is even if & only if (A.x + B.x + A.y + B.y) is even, which is true if an even number of (A.x, A.y, B.x, B.y) are odd. In what follows, I assume that (A.x + B.x + A.y + B.y) is even.
Let
dx = (B.x - A.x)
dy = (B.y - A.y)
s = (B.y^2 - A.y^2 + B.x^2 - A.x^2) / 2
So the equation of p is
y * dy + x * dx = s
Because y, dy, x, dx & s are all integers that equation is a linear Diophantine equation, and the standard way to find the solutions of such an equation is to use the extended Euclidean algorithm. Our equation will only have solutions if the gcd (greatest common divisor) of dx & dy divides s. Fortunately, that's true in this case, but I won't give the proof here.
Let Y, X be a solution of y * dy + x * dx = g, where g is the gcd(dx, dy), i.e.,
Y * dy + X * dx = g
Y * dy/g + X * dx/g = 1
Let dy' = dy/g, dx' = dx/g, s' = s/g, so
Y * dy' + X * dx' = 1
Dividing the last equation for p through by g, we get
y * dy' + x * dx' = s'
And we can now construct one solution for it.
(Y * s') * dy' + (X * s') * dx' = s'
i.e., (X * s', Y * s') is a lattice point on the line.
We can get all solutions like this:
(Y * s' + k * dx') * dy' + (X * s' - k * dy') * dx' = s', for all integers k.
To restrict the solutions to the grid from (0, 0) to (W, H), we need to solve these inequalities for k:
0 <= X * s' - k * dy' <= W and 0 <= Y * s' + k * dx' <= H
I won't show the solutions of those inequalities right here; for the details see the code below.
#! /usr/bin/env python
''' Lattice Line
Find lattice points, i.e, points with integer co-ordinates,
on the line that is the perpendicular bisector of the line segment AB,
where A & B are lattice points.
See http://stackoverflow.com/q/31265139/4014959
Written by PM 2Ring 2015.07.08
Code for Euclid's algorithm & the Diophantine solver written 2010.11.27
'''
from __future__ import division
import sys
from math import floor, ceil
class Point(object):
''' A simple 2D point '''
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return "Point(%s, %s)" % (self.x, self.y)
def __str__(self):
return "(%s, %s)" % (self.x, self.y)
def euclid(a, b):
''' Euclid's extended algorithm for the GCD.
Returns a list of tuples of (dividend, quotient, divisor, remainder)
'''
if a < b:
a, b = b, a
k = []
while True:
q, r = a // b, a % b
k.append((a, q, b, r))
if r == 0:
break
a, b = b, r
return k
def dio(aa, bb):
''' Linear Diophantine solver
Returns [x, aa, y, bb, d]: x*aa + y*bb = d
'''
a, b = abs(aa), abs(bb)
swap = a < b
if swap:
a, b = b, a
#Handle trivial cases
if a == b:
eqn = [2, a, -1, a]
elif a % b == 0:
q = a // b
eqn = [1, a, 1-q, b]
else:
#Generate quotients & remainders list
z = euclid(a, b)[::-1]
#Build equation from quotients & remainders
eqn = [0, 0, 1, 0]
for v in z[1:]:
eqn = [eqn[2], v[0], eqn[0] - eqn[2]*v[1], v[2]]
#Rearrange & fix signs, if required
if swap:
eqn = eqn[2:] + eqn[:2]
if aa < 0:
eqn[:2] = [-eqn[0], -eqn[1]]
if bb < 0:
eqn[2:] = [-eqn[2], -eqn[3]]
d = eqn[0]*eqn[1] + eqn[2]*eqn[3]
if d < 0:
eqn[0], eqn[2], d = -eqn[0], -eqn[2], -d
return eqn + [d]
def lattice_line(pA, pB, pC):
''' Find lattice points, i.e, points with integer co-ordinates, on
the line that is the perpendicular bisector of the line segment AB,
Only look for points in the rectangle from (0,0) to C
Let M be the midpoint of AB. Then M = ((A.x + B.x)/2, (A.y + B.y)/2),
and the equation of the perpendicular bisector of AB is
(y - M.y) / (x - M.x) = (A.x - B.x) / (B.y - A.y)
'''
nosolutions = 'No solutions found'
dx = pB.x - pA.x
dy = pB.y - pA.y
#Test parity of co-ords to see if there are solutions
if (dx + dy) % 2 == 1:
print nosolutions
return
#Handle horizontal & vertical lines
if dx == 0:
#AB is vertical, so bisector is horizontal
y = pB.y + pA.y
if dy == 0 or y % 2 == 1:
print nosolutions
return
y //= 2
for x in xrange(pC.x + 1):
print Point(x, y)
return
if dy == 0:
#AB is horizontal, so bisector is vertical
x = pB.x + pA.x
if x % 2 == 1:
print nosolutions
return
x //= 2
for y in xrange(pC.y + 1):
print Point(x, y)
return
#Compute s = ((pB.x + pA.x)*dx + (pB.y + pA.y)*dy) / 2
#s will always be an integer since (dx + dy) is even
#The desired line is y*dy + x*dx = s
s = (pB.x**2 - pA.x**2 + pB.y**2 - pA.y**2) // 2
#Find ex, ey, g: ex * dx + ey * dy = g, where g is the gcd of (dx, dy)
#Note that g also divides s
eqn = dio(dx, dy)
ex, ey, g = eqn[::2]
#Divide the parameters of the equation by the gcd
dx //= g
dy //= g
s //= g
#Find lattice limits
xlo = (ex * s - pC.x) / dy
xhi = ex * s / dy
if dy < 0:
xlo, xhi = xhi, xlo
ylo = -ey * s / dx
yhi = (pC.y - ey * s) / dx
if dx < 0:
ylo, yhi = yhi, ylo
klo = int(ceil(max(xlo, ylo)))
khi = int(floor(min(xhi, yhi)))
print 'Points'
for k in xrange(klo, khi + 1):
x = ex * s - dy * k
y = ey * s + dx * k
assert x*dx + y*dy == s
print Point(x, y)
def main():
if len(sys.argv) != 7:
print ''' Find lattice points, i.e, points with integer co-ordinates,
on the line that is the perpendicular bisector of the line segment AB,
where A & B are lattice points with co-ords (xA, yA) & (xB, yB).
Only print lattice points in the rectangle from (0, 0) to (W, H)
Usage:
%s xA yA xB yB W H''' % sys.argv[0]
exit(1)
coords = [int(s) for s in sys.argv[1:]]
pA = Point(*coords[0:2])
pB = Point(*coords[2:4])
pC = Point(*coords[4:6])
lattice_line(pA, pB, pC)
if __name__ == '__main__':
main()
I haven't tested this code extensively, but it appears to work correctly. :)

Ok I sure did not explained my solution clearly, let's start again. Given a grid with twice the resolution, the middle point M will be on the grid. The minimal direction vector of the perpendicular bissector is given by V = (yB - yA, xA - xB) / gcd(yB - yA, xA - xB). Then we look at M and V modulo the lattice Z/2Z x Z/2Z to check if one can find a point M + iV with even coordinates (aka on the coarse grid). We can then compute a starting point S = M + jV (j = 0 or 1 in fact) on the lattice and get the famous set of points as {S + iV, i integer}.
[Now running ;)]
This C++ code print S and V, aka the nearest lattice point to the middle and the vector one can add or subtract to get the next/previous lattice point. You then have to filter the points to get those inside the square (test it here : http://coliru.stacked-crooked.com/a/ba9f8aec45e1c2ea) :
int gcd(int n1, int n2)
{
n1 = (n1 > 0) ? n1 : -n1;
n2 = (n2 > 0) ? n2 : -n2;
if (n1 > n2)
{
int t = n1;
n1 = n2;
n2 = t;
}
while (n2 % n1 != 0)
{
int tmp = n2 % n1;
n2 = n1;
n1 = tmp;
}
return n1;
}
struct Point
{
const Point& operator=(const Point& rhs)
{
x = rhs.x;
y = rhs.y;
return *this;
}
const Point& operator+=(const Point& rhs)
{
x += rhs.x;
y += rhs.y;
return *this;
}
const Point& operator-=(const Point& rhs)
{
x += rhs.x;
y += rhs.y;
return *this;
}
const Point& operator/=(int rhs)
{
x /= rhs;
y /= rhs;
return *this;
}
const Point& reduce()
{
return *this /= gcd(x, y);
}
int x;
int y;
};
const Point operator+(Point lhs, const Point& rhs)
{
lhs += rhs;
return lhs;
}
const Point operator-(Point lhs, const Point& rhs)
{
lhs -= rhs;
return lhs;
}
const Point operator/(Point lhs, int rhs)
{
lhs /= rhs;
return lhs;
}
bool findBase(Point& p1, Point& p2, Point& oBase, Point& oDir)
{
Point m = p1 + p2;
Point v = {p2.y - p1.y, p1.x - p2.x};
oDir = v.reduce();
if (m.x % 2 == 0 && m.y % 2 == 0)
{
oBase = m / 2;
return true;
}
else if (((m.x % 2 == 0 && v.x % 2 == 0) &&
(m.y % 2 == 1 && v.y % 2 == 1)) ||
((m.x % 2 == 1 && v.x % 2 == 1) &&
(m.y % 2 == 0 && v.y % 2 == 0)) ||
((m.x % 2 == 1 && v.x % 2 == 1) &&
(m.y % 2 == 1 && v.y % 2 == 1)))
{
oBase = (m + v) / 2;
return true;
}
else
{
return false;
}
}

Related

does cvxpy need starting conditions provided is zeros are not satisfying contraints?

I am trying to modify the maxrect library to solve for maximum rectangles unconstrained by orientation.
looking at the code I see the constraints are:
""" :param coordinates:
A list of of [x, y] pairs describing a closed, convex polygon.
"""
coordinates = np.array(coordinates)
x_range = np.max(coordinates, axis=0)[0]-np.min(coordinates, axis=0)[0]
y_range = np.max(coordinates, axis=0)[1]-np.min(coordinates, axis=0)[1]
scale = np.array([x_range, y_range])
sc_coordinates = coordinates/scale
poly = Polygon(sc_coordinates)
inside_pt = (poly.representative_point().x,
poly.representative_point().y)
A1, A2, B = pts_to_leq(sc_coordinates)
bl = cvxpy.Variable(2)
tr = cvxpy.Variable(2)
br = cvxpy.Variable(2)
tl = cvxpy.Variable(2)
obj = cvxpy.Maximize(cvxpy.log(tr[0] - bl[0]) + cvxpy.log(tr[1] - bl[1]))
constraints = [bl[0] == tl[0],
br[0] == tr[0],
tl[1] == tr[1],
bl[1] == br[1],
]
for i in range(len(B)):
if inside_pt[0] * A1[i] + inside_pt[1] * A2[i] <= B[i]:
constraints.append(bl[0] * A1[i] + bl[1] * A2[i] <= B[i])
constraints.append(tr[0] * A1[i] + tr[1] * A2[i] <= B[i])
constraints.append(br[0] * A1[i] + br[1] * A2[i] <= B[i])
constraints.append(tl[0] * A1[i] + tl[1] * A2[i] <= B[i])
else:
constraints.append(bl[0] * A1[i] + bl[1] * A2[i] >= B[i])
constraints.append(tr[0] * A1[i] + tr[1] * A2[i] >= B[i])
constraints.append(br[0] * A1[i] + br[1] * A2[i] >= B[i])
constraints.append(tl[0] * A1[i] + tl[1] * A2[i] >= B[i])
that is, they convert each point in the circumscribing polygon to the Ax + Ay = B and check if the corners of the rectangle are inside of it, and maximize the diagonals. additionally, there are 4 constraints that ensure that the angles of the corners are aligned with the reference frame.
I was thinking I could just remove those 4 constraints.
However, this allows the rectangle to exceed the bounds of the circumscribing polygon. That may mean that I'm not entirely correct about the purpose of the above constraints. It could also be that now those points are allowed to drift from their cardinal orientation within the reference frame, so I tried adding different constraints to maintain cardinality of the rectangle's points:
if aligned:
constraints.append(bl[0] == tl[0])
constraints.append(br[0] == tr[0])
constraints.append(tl[1] == tr[1])
constraints.append(bl[1] == br[1])
else:
constraints.append(bl[0] < br[0])
constraints.append(bl[0] < tr[0])
constraints.append(tl[0] < br[0])
constraints.append(tl[0] < tr[0])
constraints.append(bl[1] < tl[1])
constraints.append(bl[1] < tr[1])
constraints.append(br[1] < tl[1])
constraints.append(br[1] < tr[1])
Could someone help me to see what I am missing?
sample problem:
square = Polygon([(0,0), (0,2), (2,2), (2,0)], [
[(0.5,0.5), (0.5,1.2), (1,1.2), (1,0.5)],
[(1.1,1.1), (1.1,1.5), (1.5,1.5), (1.5,1.1)]
])
line = LineString((square.exterior.coords[0], square.exterior.coords[1]))
max_hull = find_maximal_convex_hull(line, square)
print('max hull', max_hull.wkt, max_hull.area)
# this line calls my cvypy script
pa = get_maximal_rectangle(max_hull.exterior.coords)
max_rect = Polygon([(pa[0][0],pa[0][1]), (pa[0][0],pa[1][1]), (pa[1][0],pa[1][1]), (pa[1][0],pa[0][1])])
print('max rectangle', pa)
plt.axis(xmin=-0.125,xmax=2.125,ymin=-0.125,ymax=2.125)
plt.plot(*square.exterior.xy, color='g')
[plt.plot(*i.xy, color='y') for i in square.interiors]
plt.plot(*max_hull.exterior.xy, color='b')
[plt.plot(*i.xy, color='r') for i in max_hull.interiors]
plt.plot(*max_rect.exterior.xy, color='r')
plt.show()
in the above code I am given a complex shape and an edge. I must cut this down to a (roughly/nearly) largest convex shape, and the edge must intersect that shape. this work I've already done, it is assigned to the variable max_hull.
Now, I want the largest rectangle in the shape, regardless of orientation. In the image below, drawn from the code above, I show the outer square in green (the left edge is the required edge), its holes in yellow, the remaining convex shape, that is the outer polygon given to my cvypy in blue, -- it extends from the visible bottom line up to the top -- and the candidate rectangle returned from cvxpy in red.
the log output is:
<ipython-input-2-efd2419ff139>:272: ShapelyDeprecationWarning: Iteration over multi-part geometries is deprecated and will be removed in Shapely 2.0. Use the `geoms` property to access the constituent parts of a multi-part geometry.
for base in split(S, ll):
455 unique polygons considered
max hull POLYGON ((0 0.95, 0 2, 2 2, 2 1.95, 0 0.95)) 1.0999999999999999
max rectangle (array([7.13062842e-09, 9.50000011e-01]), array([1.99999998, 1.99999999]))
in order to use the maxrect library with modern cvypy and python, change get_max_rectangle (in init.py) solve statement to prob.solve() and the return statement. after applying my code, this is the customized function I am using:
import numpy as np
import cvxpy
from shapely.geometry import Polygon
def rect2poly(ll, ur):
"""
Convert rectangle defined by lower left/upper right
to a closed polygon representation.
"""
x0, y0 = ll
x1, y1 = ur
return [
[x0, y0],
[x0, y1],
[x1, y1],
[x1, y0],
[x0, y0]
]
def get_intersection(coords):
"""Given an input list of coordinates, find the intersection
section of corner coordinates. Returns geojson of the
interesection polygon.
"""
ipoly = None
for coord in coords:
if ipoly is None:
ipoly = Polygon(coord)
else:
tmp = Polygon(coord)
ipoly = ipoly.intersection(tmp)
# close the polygon loop by adding the first coordinate again
first_x = ipoly.exterior.coords.xy[0][0]
first_y = ipoly.exterior.coords.xy[1][0]
ipoly.exterior.coords.xy[0].append(first_x)
ipoly.exterior.coords.xy[1].append(first_y)
inter_coords = zip(
ipoly.exterior.coords.xy[0], ipoly.exterior.coords.xy[1])
inter_gj = {"geometry":
{"coordinates": [inter_coords],
"type": "Polygon"},
"properties": {}, "type": "Feature"}
return inter_gj, inter_coords
def two_pts_to_line(pt1, pt2):
"""
Create a line from two points in form of
a1(x) + a2(y) = b
"""
pt1 = [float(p) for p in pt1]
pt2 = [float(p) for p in pt2]
try:
slp = (pt2[1] - pt1[1]) / (pt2[0] - pt1[0])
except ZeroDivisionError:
slp = 1e5 * (pt2[1] - pt1[1])
a1 = -slp
a2 = 1.
b = -slp * pt1[0] + pt1[1]
return a1, a2, b
def pts_to_leq(coords):
"""
Converts a set of points to form Ax = b, but since
x is of length 2 this is like A1(x1) + A2(x2) = B.
returns A1, A2, B
"""
A1 = []
A2 = []
B = []
for i in range(len(coords) - 1):
pt1 = coords[i]
pt2 = coords[i + 1]
a1, a2, b = two_pts_to_line(pt1, pt2)
A1.append(a1)
A2.append(a2)
B.append(b)
return A1, A2, B
def get_maximal_rectangle(coordinates, aligned=False):
"""
Find the largest, inscribed, axis-aligned rectangle.
:param coordinates:
A list of of [x, y] pairs describing a closed, convex polygon.
"""
coordinates = np.array(coordinates)
x_range = np.max(coordinates, axis=0)[0]-np.min(coordinates, axis=0)[0]
y_range = np.max(coordinates, axis=0)[1]-np.min(coordinates, axis=0)[1]
scale = np.array([x_range, y_range])
sc_coordinates = coordinates/scale
rep_point = Polygon(sc_coordinates).representative_point()
inside_pt = (rep_point.x, rep_point.y)
A1, A2, B = pts_to_leq(sc_coordinates)
bl = cvxpy.Variable(2)
tr = cvxpy.Variable(2)
br = cvxpy.Variable(2)
tl = cvxpy.Variable(2)
obj = cvxpy.Maximize(cvxpy.log(tr[0] - bl[0]) + cvxpy.log(tr[1] - bl[1]))
constraints = []
if aligned:
constraints.append(bl[0] == tl[0])
constraints.append(br[0] == tr[0])
constraints.append(tl[1] == tr[1])
constraints.append(bl[1] == br[1])
else:
constraints.append(bl[0] < br[0])
constraints.append(bl[0] < tr[0])
constraints.append(tl[0] < br[0])
constraints.append(tl[0] < tr[0])
constraints.append(bl[1] < tl[1])
constraints.append(bl[1] < tr[1])
constraints.append(br[1] < tl[1])
constraints.append(br[1] < tr[1])
for i in range(len(B)):
if inside_pt[0] * A1[i] + inside_pt[1] * A2[i] <= B[i]:
constraints.append(bl[0] * A1[i] + bl[1] * A2[i] <= B[i])
constraints.append(tr[0] * A1[i] + tr[1] * A2[i] <= B[i])
constraints.append(br[0] * A1[i] + br[1] * A2[i] <= B[i])
constraints.append(tl[0] * A1[i] + tl[1] * A2[i] <= B[i])
else:
constraints.append(bl[0] * A1[i] + bl[1] * A2[i] >= B[i])
constraints.append(tr[0] * A1[i] + tr[1] * A2[i] >= B[i])
constraints.append(br[0] * A1[i] + br[1] * A2[i] >= B[i])
constraints.append(tl[0] * A1[i] + tl[1] * A2[i] >= B[i])
prob = cvxpy.Problem(obj, constraints)
#prob.solve(solver=cvxpy.CVXOPT, verbose=False, max_iters=1000, reltol=1e-9)
#prob.solve(solver=cvxpy.SCS, verbose=True, use_indirect=False, max_iters=int(1e5))
prob.solve()
bottom_left = np.array(bl.value).T * scale
top_right = np.array(tr.value).T * scale
#return list(bottom_left[0]), list(top_right[0])
return bottom_left, top_right
No other function in the above was modified.
The problem of finding an area-maximizing unconstrained rectangle contained in a polygon is non-convex, so you simply have no chance to express it using cvxpy.
The constraints you removed mean you have an axis-aligned rectangle, without them you just have an arbitrary 4-gon contained in the big polygon for which you are maximizing the product of two sides - not something intuitive or what you wanted.
See for instance https://docs.mosek.com/modeling-cookbook/powo.html#maximum-volume-cuboid

How to increase FPS in ursina python

I want to create survival games with infinite block terrain(like Minecraft). So i using ursina python game engine, you can see it here
So i using perlin noise to create the terrain with build-in ursina block model. I test for first 25 block and it work pretty good with above 100 FPS, so i start increase to 250 block and more because I want a infinite terrain. But i ran to some problem, when i increase to 100 block or more, my FPS start to decrease below 30 FPS (With i create just one layer).
Here is my code:
#-------------------------------Noise.py(I got on the github)-------------------------
# Copyright (c) 2008, Casey Duncan (casey dot duncan at gmail dot com)
# see LICENSE.txt for details
"""Perlin noise -- pure python implementation"""
__version__ = '$Id: perlin.py 521 2008-12-15 03:03:52Z casey.duncan $'
from math import floor, fmod, sqrt
from random import randint
# 3D Gradient vectors
_GRAD3 = ((1,1,0),(-1,1,0),(1,-1,0),(-1,-1,0),
(1,0,1),(-1,0,1),(1,0,-1),(-1,0,-1),
(0,1,1),(0,-1,1),(0,1,-1),(0,-1,-1),
(1,1,0),(0,-1,1),(-1,1,0),(0,-1,-1),
)
# 4D Gradient vectors
_GRAD4 = ((0,1,1,1), (0,1,1,-1), (0,1,-1,1), (0,1,-1,-1),
(0,-1,1,1), (0,-1,1,-1), (0,-1,-1,1), (0,-1,-1,-1),
(1,0,1,1), (1,0,1,-1), (1,0,-1,1), (1,0,-1,-1),
(-1,0,1,1), (-1,0,1,-1), (-1,0,-1,1), (-1,0,-1,-1),
(1,1,0,1), (1,1,0,-1), (1,-1,0,1), (1,-1,0,-1),
(-1,1,0,1), (-1,1,0,-1), (-1,-1,0,1), (-1,-1,0,-1),
(1,1,1,0), (1,1,-1,0), (1,-1,1,0), (1,-1,-1,0),
(-1,1,1,0), (-1,1,-1,0), (-1,-1,1,0), (-1,-1,-1,0))
# A lookup table to traverse the simplex around a given point in 4D.
# Details can be found where this table is used, in the 4D noise method.
_SIMPLEX = (
(0,1,2,3),(0,1,3,2),(0,0,0,0),(0,2,3,1),(0,0,0,0),(0,0,0,0),(0,0,0,0),(1,2,3,0),
(0,2,1,3),(0,0,0,0),(0,3,1,2),(0,3,2,1),(0,0,0,0),(0,0,0,0),(0,0,0,0),(1,3,2,0),
(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),
(1,2,0,3),(0,0,0,0),(1,3,0,2),(0,0,0,0),(0,0,0,0),(0,0,0,0),(2,3,0,1),(2,3,1,0),
(1,0,2,3),(1,0,3,2),(0,0,0,0),(0,0,0,0),(0,0,0,0),(2,0,3,1),(0,0,0,0),(2,1,3,0),
(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),
(2,0,1,3),(0,0,0,0),(0,0,0,0),(0,0,0,0),(3,0,1,2),(3,0,2,1),(0,0,0,0),(3,1,2,0),
(2,1,0,3),(0,0,0,0),(0,0,0,0),(0,0,0,0),(3,1,0,2),(0,0,0,0),(3,2,0,1),(3,2,1,0))
# Simplex skew constants
_F2 = 0.5 * (sqrt(3.0) - 1.0)
_G2 = (3.0 - sqrt(3.0)) / 6.0
_F3 = 1.0 / 3.0
_G3 = 1.0 / 6.0
class BaseNoise:
"""Noise abstract base class"""
permutation = (151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,
129,22,39,253,9,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180)
period = len(permutation)
# Double permutation array so we don't need to wrap
permutation = permutation * 2
randint_function = randint
def __init__(self, period=None, permutation_table=None, randint_function=None):
"""Initialize the noise generator. With no arguments, the default
period and permutation table are used (256). The default permutation
table generates the exact same noise pattern each time.
An integer period can be specified, to generate a random permutation
table with period elements. The period determines the (integer)
interval that the noise repeats, which is useful for creating tiled
textures. period should be a power-of-two, though this is not
enforced. Note that the speed of the noise algorithm is indpendent of
the period size, though larger periods mean a larger table, which
consume more memory.
A permutation table consisting of an iterable sequence of whole
numbers can be specified directly. This should have a power-of-two
length. Typical permutation tables are a sequnce of unique integers in
the range [0,period) in random order, though other arrangements could
prove useful, they will not be "pure" simplex noise. The largest
element in the sequence must be no larger than period-1.
period and permutation_table may not be specified together.
A substitute for the method random.randint(a, b) can be chosen. The
method must take two integer parameters a and b and return an integer N
such that a <= N <= b.
"""
if randint_function is not None: # do this before calling randomize()
if not hasattr(randint_function, '__call__'):
raise TypeError(
'randint_function has to be a function')
self.randint_function = randint_function
if period is None:
period = self.period # enforce actually calling randomize()
if period is not None and permutation_table is not None:
raise ValueError(
'Can specify either period or permutation_table, not both')
if period is not None:
self.randomize(period)
elif permutation_table is not None:
self.permutation = tuple(permutation_table) * 2
self.period = len(permutation_table)
def randomize(self, period=None):
"""Randomize the permutation table used by the noise functions. This
makes them generate a different noise pattern for the same inputs.
"""
if period is not None:
self.period = period
perm = list(range(self.period))
perm_right = self.period - 1
for i in list(perm):
j = self.randint_function(0, perm_right)
perm[i], perm[j] = perm[j], perm[i]
self.permutation = tuple(perm) * 2
class SimplexNoise(BaseNoise):
"""Perlin simplex noise generator
Adapted from Stefan Gustavson's Java implementation described here:
http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
To summarize:
"In 2001, Ken Perlin presented 'simplex noise', a replacement for his classic
noise algorithm. Classic 'Perlin noise' won him an academy award and has
become an ubiquitous procedural primitive for computer graphics over the
years, but in hindsight it has quite a few limitations. Ken Perlin himself
designed simplex noise specifically to overcome those limitations, and he
spent a lot of good thinking on it. Therefore, it is a better idea than his
original algorithm. A few of the more prominent advantages are:
* Simplex noise has a lower computational complexity and requires fewer
multiplications.
* Simplex noise scales to higher dimensions (4D, 5D and up) with much less
computational cost, the complexity is O(N) for N dimensions instead of
the O(2^N) of classic Noise.
* Simplex noise has no noticeable directional artifacts. Simplex noise has
a well-defined and continuous gradient everywhere that can be computed
quite cheaply.
* Simplex noise is easy to implement in hardware."
"""
def noise2(self, x, y):
"""2D Perlin simplex noise.
Return a floating point value from -1 to 1 for the given x, y coordinate.
The same value is always returned for a given x, y pair unless the
permutation table changes (see randomize above).
"""
# Skew input space to determine which simplex (triangle) we are in
s = (x + y) * _F2
i = floor(x + s)
j = floor(y + s)
t = (i + j) * _G2
x0 = x - (i - t) # "Unskewed" distances from cell origin
y0 = y - (j - t)
if x0 > y0:
i1 = 1; j1 = 0 # Lower triangle, XY order: (0,0)->(1,0)->(1,1)
else:
i1 = 0; j1 = 1 # Upper triangle, YX order: (0,0)->(0,1)->(1,1)
x1 = x0 - i1 + _G2 # Offsets for middle corner in (x,y) unskewed coords
y1 = y0 - j1 + _G2
x2 = x0 + _G2 * 2.0 - 1.0 # Offsets for last corner in (x,y) unskewed coords
y2 = y0 + _G2 * 2.0 - 1.0
# Determine hashed gradient indices of the three simplex corners
perm = self.permutation
ii = int(i) % self.period
jj = int(j) % self.period
gi0 = perm[ii + perm[jj]] % 12
gi1 = perm[ii + i1 + perm[jj + j1]] % 12
gi2 = perm[ii + 1 + perm[jj + 1]] % 12
# Calculate the contribution from the three corners
tt = 0.5 - x0**2 - y0**2
if tt > 0:
g = _GRAD3[gi0]
noise = tt**4 * (g[0] * x0 + g[1] * y0)
else:
noise = 0.0
tt = 0.5 - x1**2 - y1**2
if tt > 0:
g = _GRAD3[gi1]
noise += tt**4 * (g[0] * x1 + g[1] * y1)
tt = 0.5 - x2**2 - y2**2
if tt > 0:
g = _GRAD3[gi2]
noise += tt**4 * (g[0] * x2 + g[1] * y2)
return noise * 70.0 # scale noise to [-1, 1]
def noise3(self, x, y, z):
"""3D Perlin simplex noise.
Return a floating point value from -1 to 1 for the given x, y, z coordinate.
The same value is always returned for a given x, y, z pair unless the
permutation table changes (see randomize above).
"""
# Skew the input space to determine which simplex cell we're in
s = (x + y + z) * _F3
i = floor(x + s)
j = floor(y + s)
k = floor(z + s)
t = (i + j + k) * _G3
x0 = x - (i - t) # "Unskewed" distances from cell origin
y0 = y - (j - t)
z0 = z - (k - t)
# For the 3D case, the simplex shape is a slightly irregular tetrahedron.
# Determine which simplex we are in.
if x0 >= y0:
if y0 >= z0:
i1 = 1; j1 = 0; k1 = 0
i2 = 1; j2 = 1; k2 = 0
elif x0 >= z0:
i1 = 1; j1 = 0; k1 = 0
i2 = 1; j2 = 0; k2 = 1
else:
i1 = 0; j1 = 0; k1 = 1
i2 = 1; j2 = 0; k2 = 1
else: # x0 < y0
if y0 < z0:
i1 = 0; j1 = 0; k1 = 1
i2 = 0; j2 = 1; k2 = 1
elif x0 < z0:
i1 = 0; j1 = 1; k1 = 0
i2 = 0; j2 = 1; k2 = 1
else:
i1 = 0; j1 = 1; k1 = 0
i2 = 1; j2 = 1; k2 = 0
# Offsets for remaining corners
x1 = x0 - i1 + _G3
y1 = y0 - j1 + _G3
z1 = z0 - k1 + _G3
x2 = x0 - i2 + 2.0 * _G3
y2 = y0 - j2 + 2.0 * _G3
z2 = z0 - k2 + 2.0 * _G3
x3 = x0 - 1.0 + 3.0 * _G3
y3 = y0 - 1.0 + 3.0 * _G3
z3 = z0 - 1.0 + 3.0 * _G3
# Calculate the hashed gradient indices of the four simplex corners
perm = self.permutation
ii = int(i) % self.period
jj = int(j) % self.period
kk = int(k) % self.period
gi0 = perm[ii + perm[jj + perm[kk]]] % 12
gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1]]] % 12
gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2]]] % 12
gi3 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1]]] % 12
# Calculate the contribution from the four corners
noise = 0.0
tt = 0.6 - x0**2 - y0**2 - z0**2
if tt > 0:
g = _GRAD3[gi0]
noise = tt**4 * (g[0] * x0 + g[1] * y0 + g[2] * z0)
else:
noise = 0.0
tt = 0.6 - x1**2 - y1**2 - z1**2
if tt > 0:
g = _GRAD3[gi1]
noise += tt**4 * (g[0] * x1 + g[1] * y1 + g[2] * z1)
tt = 0.6 - x2**2 - y2**2 - z2**2
if tt > 0:
g = _GRAD3[gi2]
noise += tt**4 * (g[0] * x2 + g[1] * y2 + g[2] * z2)
tt = 0.6 - x3**2 - y3**2 - z3**2
if tt > 0:
g = _GRAD3[gi3]
noise += tt**4 * (g[0] * x3 + g[1] * y3 + g[2] * z3)
return noise * 32.0
def lerp(t, a, b):
return a + t * (b - a)
def grad3(hash, x, y, z):
g = _GRAD3[hash % 16]
return x*g[0] + y*g[1] + z*g[2]
class TileableNoise(BaseNoise):
"""Tileable implemention of Perlin "improved" noise. This
is based on the reference implementation published here:
http://mrl.nyu.edu/~perlin/noise/
"""
def noise3(self, x, y, z, repeat, base=0.0):
"""Tileable 3D noise.
repeat specifies the integer interval in each dimension
when the noise pattern repeats.
base allows a different texture to be generated for
the same repeat interval.
"""
i = int(fmod(floor(x), repeat))
j = int(fmod(floor(y), repeat))
k = int(fmod(floor(z), repeat))
ii = (i + 1) % repeat
jj = (j + 1) % repeat
kk = (k + 1) % repeat
if base:
i += base; j += base; k += base
ii += base; jj += base; kk += base
x -= floor(x); y -= floor(y); z -= floor(z)
fx = x**3 * (x * (x * 6 - 15) + 10)
fy = y**3 * (y * (y * 6 - 15) + 10)
fz = z**3 * (z * (z * 6 - 15) + 10)
perm = self.permutation
A = perm[i]
AA = perm[A + j]
AB = perm[A + jj]
B = perm[ii]
BA = perm[B + j]
BB = perm[B + jj]
return lerp(fz, lerp(fy, lerp(fx, grad3(perm[AA + k], x, y, z),
grad3(perm[BA + k], x - 1, y, z)),
lerp(fx, grad3(perm[AB + k], x, y - 1, z),
grad3(perm[BB + k], x - 1, y - 1, z))),
lerp(fy, lerp(fx, grad3(perm[AA + kk], x, y, z - 1),
grad3(perm[BA + kk], x - 1, y, z - 1)),
lerp(fx, grad3(perm[AB + kk], x, y - 1, z - 1),
grad3(perm[BB + kk], x - 1, y - 1, z - 1))))
#--------------------------Math.py(For InverseLefp)--------------------------------
def Clamp(t: float, minimum: float, maximum: float):
"""Float result between a min and max values."""
value = t
if t < minimum:
value = minimum
elif t > maximum:
value = maximum
return value
def InverseLefp(a: float, b: float, value: float):
if a != b:
return Clamp((value - a) / (b - a), 0, 1)
return 0
#-----------------------------Game.py(Main code)----------------------
from ursina import *
from ursina.prefabs import *
from ursina.prefabs.first_person_controller import *
from Math import InverseLefp
import Noise
app = Ursina()
#The maximum height of the terrain
maxHeight = 10
#Control the width and height of the map
mapWidth = 10
mapHeight = 10
#A class that create a block
class Voxel(Button):
def __init__(self, position=(0,0,0)):
super().__init__(
parent = scene,
position = position,
model = 'cube',
origin_y = .5,
texture = 'white_cube',
color = color.color(0, 0, random.uniform(.9, 1.0)),
highlight_color = color.lime,
)
#Detect user key input
def input(self, key):
if self.hovered:
if key == 'right mouse down':
#Place block if user right click
voxel = Voxel(position=self.position + mouse.normal)
if key == 'left mouse down':
#Break block if user left click
destroy(self)
if key == 'escape':
#Exit the game if user press the esc key
app.userExit()
#Return perlin noise value between 0 and 1 with x, y position with scale = noiseScale
def GeneratedNoiseMap(y: int, x: int, noiseScale: float):
#Check if the noise scale was invalid or not
if noiseScale <= 0:
noiseScale = 0.001
sampleX = x / noiseScale
sampleY = y / noiseScale
#The Noise.SimplexNoise().noise2 will return the value between -1 and 1
perlinValue = Noise.SimplexNoise().noise2(sampleX, sampleY)
#The InverseLefp will make the value scale to between 0 and 1
perlinValue = InverseLefp(-1, 1, perlinValue)
return perlinValue
for z in range(mapHeight):
for x in range(mapWidth):
#Calculating the height of the block and round it to integer
height = round(GeneratedNoiseMap(z, x, 20) * maxHeight)
#Place the block and make it always below the player
block = Voxel(position=(x, height - maxHeight - 1, z))
#Set the collider of the block
block.collider = 'mesh'
#Character movement
player = FirstPersonController()
#Run the game
app.run()
All file in same folder.
It was working fine but the FPS is very low, so can anyone help?
I'm not able to test this code at the moment but this should serve as a starting point:
level_parent = Entity(model=Mesh(vertices=[], uvs=[]))
for z in range(mapHeight):
for x in range(mapWidth):
height = round(GeneratedNoiseMap(z, x, 20) * maxHeight)
block = Voxel(position=(x, height - maxHeight - 1, z))
level_parent.model.vertices.extend(block.model.vertices)
level_parent.collider = 'mesh' # call this only once after all vertices are set up
For texturing, you might have to add the block.uvs from each block to level_parent.model.uvs as well. Alternatively, call level_parent.model.project_uvs() after setting up the vertices.
On my version of ursina engine (5.0.0) only this code:
`
level_parent = Entity(model=Mesh(vertices=[], uvs=[]))
for z in range(mapHeight):
for x in range(mapWidth):
height = round(GeneratedNoiseMap(z, x, 20) * maxHeight)
block = Voxel(position=(x, height - maxHeight - 1, z))
#level_parent.model.vertices.extend(block.model.vertices)
level_parent.combine().vertices.extend(block.combine().vertices)
level_parent.collider = 'mesh'
`
is working.

Intersections between Geodesics (shortest distance paths) on the surface of a sphere

I've searched far and wide but have yet to find a suitable answer to this problem. Given two lines on a sphere, each defined by their start and end points, determine whether or not and where they intersect. I've found this site (http://mathforum.org/library/drmath/view/62205.html) which runs through a good algorithm for the intersections of two great circles, although I'm stuck on determining whether the given point lies along the finite section of the great circles.
I've found several sites which claim they've implemented this, Including some questions here and on stackexchange, but they always seem to reduce back to the intersections of two great circles.
The python class I'm writing is as follows and seems to almost work:
class Geodesic(Boundary):
def _SecondaryInitialization(self):
self.theta_1 = self.point1.theta
self.theta_2 = self.point2.theta
self.phi_1 = self.point1.phi
self.phi_2 = self.point2.phi
sines = math.sin(self.phi_1) * math.sin(self.phi_2)
cosines = math.cos(self.phi_1) * math.cos(self.phi_2)
self.d = math.acos(sines - cosines * math.cos(self.theta_2 - self.theta_1))
self.x_1 = math.cos(self.theta_1) * math.cos(self.phi_1)
self.x_2 = math.cos(self.theta_2) * math.cos(self.phi_2)
self.y_1 = math.sin(self.theta_1) * math.cos(self.phi_1)
self.y_2 = math.sin(self.theta_2) * math.cos(self.phi_2)
self.z_1 = math.sin(self.phi_1)
self.z_2 = math.sin(self.phi_2)
self.theta_wraps = (self.theta_2 - self.theta_1 > PI)
self.phi_wraps = ((self.phi_1 < self.GetParametrizedCoords(0.01).phi and
self.phi_2 < self.GetParametrizedCoords(0.99).phi) or (
self.phi_1 > self.GetParametrizedCoords(0.01).phi) and
self.phi_2 > self.GetParametrizedCoords(0.99))
def Intersects(self, boundary):
A = self.y_1 * self.z_2 - self.z_1 * self.y_2
B = self.z_1 * self.x_2 - self.x_1 * self.z_2
C = self.x_1 * self.y_2 - self.y_1 * self.x_2
D = boundary.y_1 * boundary.z_2 - boundary.z_1 * boundary.y_2
E = boundary.z_1 * boundary.x_2 - boundary.x_1 * boundary.z_2
F = boundary.x_1 * boundary.y_2 - boundary.y_1 * boundary.x_2
try:
z = 1 / math.sqrt(((B * F - C * E) ** 2 / (A * E - B * D) ** 2)
+ ((A * F - C * D) ** 2 / (B * D - A * E) ** 2) + 1)
except ZeroDivisionError:
return self._DealWithZeroZ(A, B, C, D, E, F, boundary)
x = ((B * F - C * E) / (A * E - B * D)) * z
y = ((A * F - C * D) / (B * D - A * E)) * z
theta = math.atan2(y, x)
phi = math.atan2(z, math.sqrt(x ** 2 + y ** 2))
if self._Contains(theta, phi):
return point.SPoint(theta, phi)
theta = (theta + 2* PI) % (2 * PI) - PI
phi = -phi
if self._Contains(theta, phi):
return spoint.SPoint(theta, phi)
return None
def _Contains(self, theta, phi):
contains_theta = False
contains_phi = False
if self.theta_wraps:
contains_theta = theta > self.theta_2 or theta < self.theta_1
else:
contains_theta = theta > self.theta_1 and theta < self.theta_2
phi_wrap_param = self._PhiWrapParam()
if phi_wrap_param <= 1.0 and phi_wrap_param >= 0.0:
extreme_phi = self.GetParametrizedCoords(phi_wrap_param).phi
if extreme_phi < self.phi_1:
contains_phi = (phi < max(self.phi_1, self.phi_2) and
phi > extreme_phi)
else:
contains_phi = (phi > min(self.phi_1, self.phi_2) and
phi < extreme_phi)
else:
contains_phi = (phi > min(self.phi_1, self.phi_2) and
phi < max(self.phi_1, self.phi_2))
return contains_phi and contains_theta
def _PhiWrapParam(self):
a = math.sin(self.d)
b = math.cos(self.d)
c = math.sin(self.phi_2) / math.sin(self.phi_1)
param = math.atan2(c - b, a) / self.d
return param
def _DealWithZeroZ(self, A, B, C, D, E, F, boundary):
if (A - D) is 0:
y = 0
x = 1
elif (E - B) is 0:
y = 1
x = 0
else:
y = 1 / math.sqrt(((E - B) / (A - D)) ** 2 + 1)
x = ((E - B) / (A - D)) * y
theta = (math.atan2(y, x) + PI) % (2 * PI) - PI
return point.SPoint(theta, 0)
def GetParametrizedCoords(self, param_value):
A = math.sin((1 - param_value) * self.d) / math.sin(self.d)
B = math.sin(param_value * self.d) / math.sin(self.d)
x = A * math.cos(self.phi_1) * math.cos(self.theta_1) + (
B * math.cos(self.phi_2) * math.cos(self.theta_2))
y = A * math.cos(self.phi_1) * math.sin(self.theta_1) + (
B * math.cos(self.phi_2) * math.sin(self.theta_2))
z = A * math.sin(self.phi_1) + B * math.sin(self.phi_2)
new_phi = math.atan2(z, math.sqrt(x**2 + y**2))
new_theta = math.atan2(y, x)
return point.SPoint(new_theta, new_phi)
EDIT: I forgot to specify that if two curves are determined to intersect, I then need to have the point of intersection.
A simpler approach is to express the problem in terms of geometric primitive operations like the dot product, the cross product, and the triple product. The sign of the determinant of u, v, and w tells you which side of the plane spanned by v and w contains u. This enables us to detect when two points are on opposite sites of a plane. That's equivalent to testing whether a great circle segment crosses another great circle. Performing this test twice tells us whether two great circle segments cross each other.
The implementation requires no trigonometric functions, no division, no comparisons with pi, and no special behavior around the poles!
class Vector:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def dot(v1, v2):
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
def cross(v1, v2):
return Vector(v1.y * v2.z - v1.z * v2.y,
v1.z * v2.x - v1.x * v2.z,
v1.x * v2.y - v1.y * v2.x)
def det(v1, v2, v3):
return dot(v1, cross(v2, v3))
class Pair:
def __init__(self, v1, v2):
self.v1 = v1
self.v2 = v2
# Returns True if the great circle segment determined by s
# straddles the great circle determined by l
def straddles(s, l):
return det(s.v1, l.v1, l.v2) * det(s.v2, l.v1, l.v2) < 0
# Returns True if the great circle segments determined by a and b
# cross each other
def intersects(a, b):
return straddles(a, b) and straddles(b, a)
# Test. Note that we don't need to normalize the vectors.
print(intersects(Pair(Vector(1, 0, 1), Vector(-1, 0, 1)),
Pair(Vector(0, 1, 1), Vector(0, -1, 1))))
If you want to initialize unit vectors in terms of angles theta and phi, you can do that, but I recommend immediately converting to Cartesian (x, y, z) coordinates to perform all subsequent calculations.
Intersection using plane trig can be calculated using the below code in UBasic.
5 'interx.ub adapted from code at
6 'https://rosettacode.org
7 '/wiki/Find_the_intersection_of_two_linesSinclair_ZX81_BASIC
8 'In U Basic by yuji kida https://en.wikipedia.org/wiki/UBASIC
10 XA=48.7815144526:'669595.708
20 YA=-117.2847245001:'2495736.332
30 XB=48.7815093807:'669533.412
40 YB=-117.2901673467:'2494425.458
50 XC=48.7824947147:'669595.708
60 YC=-117.28751374:'2495736.332
70 XD=48.77996737:'669331.214
80 YD=-117.2922957:'2494260.804
90 print "THE TWO LINES ARE:"
100 print "YAB=";YA-XA*((YB-YA)/(XB-XA));"+X*";((YB-YA)/(XB-XA))
110 print "YCD=";YC-XC*((YD-YC)/(XD-XC));"+X*";((YD-YC)/(XD-XC))
120 X=((YC-XC*((YD-YC)/(XD-XC)))-(YA-XA*((YB-YA)/(XB-XA))))/(((YB-YA)/(XB-XA))-((YD-YC)/(XD-XC)))
130 print "Lat = ";X
140 Y=YA-XA*((YB-YA)/(XB-XA))+X*((YB-YA)/(XB-XA))
150 print "Lon = ";Y
160 'print "YCD=";YC-XC*((YD-YC)/(XD-XC))+X*((YD-YC)/(XD-XC))

Area and perimeter of a figure made out of overlapping circles

I plan on writing this code in python, but this is more of a general algorithm-design problem than one having to do with any particular language. I'm writing code to simulate a hybrid rocket engine, and long story short, the problem involves finding both the perimeter and the area of a figure composed of many (possibly thousands) of overlapping circles.
I saw this on stackexchange:
Combined area of overlapping circles
except I need to find the perimeter also. Someone in that thread mentioned Monte Carlo (random point guess) methods, but can that be used to find the perimeter as well as the area?
Thanks in advance.
I am adding a second answer instead of expanding the first one, because I am quite positive that the other answer is correct, but I am not so sure if this answer is as correct. I have done some simple testing, and it seems to work, but please point out my errors.
This is basically a quick-n-dirty implementation of what I stated before:
from math import atan2, pi
def intersectLine (a, b):
s0, e0, s1, e1 = a [0], a [1], b [0], b [1]
s = max (s0, s1)
e = min (e0, e1)
if e <= s: return (0, 0)
return (s, e)
class SectorSet:
def __init__ (self, sectors):
self.sectors = sectors [:]
def __repr__ (self):
return repr (self.sectors)
def __iadd__ (self, other):
acc = []
for mine in self.sectors:
for others in other.sectors:
acc.append (intersectLine (mine, others) )
self.sectors = [x for x in acc if x [0] != x [1] ]
return self
class Circle:
CONTAINS = 0
CONTAINED = 1
DISJOINT = 2
INTERSECT = 3
def __init__ (self, x, y, r):
self.x = float (x)
self.y = float (y)
self.r = float (r)
def intersect (self, other):
a, b, c, d, r0, r1 = self.x, self.y, other.x, other.y, self.r, other.r
r0sq, r1sq = r0 ** 2, r1 ** 2
Dsq = (c - a) ** 2 + (d - b) ** 2
D = Dsq ** .5
if D >= r0 + r1:
return Circle.DISJOINT, None, None
if D <= abs (r0 - r1):
return Circle.CONTAINED if r0 < r1 else Circle.CONTAINS, None, None
dd = .25 * ( (D + r0 + r1) * (D + r0 - r1) * (D - r0 + r1) * (-D + r0 + r1) ) ** .5
x = (a + c) / 2. + (c - a) * (r0sq - r1sq) / 2. / Dsq
x1 = x + 2 * (b - d) / Dsq * dd
x2 = x - 2 * (b - d) / Dsq * dd
y = (b + d) / 2. + (d - b) * (r0sq - r1sq) / 2. / Dsq
y1 = y - 2 * (a - c) / Dsq * dd
y2 = y + 2 * (a - c) / Dsq * dd
return Circle.INTERSECT, (x1, y1), (x2, y2)
def angle (self, point):
x0, y0, x, y = self.x, self.y, point [0], point [1]
a = atan2 (y - y0, x - x0) + 1.5 * pi
if a >= 2 * pi: a -= 2 * pi
return a / pi * 180
def sector (self, other):
typ, i1, i2 = self.intersect (other)
if typ == Circle.DISJOINT: return SectorSet ( [ (0, 360) ] )
if typ == Circle.CONTAINS: return SectorSet ( [ (0, 360) ] )
if typ == Circle.CONTAINED: return SectorSet ( [] )
a1 = self.angle (i1)
a2 = self.angle (i2)
if a1 > a2: return SectorSet ( [ (0, a2), (a1, 360) ] )
return SectorSet ( [ (a1, a2) ] )
def outline (self, others):
sectors = SectorSet ( [ (0, 360) ] )
for other in others:
sectors += self.sector (other)
u = 2 * self.r * pi
total = 0
for start, end in sectors.sectors:
total += (end - start) / 360. * u
return total
def outline (circles):
total = 0
for circle in circles:
others = [other for other in circles if circle != other]
total += circle.outline (others)
return total
a = Circle (0, 0, 2)
b = Circle (-2, -1, 1)
c = Circle (2, -1, 1)
d = Circle (0, 2, 1)
print (outline ( [a, b, c, d] ) )
Formula for calculating the intersecting points of two circles shamelessly stolen from here.
Let's say your have the circles c1, c2, ..., cn.
Take c1 and intersect it with each other circle. Initialize an empty list of circle sectors for c1.
If the intersection is zero points or one point and c1 is outside the other circle, then add a 360 degree sector to the list.
If the intersection is zero points or one point and c1 is inside the other circle, then the effective outline of c1 is 0, stop processing c1 with an outline of 0 and take the next circle.
If the intersection is two points, add the sector of c1, which is not in the other circle to the list of circle sectors of c1.
The effective outline of c1, is the length of the intersection of all sectors within the list of sectors of c1. This intersection of sectors can consist of various disjoint sectors.
Rinse and repeat for all circles and then sum up the resulting values.

How can you determine a point is between two other points on a line segment?

Let's say you have a two dimensional plane with 2 points (called a and b) on it represented by an x integer and a y integer for each point.
How can you determine if another point c is on the line segment defined by a and b?
I use python most, but examples in any language would be helpful.
Check if the cross product of (b-a) and (c-a) is 0, as tells Darius Bacon, tells you if the points a, b and c are aligned.
But, as you want to know if c is between a and b, you also have to check that the dot product of (b-a) and (c-a) is positive and is less than the square of the distance between a and b.
In non-optimized pseudocode:
def isBetween(a, b, c):
crossproduct = (c.y - a.y) * (b.x - a.x) - (c.x - a.x) * (b.y - a.y)
# compare versus epsilon for floating point values, or != 0 if using integers
if abs(crossproduct) > epsilon:
return False
dotproduct = (c.x - a.x) * (b.x - a.x) + (c.y - a.y)*(b.y - a.y)
if dotproduct < 0:
return False
squaredlengthba = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
if dotproduct > squaredlengthba:
return False
return True
Here's how I'd do it:
def distance(a,b):
return sqrt((a.x - b.x)**2 + (a.y - b.y)**2)
def is_between(a,c,b):
return distance(a,c) + distance(c,b) == distance(a,b)
Check if the cross product of b-a and c-a is0: that means all the points are collinear. If they are, check if c's coordinates are between a's and b's. Use either the x or the y coordinates, as long as a and b are separate on that axis (or they're the same on both).
def is_on(a, b, c):
"Return true iff point c intersects the line segment from a to b."
# (or the degenerate case that all 3 points are coincident)
return (collinear(a, b, c)
and (within(a.x, c.x, b.x) if a.x != b.x else
within(a.y, c.y, b.y)))
def collinear(a, b, c):
"Return true iff a, b, and c all lie on the same line."
return (b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y)
def within(p, q, r):
"Return true iff q is between p and r (inclusive)."
return p <= q <= r or r <= q <= p
This answer used to be a mess of three updates. The worthwhile info from them: Brian Hayes's chapter in Beautiful Code covers the design space for a collinearity-test function -- useful background. Vincent's answer helped to improve this one. And it was Hayes who suggested testing only one of the x or the y coordinates; originally the code had and in place of if a.x != b.x else.
(This is coded for exact arithmetic with integers or rationals; if you pass in floating-point numbers instead, there will be problems with round-off errors. I'm not even sure what's a good way to define betweenness of 2-d points in float coordinates.)
Here's another approach:
Lets assume the two points be A (x1,y1) and B (x2,y2)
The equation of the line passing through those points is (x-x1)/(y-y1)=(x2-x1)/(y2-y1) .. (just making equating the slopes)
Point C (x3,y3) will lie between A & B if:
x3,y3 satisfies the above equation.
x3 lies between x1 & x2 and y3 lies between y1 & y2 (trivial check)
The length of the segment is not important, thus using a square root is not required and should be avoided since we could lose some precision.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Segment:
def __init__(self, a, b):
self.a = a
self.b = b
def is_between(self, c):
# Check if slope of a to c is the same as a to b ;
# that is, when moving from a.x to c.x, c.y must be proportionally
# increased than it takes to get from a.x to b.x .
# Then, c.x must be between a.x and b.x, and c.y must be between a.y and b.y.
# => c is after a and before b, or the opposite
# that is, the absolute value of cmp(a, b) + cmp(b, c) is either 0 ( 1 + -1 )
# or 1 ( c == a or c == b)
a, b = self.a, self.b
return ((b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y) and
abs(cmp(a.x, c.x) + cmp(b.x, c.x)) <= 1 and
abs(cmp(a.y, c.y) + cmp(b.y, c.y)) <= 1)
Some random example of usage :
a = Point(0,0)
b = Point(50,100)
c = Point(25,50)
d = Point(0,8)
print Segment(a,b).is_between(c)
print Segment(a,b).is_between(d)
You can use the wedge and dot product:
def dot(v,w): return v.x*w.x + v.y*w.y
def wedge(v,w): return v.x*w.y - v.y*w.x
def is_between(a,b,c):
v = a - b
w = b - c
return wedge(v,w) == 0 and dot(v,w) > 0
Using a more geometric approach, calculate the following distances:
ab = sqrt((a.x-b.x)**2 + (a.y-b.y)**2)
ac = sqrt((a.x-c.x)**2 + (a.y-c.y)**2)
bc = sqrt((b.x-c.x)**2 + (b.y-c.y)**2)
and test whether ac+bc equals ab:
is_on_segment = abs(ac + bc - ab) < EPSILON
That's because there are three possibilities:
The 3 points form a triangle => ac+bc > ab
They are collinear and c is outside the ab segment => ac+bc > ab
They are collinear and c is inside the ab segment => ac+bc = ab
Here's a different way to go about it, with code given in C++. Given two points, l1 and l2 it's trivial to express the line segment between them as
l1 + A(l2 - l1)
where 0 <= A <= 1. This is known as the vector representation of a line if you're interested any more beyond just using it for this problem. We can split out the x and y components of this, giving:
x = l1.x + A(l2.x - l1.x)
y = l1.y + A(l2.y - l1.y)
Take a point (x, y) and substitute its x and y components into these two expressions to solve for A. The point is on the line if the solutions for A in both expressions are equal and 0 <= A <= 1. Because solving for A requires division, there's special cases that need handling to stop division by zero when the line segment is horizontal or vertical. The final solution is as follows:
// Vec2 is a simple x/y struct - it could very well be named Point for this use
bool isBetween(double a, double b, double c) {
// return if c is between a and b
double larger = (a >= b) ? a : b;
double smaller = (a != larger) ? a : b;
return c <= larger && c >= smaller;
}
bool pointOnLine(Vec2<double> p, Vec2<double> l1, Vec2<double> l2) {
if(l2.x - l1.x == 0) return isBetween(l1.y, l2.y, p.y); // vertical line
if(l2.y - l1.y == 0) return isBetween(l1.x, l2.x, p.x); // horizontal line
double Ax = (p.x - l1.x) / (l2.x - l1.x);
double Ay = (p.y - l1.y) / (l2.y - l1.y);
// We want Ax == Ay, so check if the difference is very small (floating
// point comparison is fun!)
return fabs(Ax - Ay) < 0.000001 && Ax >= 0.0 && Ax <= 1.0;
}
The scalar product between (c-a) and (b-a) must be equal to the product of their lengths (this means that the vectors (c-a) and (b-a) are aligned and with the same direction). Moreover, the length of (c-a) must be less than or equal to that of (b-a). Pseudocode:
# epsilon = small constant
def isBetween(a, b, c):
lengthca2 = (c.x - a.x)*(c.x - a.x) + (c.y - a.y)*(c.y - a.y)
lengthba2 = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
if lengthca2 > lengthba2: return False
dotproduct = (c.x - a.x)*(b.x - a.x) + (c.y - a.y)*(b.y - a.y)
if dotproduct < 0.0: return False
if abs(dotproduct*dotproduct - lengthca2*lengthba2) > epsilon: return False
return True
I needed this for javascript for use in an html5 canvas for detecting if the users cursor was over or near a certain line. So I modified the answer given by Darius Bacon into coffeescript:
is_on = (a,b,c) ->
# "Return true if point c intersects the line segment from a to b."
# (or the degenerate case that all 3 points are coincident)
return (collinear(a,b,c) and withincheck(a,b,c))
withincheck = (a,b,c) ->
if a[0] != b[0]
within(a[0],c[0],b[0])
else
within(a[1],c[1],b[1])
collinear = (a,b,c) ->
# "Return true if a, b, and c all lie on the same line."
((b[0]-a[0])*(c[1]-a[1]) < (c[0]-a[0])*(b[1]-a[1]) + 1000) and ((b[0]-a[0])*(c[1]-a[1]) > (c[0]-a[0])*(b[1]-a[1]) - 1000)
within = (p,q,r) ->
# "Return true if q is between p and r (inclusive)."
p <= q <= r or r <= q <= p
Here's how I did it at school. I forgot why it is not a good idea.
EDIT:
#Darius Bacon: cites a "Beautiful Code" book which contains an explanation why the belowed code is not a good idea.
#!/usr/bin/env python
from __future__ import division
epsilon = 1e-6
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
class LineSegment:
"""
>>> ls = LineSegment(Point(0,0), Point(2,4))
>>> Point(1, 2) in ls
True
>>> Point(.5, 1) in ls
True
>>> Point(.5, 1.1) in ls
False
>>> Point(-1, -2) in ls
False
>>> Point(.1, 0.20000001) in ls
True
>>> Point(.1, 0.2001) in ls
False
>>> ls = LineSegment(Point(1, 1), Point(3, 5))
>>> Point(2, 3) in ls
True
>>> Point(1.5, 2) in ls
True
>>> Point(0, -1) in ls
False
>>> ls = LineSegment(Point(1, 2), Point(1, 10))
>>> Point(1, 6) in ls
True
>>> Point(1, 1) in ls
False
>>> Point(2, 6) in ls
False
>>> ls = LineSegment(Point(-1, 10), Point(5, 10))
>>> Point(3, 10) in ls
True
>>> Point(6, 10) in ls
False
>>> Point(5, 10) in ls
True
>>> Point(3, 11) in ls
False
"""
def __init__(self, a, b):
if a.x > b.x:
a, b = b, a
(self.x0, self.y0, self.x1, self.y1) = (a.x, a.y, b.x, b.y)
self.slope = (self.y1 - self.y0) / (self.x1 - self.x0) if self.x1 != self.x0 else None
def __contains__(self, c):
return (self.x0 <= c.x <= self.x1 and
min(self.y0, self.y1) <= c.y <= max(self.y0, self.y1) and
(not self.slope or -epsilon < (c.y - self.y(c.x)) < epsilon))
def y(self, x):
return self.slope * (x - self.x0) + self.y0
if __name__ == '__main__':
import doctest
doctest.testmod()
Ok, lots of mentions of linear algebra (cross product of vectors) and this works in a real (ie continuous or floating point) space but the question specifically stated that the two points were expressed as integers and thus a cross product is not the correct solution although it can give an approximate solution.
The correct solution is to use Bresenham's Line Algorithm between the two points and to see if the third point is one of the points on the line. If the points are sufficiently distant that calculating the algorithm is non-performant (and it'd have to be really large for that to be the case) I'm sure you could dig around and find optimisations.
Any point on the line segment (a, b) (where a and b are vectors) can be expressed as a linear combination of the two vectors a and b:
In other words, if c lies on the line segment (a, b):
c = ma + (1 - m)b, where 0 <= m <= 1
Solving for m, we get:
m = (c.x - b.x)/(a.x - b.x) = (c.y - b.y)/(a.y - b.y)
So, our test becomes (in Python):
def is_on(a, b, c):
"""Is c on the line segment ab?"""
def _is_zero( val ):
return -epsilon < val < epsilon
x1 = a.x - b.x
x2 = c.x - b.x
y1 = a.y - b.y
y2 = c.y - b.y
if _is_zero(x1) and _is_zero(y1):
# a and b are the same point:
# so check that c is the same as a and b
return _is_zero(x2) and _is_zero(y2)
if _is_zero(x1):
# a and b are on same vertical line
m2 = y2 * 1.0 / y1
return _is_zero(x2) and 0 <= m2 <= 1
elif _is_zero(y1):
# a and b are on same horizontal line
m1 = x2 * 1.0 / x1
return _is_zero(y2) and 0 <= m1 <= 1
else:
m1 = x2 * 1.0 / x1
if m1 < 0 or m1 > 1:
return False
m2 = y2 * 1.0 / y1
return _is_zero(m2 - m1)
c#
From http://www.faqs.org/faqs/graphics/algorithms-faq/
-> Subject 1.02: How do I find the distance from a point to a line?
Boolean Contains(PointF from, PointF to, PointF pt, double epsilon)
{
double segmentLengthSqr = (to.X - from.X) * (to.X - from.X) + (to.Y - from.Y) * (to.Y - from.Y);
double r = ((pt.X - from.X) * (to.X - from.X) + (pt.Y - from.Y) * (to.Y - from.Y)) / segmentLengthSqr;
if(r<0 || r>1) return false;
double sl = ((from.Y - pt.Y) * (to.X - from.X) - (from.X - pt.X) * (to.Y - from.Y)) / System.Math.Sqrt(segmentLengthSqr);
return -epsilon <= sl && sl <= epsilon;
}
An answer in C# using a Vector2D class
public static bool IsOnSegment(this Segment2D #this, Point2D c, double tolerance)
{
var distanceSquared = tolerance*tolerance;
// Start of segment to test point vector
var v = new Vector2D( #this.P0, c ).To3D();
// Segment vector
var s = new Vector2D( #this.P0, #this.P1 ).To3D();
// Dot product of s
var ss = s*s;
// k is the scalar we multiply s by to get the projection of c onto s
// where we assume s is an infinte line
var k = v*s/ss;
// Convert our tolerance to the units of the scalar quanity k
var kd = tolerance / Math.Sqrt( ss );
// Check that the projection is within the bounds
if (k <= -kd || k >= (1+kd))
{
return false;
}
// Find the projection point
var p = k*s;
// Find the vector between test point and it's projection
var vp = (v - p);
// Check the distance is within tolerance.
return vp * vp < distanceSquared;
}
Note that
s * s
is the dot product of the segment vector via operator overloading in C#
The key is taking advantage of the projection of the point onto the infinite line and observing that the scalar quantity of the projection tells us trivially if the projection is on the segment or not. We can adjust the bounds of the scalar quantity to use a fuzzy tolerance.
If the projection is within bounds we just test if the distance from the point to the projection is within bounds.
The benefit over the cross product approach is that the tolerance has a meaningful value.
C# version of Jules' answer:
public static double CalcDistanceBetween2Points(double x1, double y1, double x2, double y2)
{
return Math.Sqrt(Math.Pow (x1-x2, 2) + Math.Pow (y1-y2, 2));
}
public static bool PointLinesOnLine (double x, double y, double x1, double y1, double x2, double y2, double allowedDistanceDifference)
{
double dist1 = CalcDistanceBetween2Points(x, y, x1, y1);
double dist2 = CalcDistanceBetween2Points(x, y, x2, y2);
double dist3 = CalcDistanceBetween2Points(x1, y1, x2, y2);
return Math.Abs(dist3 - (dist1 + dist2)) <= allowedDistanceDifference;
}
Here is some Java code that worked for me:
boolean liesOnSegment(Coordinate a, Coordinate b, Coordinate c) {
double dotProduct = (c.x - a.x) * (c.x - b.x) + (c.y - a.y) * (c.y - b.y);
return (dotProduct < 0);
}
You could also use the very convenient scikit-spatial library.
For instance, you could create a Line object defined by the two points a and b:
>>> point_a = [0, 0]
>>> point_b = [1, 0]
>>> line = Line.from_points(point_a, point_b)
then you can use the side_point method of the Line class to check whether point c lies on line or not.
>>> line.side_point([0.5, 0])
0
If the output is 0, then point c lies on line.
how about just ensuring that the slope is the same and the point is between the others?
given points (x1, y1) and (x2, y2) ( with x2 > x1)
and candidate point (a,b)
if (b-y1) / (a-x1) = (y2-y2) / (x2-x1) And x1 < a < x2
Then (a,b) must be on line between (x1,y1) and (x2, y2)
Here is my solution with C# in Unity.
private bool _isPointOnLine( Vector2 ptLineStart, Vector2 ptLineEnd, Vector2 ptPoint )
{
bool bRes = false;
if((Mathf.Approximately(ptPoint.x, ptLineStart.x) || Mathf.Approximately(ptPoint.x, ptLineEnd.x)))
{
if(ptPoint.y > ptLineStart.y && ptPoint.y < ptLineEnd.y)
{
bRes = true;
}
}
else if((Mathf.Approximately(ptPoint.y, ptLineStart.y) || Mathf.Approximately(ptPoint.y, ptLineEnd.y)))
{
if(ptPoint.x > ptLineStart.x && ptPoint.x < ptLineEnd.x)
{
bRes = true;
}
}
return bRes;
}
You can do it by solving the line equation for that line segment with the point coordinates you will know whether that point is on the line and then checking the bounds of the segment to know whether it is inside or outside of it. You can apply some threshold because well it is somewhere in space mostl likely defined by a floating point value and you must not hit the exact one.
Example in php
function getLineDefinition($p1=array(0,0), $p2=array(0,0)){
$k = ($p1[1]-$p2[1])/($p1[0]-$p2[0]);
$q = $p1[1]-$k*$p1[0];
return array($k, $q);
}
function isPointOnLineSegment($line=array(array(0,0),array(0,0)), $pt=array(0,0)){
// GET THE LINE DEFINITION y = k.x + q AS array(k, q)
$def = getLineDefinition($line[0], $line[1]);
// use the line definition to find y for the x of your point
$y = $def[0]*$pt[0]+$def[1];
$yMin = min($line[0][1], $line[1][1]);
$yMax = max($line[0][1], $line[1][1]);
// exclude y values that are outside this segments bounds
if($y>$yMax || $y<$yMin) return false;
// calculate the difference of your points y value from the reference value calculated from lines definition
// in ideal cases this would equal 0 but we are dealing with floating point values so we need some threshold value not to lose results
// this is up to you to fine tune
$diff = abs($pt[1]-$y);
$thr = 0.000001;
return $diff<=$thr;
}

Categories

Resources