Interpolation of a triangle - geometry

I have a unit right triangle and a value at each of the 3 vertices.
I need to interpolate to find the value at a point inside the triangle.
Hours of searching have turned up nothing that actually tells me how to do this.
Here is my closest attempt, which is actually pretty close but not quite right -
result =
v1 * (1 - x) * (1 - y) +
v2 * x * (1 - y) +
v3 * x * y;
v1, v2, and v3 are the values at the 3 vertices of the triangle.
(x, y) is the point in the triangle that you are trying to find the value of.
Any kind of method would help me here. It doesn't necessarily need to be a unit/right triangle.
Updated info:
I have a grid of evenly spaced points and a value at each point.
I make a triangle out of the nearest 3 points on the grid.
Here is a picture to illustrate it -
So I have to interpolate between 5, 3, and 7 to find the value of x.
The point could also be inside the other triangle, meaning you would interpolate between 5, 7, and the value of the bottom left corner of the square.
In the code I showed, v1 = 5, v2 = 3, v3 = 7.
x is the fractional distance (range [0-1]) in the "x" direction, and y is the fractional distance in the "y" direction.
In the picture's example, x would probably be about 0.75 and y would be about 0.2
Here are my closest attempts -
Created using -
if (x > y) //if x > y then the point is in the upper right triangle
return
v1 * (1 - x) * (1 - y) +
v2 * x * (1 - y) +
v3 * x * y;
else //bottom left triangle
return
v1 * (1 - x) * (1 - y) +
v4 * (1 - x) * y +
v3 * x * y;
And another attempt -
Created using -
if (x > y)
return
(1 - x) * v1 + (x - y) * v2 + y * v3;
else
return
(1 - y) * v1 + (y - x) * v4 + x * v3;
They're both close to what I need but obviously not quite right.

You should use barycentric coordinates. There is a very thorough write-up here that also discusses alternative solutions and why barycentric coordinates are best: CodePlea - Interpolating in a Triangle
Basically, the weights will end up looking like this:

Actually the simplest and most robust solution is based on barycentric coordinates -
http://answers.unity3d.com/questions/383804/calculate-uv-coordinates-of-3d-point-on-plane-of-m.html

I asked this 3 years ago and have still been working on a way to do this. I do believe it is impossible to do it without artifacts unless using an equilateral triangle.
Here is a decent way to do it using barycentric coordinates and then adding a technique that gets rid of most of the artifacts.
v1, v2, v3 are the values at the three points of the triangle. x, y is the point you want to find a value for.
if (x > y)
{
b1 = -(x - 1);
b2 = (x - 1) - (y - 1);
b3 = 1 - b1 - b2;
}
else
{
b1 = -(y - 1);
b2 = -((x - 1) - (y - 1));
b3 = 1 - b1 - b2;
}
float
abs = x - y;
if (abs < 0) abs *= -1;
if (abs < 0.25f)
{
abs = 0.25f - abs;
abs *= abs;
b1 -= abs;
b3 -= abs;
}
b1 *= b1;
b2 *= b2;
b3 *= b3;
float fd = 1 / (b1 + b2 + b3);
b1 *= fd;
b2 *= fd;
b3 *= fd;
return
v1 * b1 +
v2 * b2 +
v3 * b3;

