Finding intersection between ellipse and a line - python

I am trying to find intersection points between an elipse and a line, but seem to be unable to do so. I have tried two approaches, one with shapely by trying to find intersection between LineString and LinearRing as in the code bellow, but did not get any usable values out of it. One of the problems, is the elipse will always be off center and at an small or high angle
# -*- coding: utf-8 -*-
"""
Created on Mon Aug 19 17:38:55 2013
#author: adudchenko
"""
from pylab import *
import numpy as np
from shapely.geometry.polygon import LinearRing
from shapely.geometry import LineString
def ellipse_polyline(ellipses, n=100):
t = np.linspace(0, 2*np.pi, n, endpoint=False)
st = np.sin(t)
ct = np.cos(t)
result = []
for x0, y0, a, b, angle in ellipses:
angle = np.deg2rad(angle)
sa = np.sin(angle)
ca = np.cos(angle)
p = np.empty((n, 2))
p[:, 0] = x0 + a * ca * ct - b * sa * st
p[:, 1] = y0 + a * sa * ct + b * ca * st
result.append(p)
return result
def intersections(a, line):
ea = LinearRing(a)
eb = LinearRing(b)
mp = ea.intersection(eb)
print mp
x = [p.x for p in mp]
y = [p.y for p in mp]
return x, y
ellipses = [(1, 1, 2, 1, 45), (2, 0.5, 5, 1.5, -30)]
a, b = ellipse_polyline(ellipses)
line=LineString([[0,0],[4,4]])
x, y = intersections(a, line)
figure()
plot(x, y, "o")
plot(a[:,0], a[:,1])
plot(b[:,0], b[:,1])
show()
I also tried using fsolve as in example bellow, but it finds the wrong intersect points ( or actually one wrong point.
from pylab import *
from scipy.optimize import fsolve
import numpy as np
def ellipse_polyline(ellipses, n=100):
t = np.linspace(0, 2*np.pi, n, endpoint=False)
st = np.sin(t)
ct = np.cos(t)
result = []
for x0, y0, a, b, angle in ellipses:
angle = np.deg2rad(angle)
sa = np.sin(angle)
ca = np.cos(angle)
p = np.empty((n, 2))
p[:, 0] = x0 + a * ca * np.cos(t) - b * sa * np.sin(t)
p[:, 1] = y0 + a * sa * np.cos(t) + b * ca * np.sin(t)
result.append(p)
return result
def ellipse_line(txy):
t,x,y=txy
x0, y0, a, b, angle,m,lb=1, 1, 2, 1, 45,0.5,0
sa = np.sin(angle)
ca = np.cos(angle)
return (x0 + a * ca * np.cos(t) - b * sa * np.sin(t)-x,y0 + a * sa * np.cos(t) + b * ca * np.sin(t)-y,m*x+lb-y)
a,b= ellipse_polyline([(1, 1, 2, 1, 45), (2, 0.5, 5, 1.5, -30)])
t,y,x=fsolve(ellipse_line,(0,0,0))
print t,y,x
#print a[:,0]
m=0.5
bl=0
xl,yl=[],[]
for i in range(10):
xl.append(i)
yl.append(m*i+bl)
figure()
plot(x, y, "o")
plot(a[:,0], a[:,1])
plot(xl,yl)
Any help would be appriceated?

With the Shapely part, def intersections(a, line) can be fixed. First, there is an error, where it references a global b instead of using the line parameter, which is ignored. Thus the comparison is between the two ellipses a and b, and not between each ellipse to line, as I think is intended.
Also, the intersection between two lines can be one of several outcomes: an empty set, a point (1 intersection) a multipoint (more than 1 intersection), a linestring (if any part(s) of the linestrings overlap and are parallel to each other), or a collection of points and lines. Assuming only the first three outcomes:
def intersections(a, line):
ea = LinearRing(a)
mp = ea.intersection(line)
if mp.is_empty:
print('Geometries do not intersect')
return [], []
elif mp.geom_type == 'Point':
return [mp.x], [mp.y]
elif mp.geom_type == 'MultiPoint':
return [p.x for p in mp], [p.y for p in mp]
else:
raise ValueError('something unexpected: ' + mp.geom_type)
So now these look correct:
>>> intersections(a, line)
([2.414213562373095], [2.414213562373095])
>>> intersections(b, line)
([0.0006681263405436677, 2.135895843256409], [0.0006681263405436642, 2.135895843256409])

Related

Deriving Cubic Bezier Curve control points & handles from series of points in Python

I am trying to find the control points and handles of a Cubic Bezier curve from a series of points. My current code is below (credit to Zero Zero on the Python Discord). The Cubic Spline is creating the desired fit, but the handles (in orange) are incorrect. How may I find the handles of this curve?
Thank you!
import numpy as np
import scipy as sp
def fit_curve(points):
# Fit a cubic bezier curve to the points
curve = sp.interpolate.CubicSpline(points[:, 0], points[:, 1], bc_type=((1, 0.0), (1, 0.0)))
# Get 4 control points for the curve
p = np.zeros((4, 2))
p[0, :] = points[0, :]
p[3, :] = points[-1, :]
p[1, :] = points[0, :] + 0.3 * (points[-1, :] - points[0, :])
p[2, :] = points[-1, :] - 0.3 * (points[-1, :] - points[0, :])
return p, curve
ypoints = [0.0, 0.03771681353260319, 0.20421680080883106, 0.49896111463402026, 0.7183501026981503, 0.8481517096346528, 0.9256128196832564, 0.9705404287079152, 0.9933297674379904, 1.0]
xpoints = [x for x in range(len(ypoints))]
points = np.array([xpoints, ypoints]).T
from scipy.interpolate import splprep, splev
tck, u = splprep([xpoints, ypoints], s=0)
#print(tck, u)
xnew, ynew = splev(np.linspace(0, 1, 100), tck)
# Plot the original points and the Bézier curve
import matplotlib.pyplot as plt
#plt.plot(xpoints, ypoints, 'x', xnew, ynew, xpoints, ypoints, 'b')
plt.axis([0, 10, -0.05, 1.05])
plt.legend(['Points', 'Bézier curve', 'True curve'])
plt.title('Bézier curve fitting')
# Get the curve
p, curve = fit_curve(points)
# Plot the points and the curve
plt.plot(points[:, 0], points[:, 1], 'o')
plt.plot(p[:, 0], p[:, 1], 'o')
plt.plot(np.linspace(0, 9, 100), curve(np.linspace(0, 9, 100)))
plt.show()
The answer for my case was a Bezier best fit function that accepts an input of point values, fits the points to a Cubic Spline, and outputs the Bézier handles of the curve by finding their coefficients.
Here is one such script, fitCurves, which can be used like so:
import numpy as np
from fitCurve import fitCurve
import matplotlib.pyplot as plt
y = [0.0,
0.03771681353260319,
0.20421680080883106,
0.49896111463402026,
0.7183501026981503,
0.8481517096346528,
0.9256128196832564,
0.9705404287079152,
0.9933297674379904,
1.0]
x = np.linspace(0, 1, len(y))
pts = np.array([x,y]).T
bezier_handles = fitCurve(points=pts , maxError=20)
x_bez = []
y_bez = []
for bez in bezier_handles:
for pt in bez:
x_bez.append(pt[0])
y_bez.append(pt[1])
plt.plot(pts[:,0], pts[:,1], 'bo-', label='Points')
plt.plot(x_bez[:2], y_bez[:2], 'ro--', label='Handle') # handle 1
plt.plot(x_bez[2:4], y_bez[2:4], 'ro--') # handle 2
plt.legend()
plt.show()
fitCurve.py
from numpy import *
""" Python implementation of
Algorithm for Automatically Fitting Digitized Curves
by Philip J. Schneider
"Graphics Gems", Academic Press, 1990
"""
# evaluates cubic bezier at t, return point
def q(ctrlPoly, t):
return (1.0-t)**3 * ctrlPoly[0] + 3*(1.0-t)**2 * t * ctrlPoly[1] + 3*(1.0-t)* t**2 * ctrlPoly[2] + t**3 * ctrlPoly[3]
# evaluates cubic bezier first derivative at t, return point
def qprime(ctrlPoly, t):
return 3*(1.0-t)**2 * (ctrlPoly[1]-ctrlPoly[0]) + 6*(1.0-t) * t * (ctrlPoly[2]-ctrlPoly[1]) + 3*t**2 * (ctrlPoly[3]-ctrlPoly[2])
# evaluates cubic bezier second derivative at t, return point
def qprimeprime(ctrlPoly, t):
return 6*(1.0-t) * (ctrlPoly[2]-2*ctrlPoly[1]+ctrlPoly[0]) + 6*(t) * (ctrlPoly[3]-2*ctrlPoly[2]+ctrlPoly[1])
# Fit one (ore more) Bezier curves to a set of points
def fitCurve(points, maxError):
leftTangent = normalize(points[1] - points[0])
rightTangent = normalize(points[-2] - points[-1])
return fitCubic(points, leftTangent, rightTangent, maxError)
def fitCubic(points, leftTangent, rightTangent, error):
# Use heuristic if region only has two points in it
if (len(points) == 2):
dist = linalg.norm(points[0] - points[1]) / 3.0
bezCurve = [points[0], points[0] + leftTangent * dist, points[1] + rightTangent * dist, points[1]]
return [bezCurve]
# Parameterize points, and attempt to fit curve
u = chordLengthParameterize(points)
bezCurve = generateBezier(points, u, leftTangent, rightTangent)
# Find max deviation of points to fitted curve
maxError, splitPoint = computeMaxError(points, bezCurve, u)
if maxError < error:
return [bezCurve]
# If error not too large, try some reparameterization and iteration
if maxError < error**2:
for i in range(20):
uPrime = reparameterize(bezCurve, points, u)
bezCurve = generateBezier(points, uPrime, leftTangent, rightTangent)
maxError, splitPoint = computeMaxError(points, bezCurve, uPrime)
if maxError < error:
return [bezCurve]
u = uPrime
# Fitting failed -- split at max error point and fit recursively
beziers = []
centerTangent = normalize(points[splitPoint-1] - points[splitPoint+1])
beziers += fitCubic(points[:splitPoint+1], leftTangent, centerTangent, error)
beziers += fitCubic(points[splitPoint:], -centerTangent, rightTangent, error)
return beziers
def generateBezier(points, parameters, leftTangent, rightTangent):
bezCurve = [points[0], None, None, points[-1]]
# compute the A's
A = zeros((len(parameters), 2, 2))
for i, u in enumerate(parameters):
A[i][0] = leftTangent * 3*(1-u)**2 * u
A[i][1] = rightTangent * 3*(1-u) * u**2
# Create the C and X matrices
C = zeros((2, 2))
X = zeros(2)
for i, (point, u) in enumerate(zip(points, parameters)):
C[0][0] += dot(A[i][0], A[i][0])
C[0][1] += dot(A[i][0], A[i][1])
C[1][0] += dot(A[i][0], A[i][1])
C[1][1] += dot(A[i][1], A[i][1])
tmp = point - q([points[0], points[0], points[-1], points[-1]], u)
X[0] += dot(A[i][0], tmp)
X[1] += dot(A[i][1], tmp)
# Compute the determinants of C and X
det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1]
det_C0_X = C[0][0] * X[1] - C[1][0] * X[0]
det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1]
# Finally, derive alpha values
alpha_l = 0.0 if det_C0_C1 == 0 else det_X_C1 / det_C0_C1
alpha_r = 0.0 if det_C0_C1 == 0 else det_C0_X / det_C0_C1
# If alpha negative, use the Wu/Barsky heuristic (see text) */
# (if alpha is 0, you get coincident control points that lead to
# divide by zero in any subsequent NewtonRaphsonRootFind() call. */
segLength = linalg.norm(points[0] - points[-1])
epsilon = 1.0e-6 * segLength
if alpha_l < epsilon or alpha_r < epsilon:
# fall back on standard (probably inaccurate) formula, and subdivide further if needed.
bezCurve[1] = bezCurve[0] + leftTangent * (segLength / 3.0)
bezCurve[2] = bezCurve[3] + rightTangent * (segLength / 3.0)
else:
# First and last control points of the Bezier curve are
# positioned exactly at the first and last data points
# Control points 1 and 2 are positioned an alpha distance out
# on the tangent vectors, left and right, respectively
bezCurve[1] = bezCurve[0] + leftTangent * alpha_l
bezCurve[2] = bezCurve[3] + rightTangent * alpha_r
return bezCurve
def reparameterize(bezier, points, parameters):
return [newtonRaphsonRootFind(bezier, point, u) for point, u in zip(points, parameters)]
def newtonRaphsonRootFind(bez, point, u):
"""
Newton's root finding algorithm calculates f(x)=0 by reiterating
x_n+1 = x_n - f(x_n)/f'(x_n)
We are trying to find curve parameter u for some point p that minimizes
the distance from that point to the curve. Distance point to curve is d=q(u)-p.
At minimum distance the point is perpendicular to the curve.
We are solving
f = q(u)-p * q'(u) = 0
with
f' = q'(u) * q'(u) + q(u)-p * q''(u)
gives
u_n+1 = u_n - |q(u_n)-p * q'(u_n)| / |q'(u_n)**2 + q(u_n)-p * q''(u_n)|
"""
d = q(bez, u)-point
numerator = (d * qprime(bez, u)).sum()
denominator = (qprime(bez, u)**2 + d * qprimeprime(bez, u)).sum()
if denominator == 0.0:
return u
else:
return u - numerator/denominator
def chordLengthParameterize(points):
u = [0.0]
for i in range(1, len(points)):
u.append(u[i-1] + linalg.norm(points[i] - points[i-1]))
for i, _ in enumerate(u):
u[i] = u[i] / u[-1]
return u
def computeMaxError(points, bez, parameters):
maxDist = 0.0
splitPoint = len(points)/2
for i, (point, u) in enumerate(zip(points, parameters)):
dist = linalg.norm(q(bez, u)-point)**2
if dist > maxDist:
maxDist = dist
splitPoint = i
return maxDist, splitPoint
def normalize(v):
return v / linalg.norm(v)