Ok, so we will do a linear interpolation, assuming that the gradient is constant with respect to x and to y. d/dx = v2 - v1 and d/dy = v3 - v2, and f(0,0) = v1. We have a simple two dimensional differential equation.
d{f(x,y)} = (v2 - v1)*dx
f(x,y) = (v2 - v1)*x + g(y)
d{f(x,y)} = g'(y) = (v3 - v2)*dy
g(y) = (v3 - v2)*y + C
f(x,y) = (v2 - v1)*x + (v3 - v2)*y + C
f(0,0) = v1 = (v2 - v1)*0 + (v3 - v2)*0 + C = C
f(x,y) = (v2 - v1)*x + (v3 - v2)*y + v1
or in terms of v1 v2 and v3
f(x,y) = (1 - x)*v1 + (x - y)*v2 + y*v3
If you want to do it in a square for four vertices, as above with v4 in the bottom left at x=0 y=1, here are the conditions: d/dx = (v2 - v1) (1 - y) + (v3 - v4) y, d/dy = (v3 - v2) x + (v4 - v1) (1 - x), f(0,0) = v1
d/dx = (v2 - v1) (1 - y) + (v3 - v4) y
f(x,y) = (v2 - v1) (1 - y) x + (v3 - v4) y x + g(y)
d/dy = (v3 - v2) x + (v4 - v1) (1 - x) = -(v2 - v1) x + (v3 - v4) x + g'(y)
v3 - v2 + (v4 - v1) / x + v4 - v1 = -v2 + v1 + v3 - v4 + g'(y) / x
(v4 - v1) / x + 2*(v4 - v1) = g'(y) / x
g'(y) = (v4 - v1) + 2 x (v4 - v1)
g(y) = (v4 - v1) (1 + 2 x) y + C
f(x,y) = (v2 - v1) (1 - y) x + (v3 - v4) y x + (v4 - v1) (1 + 2 x) y + C
f(0,0) = (v2 - v1) (1 - 0) 0 + (v3 - v4) 0 0 + (v4 - v1) (1 + 2 0) 0 + C = v1
f(x,y) = (v2 - v1) (1 - y) x + (v3 - v4) y x + (v4 - v1) (1 + 2 x) y + v1

Here is some pseudocode for nearest-neighbor:
if( dist( p, p1 ) <= dist( p, p2 ) && dist( p, p1 ) <= dist( p, p3 ) )
return val( p1 )
if( dist( p, p2 ) <= dist( p, p3 ) && dist( p, p2 ) <= dist( p, p1 ) )
return val( p2 )
if( dist( p, p3 ) <= dist( p, p1 ) && dist( p, p3 ) <= dist( p, p2 ) )
return val( p3 )
I think this also generates a voronoi diagram

Related

Why the 2D spline graph is incorrect?