Projectile motion 3d odes

I am trying to solve this set of differential equations with solve_ivp in Python. Winds are horizontal two dimensional vectors, so I need differential equation solved in y dimension.
This is the error I get:
ValueError: not enough values to unpack (expected 6, got 4)
Is there any other way of solving it? Here is my code:(just projectile motion in x-z plane, distance as function of angle). For the example of two such initial angles (with which we hit approximately the same point) I am trying to calculate the dispersion of hits in the wind according to the point hit in no wind.
Thanks for any help or pointers
from random import randrange
from statistics import stdev
from turtle import title
import numpy as np
import scipy as sp
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
m = 30
v0 = 301
g = 9.81
ro = 1.28
A = 0.0026
c = 1
k = 0.5 * c * ro * A
B = k / m
V = v0
c_max = 10
c_windx = 0
c_windy = 0
def dSdt(t, S, B, c_windx, c_windy):
x, vx, y, vy, z, vz = S
speed = np.hypot(np.hypot((vx + c_windx),(vy + c_windy)), vz)
return [vx, -B * speed * (vx + c_windx), vy, -B * speed * (vy + c_windy), vz, -B * speed * vz - g]
def distance(angle, B, c_windx, V=v0, t=600):
v0x = V * np.cos(np.radians(angle))
v0y = 0
v0z = V * np.sin(np.radians(angle))
sol = solve_ivp(dSdt, [0, t], y0 =[0,v0x,0,v0y,0,v0z], t_eval=np.linspace(0,t,10000), args=(B, c_windx,c_windy), atol=1e-7, rtol=1e-4)
justabove = np.where(np.diff(np.sign(sol.y[2])) < 0)[0][0]
justunder = justabove + 1
xloc = (sol.y[0][justabove] + sol.y[0][justunder])/2
return xloc
angles = np.linspace(0, 90, 200)
xlocs = np.vectorize(distance)(angles, B=B, c_windx = c_windx)
plt.plot(angles, xlocs)
plt.xlabel('angles []')
plt.ylabel('distance [m]')
plt.axvline(angles[np.argmax(xlocs)], ls='--', color='r')
plt.text(angles[np.argmax(xlocs)] + 0.2 ,0, round(angles[np.argmax(xlocs)], 2), rotation=90, color='r')
plt.title('Distance(angle)')
plt.show()
alfa = 15 #its optional
def same_distance_alfa(B,c_windx, V=v0, t=600):
for a in np.arange(alfa + 5, 90, 0.1):
if abs(distance(alfa, B, c_windx, V=v0, t=600) - distance(a, B, c_windx, V=v0, t=600) < 10):
print((distance(15, B, c_windx, V=v0, t=600) - distance(a, B, c_windx, V=v0, t=600)))
return a
beta = same_distance_alfa(B)

Rotation of arbitrary plane around a given axis results in inconsistent results

I am trying to rotate and translate arbitrary planes around some arbitrary axis.
For testing purposes I have written a simple python program that rotates a random plane around the X axis in degrees.
Unfortunately when checking the angle between the planes I get inconsistent results. This is the code:
def angle_between_planes(plane1, plane2):
plane1 = (plane1 / np.linalg.norm(plane1))[:3]
plane2 = (plane2/ np.linalg.norm(plane2))[:3]
cos_a = np.dot(plane1.T, plane2) / (np.linalg.norm(plane1) * np.linalg.norm(plane2))
print(np.arccos(cos_a)[0, 0])
def test():
axis = np.array([1, 0, 0])
theta = np.pi / 2
translation = np.array([0, 0, 0])
T = get_transformation(translation, axis * theta)
for i in range(1, 10):
source = np.append(np.random.randint(1, 20, size=3), 0).reshape(4, 1)
target = np.dot(T, source)
angle_between_planes(source, target)
It prints:
1.21297144225
1.1614420953
1.48042948278
1.10098697889
0.992418096794
1.16954303911
1.04180591409
1.08015300394
1.51949177153
When debugging this code I see that the transformation matrix is correct, as it shows that it is
I'm not sure what's wrong and would love any assistance here.
*
The code that generates the transformation matrix is:
def get_transformation(translation_vec, rotation_vec):
r_4 = np.array([0, 0, 0, 1]).reshape(1, 4)
rotation_vec= rotation_vec.reshape(3, 1)
theta = np.linalg.norm(rotation_vec)
axis = rotation_vec/ theta
R = get_rotation_mat_from_axis_and_angle(axis, theta)
T = translation_vec.reshape(3, 1)
R_T = np.append(R, T, axis = 1)
return np.append(R_T, r_4, axis=0)
def get_rotation_mat_from_axis_and_angle(axis, theta):
axis = axis / np.linalg.norm(axis)
a, b, c = axis
omct = 1 - np.cos(theta)
ct = np.cos(theta)
st = np.sin(theta)
rotation_matrix = np.array([a * a * omct + ct, a * b * omct - c * st, a * c * omct + b * st,
a * b * omct + c * st, b * b * omct + ct, b * c * omct - a * st,
a * c * omct - b * st, b * c * omct + a * st, c * c * omct + ct]).reshape(3, 3)
rotation_matrix[abs(rotation_matrix) < 1e-8] = 0
return rotation_matrix
The source you generate is not a vector. In order to be one, it should have its fourth coordinate equal to zero.
You could generate valid ones with:
source = np.append(np.random.randint(1, 20, size=3), 0).reshape(4, 1)
Note that your code can't be tested as you pasted it in your question: for example, vec = vec.reshape(3, 1) in get_transformation uses vec that hasn't been defined anywhere before...