I started getting into spline principles to use them in my studies. However, I got stuck in programming the code for Wikipedia [example] . The final graph doesn't correspond to the Wiki's example. I get two curves that are not connected, despite the introduction of the parameter t(x):
Spline segments
Would you mind helping me in finding the cause of the error? Thanks.
import numpy as np
import matplotlib.pyplot as plt
x0, y0, x1, y1, x2, y2 = -1, 0.5, 0, 0, 3, 3
def spline(x0, y0, x1, y1, x2, y2):
a11 = 2 / (x1 - x0)
a12 = 1 / (x1 - x0)
a13 = 0
a21 = a12
a22 = 2 * (1 / (x1 - x0) + 1 / (x2 - x1))
a23 = 1 / (x2 - x1)
a31 = a13
a32 = a23
a33 = 2 / (x2 - x1)
b1 = 3 * ((y1 - y0) / (x1 - x0) ** 2)
b2 = 3 * ((y1 - y0) / (x1 - x0) ** 2 + (y2 - y1) / (x2 - x1) ** 2)
b3 = 3 * ((y2 - y1) / (x2 - x1) ** 2)
A = np.array([[a11, a12, a13], [a21, a22, a23], [a31, a32, a33]])
L = np.array([[-b1], [-b2], [-b3]])
AT = np.transpose(A)
ATA = np.matmul(AT, A)
invATA = np.linalg.inv(ATA)
ATL = np.matmul(AT, L)
X = - np.matmul(invATA, ATL)
[k0, k1, k2] = X
a1 = k0 * (x1 - x0) - (y1 - y0)
b1 = -k1 * (x1 - x0) + (y1 - y0)
a2 = k1 * (x2 - x1) - (y2 - y1)
b2 = -k2 * (x2 - x1) + (y2 - y1)
return a1[0], b1[0], a2[0], b2[0]
a1, b1, a2, b2 = spline(-1, 0.5, 0, 0, 3, 3)
def q1(t):
return (1 - t) * y0 + t * y1 + t *(1 - t) * ((1 - t) * a1 + t * b1)
def q2(t):
return (1 - t) * y1 + t * y2 + t *(1 - t) * ((1 - t) * a2 + t * b2)
x_list = []
y1_list = []
y2_list = []
for x in range(1000):
t = (x/1000 - x1) / (x2 - x1)
x_list.append(x)
y1_list.append(q1(t))
y2_list.append(q2(t))
plt.scatter(x_list, y1_list)
plt.scatter(x_list, y2_list)
I found it! The key was the proper x-range corresponding to the 3 given points.
import numpy as np
import matplotlib.pyplot as plt
x0, y0, x1, y1, x2, y2 = -1, 0.5, 0, 0, 3, 3
def spline(x0, y0, x1, y1, x2, y2):
a11 = 2 / (x1 - x0)
a12 = 1 / (x1 - x0)
a13 = 0
a21 = a12
a22 = 2 * (1 / (x1 - x0) + 1 / (x2 - x1))
a23 = 1 / (x2 - x1)
a31 = a13
a32 = a23
a33 = 2 / (x2 - x1)
b1 = 3 * ((y1 - y0) / (x1 - x0) ** 2)
b2 = 3 * ((y1 - y0) / (x1 - x0) ** 2 + (y2 - y1) / (x2 - x1) ** 2)
b3 = 3 * ((y2 - y1) / (x2 - x1) ** 2)
A = np.array([[a11, a12, a13], [a21, a22, a23], [a31, a32, a33]])
L = np.array([[-b1], [-b2], [-b3]])
AT = np.transpose(A)
ATA = np.matmul(AT, A)
invATA = np.linalg.inv(ATA)
ATL = np.matmul(AT, L)
X = - np.matmul(invATA, ATL)
[k0, k1, k2] = X
a1 = k0 * (x1 - x0) - (y1 - y0)
b1 = -k1 * (x1 - x0) + (y1 - y0)
a2 = k1 * (x2 - x1) - (y2 - y1)
b2 = -k2 * (x2 - x1) + (y2 - y1)
return a1[0], b1[0], a2[0], b2[0]
a1, b1, a2, b2 = spline(-1, 0.5, 0, 0, 3, 3)
def q1(t):
return (1 - t) * y0 + t * y1 + t *(1 - t) * ((1 - t) * a1 + t * b1)
def q2(t):
return (1 - t) * y1 + t * y2 + t *(1 - t) * ((1 - t) * a2 + t * b2)
x1_list = []
x2_list = []
y1_list = []
y2_list = []
for x in range(-1000, 0, 1):
t = (x/1000 - x0) / (x1 - x0)
x1_list.append(x)
y1_list.append(q1(t))
for x in range(3000):
t = (x/1000 - x1) / (x2 - x1)
x2_list.append(x)
y2_list.append(q2(t))
plt.scatter(x1_list, y1_list)
plt.scatter(x2_list, y2_list)

How to properly convert hsl colors to rgb colors in lua?