Generate random locations within a triangular domain

I want to generate x and y having a uniform distribution and limited by [xmin,xmax] and [ymin,ymax]
The points (x,y) should be inside a triangle.
How can I solve such a problem?
Here's some code that generates points uniformly on an arbitrary triangle in the plane.
import random
def point_on_triangle(pt1, pt2, pt3):
"""
Random point on the triangle with vertices pt1, pt2 and pt3.
"""
x, y = sorted([random.random(), random.random()])
s, t, u = x, y - x, 1 - y
return (s * pt1[0] + t * pt2[0] + u * pt3[0],
s * pt1[1] + t * pt2[1] + u * pt3[1])
The idea is to compute a weighted average of the three vertices, with the weights given by a random break of the unit interval [0, 1] into three pieces (uniformly over all such breaks). Here x and y represent the places at which we break the unit interval, and s, t and u are the length of the pieces following that break. We then use s, t and u as the barycentric coordinates of the point in the triangle.
Here's a variant of the above that avoids the need to sort, instead making use of an absolute value call:
def point_on_triangle2(pt1, pt2, pt3):
"""
Random point on the triangle with vertices pt1, pt2 and pt3.
"""
x, y = random.random(), random.random()
q = abs(x - y)
s, t, u = q, 0.5 * (x + y - q), 1 - 0.5 * (q + x + y)
return (
s * pt1[0] + t * pt2[0] + u * pt3[0],
s * pt1[1] + t * pt2[1] + u * pt3[1],
)
Here's an example usage that generates 10000 points in a triangle:
pt1 = (1, 1)
pt2 = (2, 4)
pt3 = (5, 2)
points = [point_on_triangle(pt1, pt2, pt3) for _ in range(10000)]
And a plot obtained from the above, demonstrating the uniformity. The plot was generated by this code:
import matplotlib.pyplot as plt
x, y = zip(*points)
plt.scatter(x, y, s=0.1)
plt.show()
Here's the image:
And since you tagged the question with the "numpy" tag, here's a NumPy version that generates multiple samples at once. Note that it uses the matrix multiplication operator #, introduced in Python 3.5 and supported in NumPy >= 1.10. You'll need to replace that with a call to np.dot on older Python or NumPy versions.
import numpy as np
def points_on_triangle(v, n):
"""
Give n random points uniformly on a triangle.
The vertices of the triangle are given by the shape
(2, 3) array *v*: one vertex per row.
"""
x = np.sort(np.random.rand(2, n), axis=0)
return np.column_stack([x[0], x[1]-x[0], 1.0-x[1]]) # v
# Example usage
v = np.array([(1, 1), (2, 4), (5, 2)])
points = points_on_triangle(v, 10000)
Ok, time to add another version, I guess. There is known algorithm to sample uniformly in triangle, see paper, chapter 4.2 for details.
Python code:
import math
import random
import matplotlib.pyplot as plt
def trisample(A, B, C):
"""
Given three vertices A, B, C,
sample point uniformly in the triangle
"""
r1 = random.random()
r2 = random.random()
s1 = math.sqrt(r1)
x = A[0] * (1.0 - s1) + B[0] * (1.0 - r2) * s1 + C[0] * r2 * s1
y = A[1] * (1.0 - s1) + B[1] * (1.0 - r2) * s1 + C[1] * r2 * s1
return (x, y)
random.seed(312345)
A = (1, 1)
B = (2, 4)
C = (5, 2)
points = [trisample(A, B, C) for _ in range(10000)]
xx, yy = zip(*points)
plt.scatter(xx, yy, s=0.2)
plt.show()
And result looks like
Uniform on the triangle?
import numpy as np
N = 10 # number of points to create in one go
rvs = np.random.random((N, 2)) # uniform on the unit square
# Now use the fact that the unit square is tiled by the two triangles
# 0 <= y <= x <= 1 and 0 <= x < y <= 1
# which are mapped onto each other (except for the diagonal which has
# probability 0) by swapping x and y.
# We use this map to send all points of the square to the same of the
# two triangles. Because the map preserves areas this will yield
# uniformly distributed points.
rvs = np.where(rvs[:, 0, None]>rvs[:, 1, None], rvs, rvs[:, ::-1])
Finally, transform the coordinates
xmin, ymin, xmax, ymax = -0.1, 1.1, 2.0, 3.3
rvs = np.array((ymin, xmin)) + rvs*(ymax-ymin, xmax-xmin)
Uniform marginals? The simplest solution would be to uniformly concentrate the mass on the line (ymin, xmin) - (ymax, xmax)
rvs = np.random.random((N,))
rvs = np.c_[ymin + (ymax-ymin)*rvs, xmin + (xmax-xmin)*rvs]
but that is not very interesting, is it?

Producing 2D perlin noise with numpy

I'm trying to produce 2D perlin noise using numpy, but instead of something smooth I get this :
my broken perlin noise, with ugly squares everywhere
For sure, I'm mixing up my dimensions somewhere, probably when I combine the four gradients ... But I can't find it and my brain is melting right now. Anyone can help me pinpoint the problem ?
Anyway, here is the code:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
def perlin(x,y,seed=0):
# permutation table
np.random.seed(seed)
p = np.arange(256,dtype=int)
np.random.shuffle(p)
p = np.stack([p,p]).flatten()
# coordinates of the first corner
xi = x.astype(int)
yi = y.astype(int)
# internal coordinates
xf = x - xi
yf = y - yi
# fade factors
u = fade(xf)
v = fade(yf)
# noise components
n00 = gradient(p[p[xi]+yi],xf,yf)
n01 = gradient(p[p[xi]+yi+1],xf,yf-1)
n11 = gradient(p[p[xi+1]+yi+1],xf-1,yf-1)
n10 = gradient(p[p[xi+1]+yi],xf-1,yf)
# combine noises
x1 = lerp(n00,n10,u)
x2 = lerp(n10,n11,u)
return lerp(x2,x1,v)
def lerp(a,b,x):
"linear interpolation"
return a + x * (b-a)
def fade(t):
"6t^5 - 15t^4 + 10t^3"
return 6 * t**5 - 15 * t**4 + 10 * t**3
def gradient(h,x,y):
"grad converts h to the right gradient vector and return the dot product with (x,y)"
vectors = np.array([[0,1],[0,-1],[1,0],[-1,0]])
g = vectors[h%4]
return g[:,:,0] * x + g[:,:,1] * y
lin = np.linspace(0,5,100,endpoint=False)
y,x = np.meshgrid(lin,lin)
plt.imshow(perlin(x,y,seed=0))
Thanks to Paul Panzer and a good night of sleep it works now ...
import numpy as np
import matplotlib.pyplot as plt
def perlin(x, y, seed=0):
# permutation table
np.random.seed(seed)
p = np.arange(256, dtype=int)
np.random.shuffle(p)
p = np.stack([p, p]).flatten()
# coordinates of the top-left
xi, yi = x.astype(int), y.astype(int)
# internal coordinates
xf, yf = x - xi, y - yi
# fade factors
u, v = fade(xf), fade(yf)
# noise components
n00 = gradient(p[p[xi] + yi], xf, yf)
n01 = gradient(p[p[xi] + yi + 1], xf, yf - 1)
n11 = gradient(p[p[xi + 1] + yi + 1], xf - 1, yf - 1)
n10 = gradient(p[p[xi + 1] + yi], xf - 1, yf)
# combine noises
x1 = lerp(n00, n10, u)
x2 = lerp(n01, n11, u) # FIX1: I was using n10 instead of n01
return lerp(x1, x2, v) # FIX2: I also had to reverse x1 and x2 here
def lerp(a, b, x):
"linear interpolation"
return a + x * (b - a)
def fade(t):
"6t^5 - 15t^4 + 10t^3"
return 6 * t**5 - 15 * t**4 + 10 * t**3
def gradient(h, x, y):
"grad converts h to the right gradient vector and return the dot product with (x,y)"
vectors = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]])
g = vectors[h % 4]
return g[:, :, 0] * x + g[:, :, 1] * y
lin = np.linspace(0, 5, 100, endpoint=False)
x, y = np.meshgrid(lin, lin) # FIX3: I thought I had to invert x and y here but it was a mistake
plt.imshow(perlin(x, y, seed=2), origin='upper')

Categories

Resources