I have the following code:
#!/usr/bin/env lua5.3
-- Code adapted from https://github.com/EmmanuelOga/columns/blob/master/utils/color.lua#L51
local function hslToRgb(h, s, l, a)
local r, g, b
h = (h / 255)
s = (s / 100)
l = (l / 100)
if s == 0 then
r, g, b = l, l, l -- achromatic
else
local function hue2rgb(p, q, t)
if t < 0 then t = t + 1 end
if t > 1 then t = t - 1 end
if t < 1/6 then return p + (q - p) * 6 * t end
if t < 1/2 then return q end
if t < 2/3 then return p + (q - p) * (2/3 - t) * 6 end
return p
end
local q
if l < 0.5 then q = l * (1 + s) else q = l + s - l * s end
local p = 2 * l - q
r = hue2rgb(p, q, h + 1/3)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h - 1/3)
end
if not a then a = 1 end
return r * 255, g * 255, b * 255, a * 255
end
local h,s,l,a
h,s,l,a = hslToRgb(220, 16.4, 21.6)
print(h,s,l,a)
-- expected output: 46 52 64 255
-- actual output: 64.11312 46.04688 60.92496 255
But, as stated at the end, the color values it outputs are completely wrong. The decimals are not an issue (as in, it's not an issue that it outputs them; their values are still wrong).
A hue value it's calculated in degrees, so the max isn't 255, but 360:
function hslToRgb(h, s, l)
h = h / 360
s = s / 100
l = l / 100
local r, g, b;
if s == 0 then
r, g, b = l, l, l; -- achromatic
else
local function hue2rgb(p, q, t)
if t < 0 then t = t + 1 end
if t > 1 then t = t - 1 end
if t < 1 / 6 then return p + (q - p) * 6 * t end
if t < 1 / 2 then return q end
if t < 2 / 3 then return p + (q - p) * (2 / 3 - t) * 6 end
return p;
end
local q = l < 0.5 and l * (1 + s) or l + s - l * s;
local p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
end
if not a then a = 1 end
return r * 255, g * 255, b * 255, a * 255
end
You can see this code working here.
I have been working on a more elegant solution to the HSV to RGB problem for a little bit now and this is what I've come up with.
local ceil = math.ceil
local abs = math.abs
local function clamp(v, min, max)
if v < min then return min end
if v > max then return max end
return v
end
local function HSV(h, s, v)
local vert = ceil(h / 120)
local r = abs(((h / 60) - 2 * vert))
local r, g, b = clamp(r, 1 - s, 1), clamp(2 - r, 1 - s, 1), (1 - s * v)
if vert == 1 then return r, g, b end
if vert == 2 then return b, r, g end
if vert == 3 then return g, b, r end
end
-- HSV to RGB
min = math.min
max = math.max
abs = math.abs
local function HSV2RGB (h, s, v)
local k1 = v*(1-s)
local k2 = v - k1
local r = min (max (3*abs (((h )/180)%2-1)-1, 0), 1)
local g = min (max (3*abs (((h -120)/180)%2-1)-1, 0), 1)
local b = min (max (3*abs (((h +120)/180)%2-1)-1, 0), 1)
return k1 + k2 * r, k1 + k2 * g, k1 + k2 * b
end

Multi Parameter Function Replacement with Sympy

I've got a math equation that I want to visually record the substitution of.
The equation is y = h * f(t + h, h + f(t, h)), where f(x, y) = x + y - 1
I want to substitute f into y, such that I get:
y = h * f(t + h, h + (t + h - 1))
y = h * (t + h + h + (t + h - 1) - 1)
I've had issues with replace not allowing me to do multi-parameter substitution
I don't have much code, since I'm not sure how to implement it
from sympy import *
f = Function('f')(x, y)
eqn = h * f(t + h, h + f(t, h))
Thanks
sympy.Function is used for declaring undefined functions but in your case the function is known.
The following code seems to work fine over here
from sympy import *
x,y,t,h = symbols('x y t h')
def f(x,y):
return x + y - 1
y = h * f(t+h,h+f(t,h))
y = expand(y)
display(y)
The role of the expand function was to work out the outer multiplication by h in the definition of y.
You can run it in a Jupyter notebook or as an alternative use the print or display function, I get the following result:
Extending average's answer -- Their solution works perfectly if the function is known.
To make it work for a function that's input from the user, you need to do this:
function = input()
def f(x, y, evaluate = False):
eqn = sympify(function)
if evaluate:
eqn = eqn.subs([("x", x), ("y", y)])
return eqn
y = h + f(h, t, True)
This way, if the user inputs "x ** y" for f, y will expand to h + h ** t

Heron's method in haskell

I'm getting divide by zero exceptions in this code of heron's method, and I am kind of lost here.
epsilon:: Integral a => a
epsilon = 1
heron:: Integral a => a -> a
heron r = help 0
where
help x
| abs (heron' x - heron' (x + 1)) < epsilon = heron' (x + 1)
| otherwise = help (x + 1)
heron' 0 = 1
heron' x = (1 `div` 2) * (heron' (x-1) + (r `div` heron' (x-1)))
Any suggestions where in this code I have to look to solve this problem?
(1 `div` 2) is definitely a problem , but what do I need to write instead?
If you need division of this kind, you probably want to use (/) instead of div and Fractional instead of Integral. So:
epsilon:: Fractional a => a
epsilon = 1
heron:: (Fractional a, Ord a) => a -> a
heron r = help 0
where
help x
| abs (heron' x - heron' (x + 1)) < epsilon = heron' (x + 1)
| otherwise = help (x + 1)
heron' 0 = 1
heron' x = (1 / 2) * (heron' (x-1) + (r / heron' (x-1)))

Why does my implementation of SVG arc conversion not pass QuickCheck?

I implemented W3s recommended algorithm for converting SVG-path arcs from endpoint-arcs to center-arcs and back in Haskell.
type EndpointArc = ( Double, Double, Double, Double
, Bool, Bool, Double, Double, Double )
type CenterArc = ( Double, Double, Double, Double
, Double, Double, Double )
endpointToCenter :: EndpointArc -> CenterArc
centerToEndpoint :: CenterArc -> EndpointArc
See full implementation and test-code here.
But I can't get this property to pass:
import Test.QuickCheck
import Data.AEq ((~==))
instance Arbitrary EndpointArc where
arbitrary = do
((x1,y1),(x2,y2)) <- arbitrary `suchThat` (\(u,v) -> u /= v)
rx <- arbitrary `suchThat` (>0)
ry <- arbitrary `suchThat` (>0)
phi <- choose (0,2*pi)
(fA,fS) <- arbitrary
return $ correctRadiiSize (x1, y1, x2, y2, fA, fS, rx, ry, phi)
prop_conversionRetains :: EndpointArc -> Bool
prop_conversionRetains earc =
let result = centerToEndpoint (endpointToCenter earc)
in earc ~== result
Sometimes this is due to floating point errors (which seem to exceed ieee754) but sometimes there are NaNs in the result.
(NaN,NaN,NaN,NaN,False,False,1.0314334509082723,2.732814841776921,1.2776112657142984)
Which indicates there is no solution although I think I scale rx,ry as described in F.6.6.2 in W3's document.
import Numeric.Matrix
m :: [[Double]] -> Matrix Double
m = fromList
toTuple :: Matrix Double -> (Double, Double)
toTuple = (\[[x],[y]] -> (x,y)) . toList
primed :: Double -> Double -> Double -> Double -> Double
-> (Double, Double)
primed x1 y1 x2 y2 phi = toTuple $
m [[ cos phi, sin phi]
,[-sin phi, cos phi]
]
* m [[(x1 - x2)/2]
,[(y1 - y2)/2]
]
correctRadiiSize :: EndpointArc -> EndpointArc
correctRadiiSize (x1, y1, x2, y2, fA, fS, rx, ry, phi) =
let (x1',y1') = primed x1 y1 x2 y2 phi
lambda = (x1'^2/rx^2) + (y1'^2/ry^2)
(rx',ry') | lambda <= 1 = (rx, ry)
| otherwise = ((sqrt lambda) * rx, (sqrt lambda) * ry)
in (x1, y1, x2, y2, fA, fS, rx', ry', phi)
OK, I figured this out myself. The clue was of course in W3s document:
In the case that the radii are scaled up using equation (F.6.6.3), the radicand of (F.6.5.2) is zero and there is exactly one solution for the center of the ellipse.
F.6.5.2 in my code is
(cx',cy') = (sq * rx * y1' / ry, sq * (-ry) * x1' / rx)
where sq = negateIf (fA == fS) $ sqrt
$ ( rx^2 * ry^2 - rx^2 * y1'^2 - ry^2 * x1'^2 )
/ ( rx^2 * y1'^2 + ry^2 * x1'^2 )
The radicand that it is referring to is
( rx^2 * ry^2 - rx^2 * y1'^2 - ry^2 * x1'^2 )
/ ( rx^2 * y1'^2 + ry^2 * x1'^2 )
But of course, because we are working with floats it's not exactly zero but approximately and sometimes it might be something like -6.99496644301622e-17 which is negative! The square-root of a negative number is a complex number so the calculation returns NaN.
The trick really would be to propagate the fact that rx and ry have been resized to return zero and make sq zero instead of going through the whole calculation unecessarily but the quick fix is just to take the absolute value of the radicand.
(cx',cy') = (sq * rx * y1' / ry, sq * (-ry) * x1' / rx)
where sq = negateIf (fA == fS) $ sqrt $ abs
$ ( rx^2 * ry^2 - rx^2 * y1'^2 - ry^2 * x1'^2 )
/ ( rx^2 * y1'^2 + ry^2 * x1'^2 )
After that there are some remaining floating point issues. Firstly the error exceeds what is allowed for by ieee754's ~== operator so I made my own approxEq
approxEq (x1a, y1a, x2a, y2a, fAa, fSa, rxa, rya, phia) (x1b, y1b, x2b, y2b, fAb, fSb, rxb, ryb, phib) =
abs (x1a - x1b ) < 0.001
&& abs (y1a - y1b ) < 0.001
&& abs (x2a - x2b ) < 0.001
&& abs (y2a - y2b ) < 0.001
&& abs (y2a - y2b ) < 0.001
&& abs (rxa - rxb ) < 0.001
&& abs (rya - ryb ) < 0.001
&& abs (phia - phib) < 0.001
&& fAa == fAb
&& fSa == fSb
prop_conversionRetains :: EndpointArc -> Bool
prop_conversionRetains earc =
let result = centerToEndpoint (trace ("FIRST:" ++ show (endpointToCenter earc)) (endpointToCenter earc))
in earc `approxEq` trace ("SECOND:" ++ show result) result
Which starts bringing cases where fA is getting flipped. Spot the magic number:
FIRST:(-5.988957688551294,-39.5430169665332,64.95929681921707,29.661347617532357,5.939852349879405,-1.2436798376040206,3.141592653589793)
SECOND:(4.209851895761209,-73.01839718538467,-16.18776727286379,-6.067636747681732,False,True,64.95929681921707,29.661347617532357,5.939852349879405)
*** Failed! Falsifiable (after 20 tests):
(4.209851895761204,-73.01839718538467,-16.18776781572145,-6.0676366434916655,True,True,64.95929681921707,29.661347617532357,5.939852349879405)
You got it! fA = abs dtheta > pi is in centerToEndpoint so if it's therabouts then it can go either way.
So I took out the fA condition and increased the number of tests in quickcheck
approxEq (x1a, y1a, x2a, y2a, fAa, fSa, rxa, rya, phia) (x1b, y1b, x2b, y2b, fAb, fSb, rxb, ryb, phib) =
abs (x1a - x1b ) < 0.001
&& abs (y1a - y1b ) < 0.001
&& abs (x2a - x2b ) < 0.001
&& abs (y2a - y2b ) < 0.001
&& abs (y2a - y2b ) < 0.001
&& abs (rxa - rxb ) < 0.001
&& abs (rya - ryb ) < 0.001
&& abs (phia - phib) < 0.001
-- && fAa == fAb
&& fSa == fSb
main = quickCheckWith stdArgs {maxSuccess = 50000} prop_conversionRetains
Which shows that the threshold approxEq is still not lax enough.
approxEq (x1a, y1a, x2a, y2a, fAa, fSa, rxa, rya, phia) (x1b, y1b, x2b, y2b, fAb, fSb, rxb, ryb, phib) =
abs (x1a - x1b ) < 1
&& abs (y1a - y1b ) < 1
&& abs (x2a - x2b ) < 1
&& abs (y2a - y2b ) < 1
&& abs (y2a - y2b ) < 1
&& abs (rxa - rxb ) < 1
&& abs (rya - ryb ) < 1
&& abs (phia - phib) < 1
-- && fAa == fAb
&& fSa == fSb
Which I can finally get to pass reliably with a high number of tests. Well its all just to make some funny graphics anyway... I am sure it's accurate enough :)

Resources