Problem background: I am working on a monospaced parametric font, which allows for rendering letters of different styles by tweaking a set of parameters.
Specific problem: Given a rectangular region of W width and H height and given a stroke width S what should be the width of a horizontal projection/intersection X of stroke when rendering letter 'V'?
The letter is constrained by the rectangular region and cannot overlap or escape it in any fashion. The letter is symmetrical. I am not bothered by extreme argument values that would make the letter unrenderable.
My thoughts: Does this involve some sort of geometric constraint solver leading to an approximate solution based on a number of iterations?
Answer expectation: General direction on a class of the problem, ideally some formulas. Thank you.
Let denote length of low empty segment as
p = (w-x)/2
so
x = w - 2 * p
Look at annotated drawing - triangles ABC and DFA are similar right-angled ones with similar acute angles ^CAB and ^ADF, AB=h, AD=x, FD=s, BC=p)
So we can see that ratio of h to hypotenuse is the same as ratio of s and x (it is cosine of acute angle in left low triangle).
h / Sqrt(h^2 + p^2) = s / x = s / (w - 2 * p)
h^2 / (h^2 + p^2) = s^2 / (w^2 - 4 * w * p + 4 * p^2)
h^2 * (w^2 - 4 * w * p + 4 * p^2) = s^2 * (h^2 + p^2)
h^2 * w^2 - 4 * w * h^2 * p + 4 * h^2 * p^2 = s^2 * h^2 + s^2 * p^2
p^2 * (4 * h^2 - s^2) - p * (4 * w * h^2 ) + h^2 * (w^2 - s^2) = 0
Now we have quadratic equation for unknown p. Choose reliable p root value (positive and less than w/2) and calculate x
For example, h=4; w=6; s=1.5 gives p~2.14, so x ~ 1.7. Maple sheet:
(Everything's in 3D) Firstly a plane is given. On this plane I'm drawing a line. The beginning and ending point of the line is given therefore. What I have to do is to use the line as the diagonal of the rectangle to create the rectangle. For this I just need the other two missing points which are also in the very same plane I've got.
How can I determine the missing two points?
In 2D for example if you have like point B (6|4) and C (1|2) then you can conclude that A is on (1|4) and D is on (6|2).
But I struggle to find a method/algorithm to do so in a 3D world.
PS: If I used the wrong tag please tell me another suggestion, thx!
To show that infinite number of rectangles with common diagonal does exist in the same plane:
You have vertices A and C, and plane normal vector n, and want to determine vertices B and D.
Let B = (bx, by, bz) (unknown)
Condition of perpendicularity of AB and BC edges: dot product of vectors is zero.
(bx-ax) * (bx-сx) + (by-ay) * (by-сy) + (bz-az) * (bz-сz) = 0
Condition of "B lies in the plane": dot product of AB and normal is zero
(bx-ax) * nx + (by-ay) * ny + (bz-az) * nz = 0
So you have two linear equations for three unknowns bx, by, bz - infinite number of solutions.
Perhaps you might have some additional condition/restriction to define solution uniquely (as axis-aligned rectangle in your 2d example)
Edit:
Arbitrary possible variant: let AB edge is parallel to OXY plane, so it is perpendicular to OZ axis, and the third equation is
(bx-ax) * 0 + (by-ay) * 0 + (bz-az) * 1 = 0, so
(bz - az) = 0
and you can substitute this expression and solve system for two unknowns bx and by
(bx-ax) * (bx-сx) + (by-ay) * (by-сy) = 0
(bx-ax) * nx + (by-ay) * ny = 0
I'm trying to perform bilinear quadrilateral interpolation. So I have four nodes with known values and I want to find a value that lies in between those four nodes by interpolation, but the four nodes do not form a rectangle. 4-node sketch
I found several ways to solve this, but none of them is implemented in Python already. Does there exist somewhere an already finished python implementation? If not which of the two solutions below would you recommend? Or would you recommend another approach?
**************Different solutions*******************
Solution 1:
I found here, https://www.colorado.edu/engineering/CAS/courses.d/IFEM.d/IFEM.Ch16.d/IFEM.Ch16.pdf, that I should solve the following set of equations: set of equations with Ni being: N definition.
Finally this results in solving a set of equations of the form:
a*x+b*y+c*xy=z1
d*x+e*y+f*xy=z2
with x and y being the unknowns. This could be solved numerically using fsolve.
Solution 2:
This one is completely explained here: https://math.stackexchange.com/questions/828392/spatial-interpolation-for-irregular-grid
but it's quite complex and I think it will take me longer to code it.
Due to a lack of answers I went for the first option. You can find the code below. Recommendations to improve this code are always welcome.
import numpy as np
from scipy.optimize import fsolve
def interpolate_quatrilateral(pt1,pt2,pt3,pt4,pt):
'''Interpolates a value in a quatrilateral figure defined by 4 points.
Each point is a tuple with 3 elements, x-coo,y-coo and value.
point1 is the lower left corner, point 2 the lower right corner,
point 3 the upper right corner and point 4 the upper left corner.
args is a list of coordinates in the following order:
x1,x2,x3,x4 and x (x-coo of point to be interpolated) and y1,y2...
code based on the theory found here:
https://www.colorado.edu/engineering/CAS/courses.d/IFEM.d/IFEM.Ch16.d/IFEM.Ch16.pdf'''
coos = (pt1[0],pt2[0],pt3[0],pt4[0],pt[0],
pt1[1],pt2[1],pt3[1],pt4[1],pt[1]) #coordinates of the points merged in tuple
guess = np.array([0,0]) #The center of the quadrilateral seem like a good place to start
[eta, mu] = fsolve(func=find_local_coo_equations, x0=guess, args=coos)
densities = (pt1[2], pt2[2], pt3[2], pt4[2])
density = find_density(eta,mu,densities)
return density
def find_local_coo_equations(guess, *args):
'''This function creates the transformed coordinate equations of the quatrilateral.'''
eta = guess[0]
mu = guess[1]
eq=[0,0]#Initialize eq
eq[0] = 1 / 4 * (args[0] + args[1] + args[2] + args[3]) - args[4] + \
1 / 4 * (-args[0] - args[1] + args[2] + args[3]) * mu + \
1 / 4 * (-args[0] + args[1] + args[2] - args[3]) * eta + \
1 / 4 * (args[0] - args[1] + args[2] - args[3]) * mu * eta
eq[1] = 1 / 4 * (args[5] + args[6] + args[7] + args[8]) - args[9] + \
1 / 4 * (-args[5] - args[6] + args[7] + args[8]) * mu + \
1 / 4 * (-args[5] + args[6] + args[7] - args[8]) * eta + \
1 / 4 * (args[5] - args[6] + args[7] - args[8]) * mu * eta
return eq
def find_density(eta,mu,densities):
'''Finds the final density based on the eta and mu local coordinates calculated
earlier and the densities of the 4 points'''
N1 = 1/4*(1-eta)*(1-mu)
N2 = 1/4*(1+eta)*(1-mu)
N3 = 1/4*(1+eta)*(1+mu)
N4 = 1/4*(1-eta)*(1+mu)
density = densities[0]*N1+densities[1]*N2+densities[2]*N3+densities[3]*N4
return density
pt1= (0,0,1)
pt2= (1,0,1)
pt3= (1,1,2)
pt4= (0,1,2)
pt= (0.5,0.5)
print(interpolate_quatrilateral(pt1,pt2,pt3,pt4,pt))
I have triangle: a, b, c. Each vertex has a value: va, vb, vc. In my software the user drags point p around inside and outside of this triangle. I use barycentric coordinates to determine the value vp at p based on va, vb, and vc. So far, so good.
Now I want to limit p so that vp is within range min and max. If a user chooses p where vp is < min or > max, how can I find the point closest to p where vp is equal to min or max, respectively?
Edit: Here is an example where I test each point. Light gray is within min/max. How can I find the equations of the lines that make up the min/max boundary?
a = 200, 180
b = 300, 220
c = 300, 300
va = 1
vb = 1.4
vc = 3.2
min = 0.5
max = 3.5
Edit: FWIW, so far first I get the barycentric coordinates v,w for p using the triangle vertices a, b, c (standard stuff I think, but looks like this). Then to get vp:
u = 1 - w - v
vp = va * u + vb * w + vc * v
That is all fine. My trouble is that I need the line equations for min/max so I can choose a new position for p when vp is out of range. The new position for p is the point closest to p on the min or max line.
Note that p is an XY coordinate and vp is a value for that coordinate determined by the triangle and the values at each vertex. min and max are also values. The two line equations I need will give me XY coordinates for which the values determined by the triangle are min or max.
It doesn't matter if barycentric coordinates are used in the solution.
The trick is to use the ratio of value to cartesian distance to extend each triangle edge until it hits min or max. Easier to see with a pic:
The cyan lines show how the triangle edges are extended, the green Xs are points on the min or max lines. With just 2 of these points we know the slope if the line. The yellow lines show connecting the Xs aligns with the light gray.
The math works like this, first get the value distance between vb and vc:
valueDistBtoC = vc - vb
Then get the cartesian distance from b to c:
cartesianDistBtoC = b.distance(c)
Then get the value distance from b to max:
valueDistBtoMax = max - vb
Now we can cross multiply to get the cartesian distance from b to max:
cartesianDistBtoMax = (valueDistBtoMax * cartesianDistBtoC) / valueDistBtoC
Do the same for min and also for a,b and c,a. The 6 points are enough to restrict the position of p.
Consider your triangle to actually be a 3D triangle, with points (ax,ay,va), (bx,by,vb), and (cx,cy,vc). These three points define a plane, containing all the possible p,vp triplets obtainable through barycentric interpolation.
Now think of your constraints as two other planes, at z>=max and z<=min. Each of these planes intersects your triangle's plane along an infinite line; the infinite beam between them, projected back down onto the xy plane, represents the area of points which satisfy the constraints. Once you have the lines (projected down), you can just find which (if either) is violated by a particular point, and move it onto that constraint (along a vector which is perpendicular to the constraint).
Now I'm not sure about your hexagon, though. That's not the shape I would expect.
Mathematically speaking the problem is simply a change of coordinates. The more difficult part is finding a good notation for the quantities involved.
You have two systems of coordinates: (x,y) are the cartesian coordinates of your display and (v,w) are the baricentric coordinates with respect to the vectors (c-a),(b-a) which determine another (non orthogonal) system.
What you need is to find the equation of the two lines in the (x,y) system, then it will be easy to project the point p on these lines.
To achieve this you could explicitly find the matrix to pass from (x,y) coordinates to (v,w) coordinates and back. The function you are using toBaryCoords makes this computation to find the coordinates (v,w) from (x,y) and we can reuse that function.
We want to find the coefficients of the transformation from world coordinates (x,y) to barycentric coordinates (v,w). It must be in the form
v = O_v + x_v * x + y_v * y
w = O_w + x_w * x + y_w * y
i.e.
(v,w) = (O_v,O_w) + (x_v,y_y) * (x,y)
and you can determine (O_v,O_w) by computing toBaryCoord(0,0), then find (x_v,x_w) by computing the coordinates of (1,0) and find (y_v,y_w)=toBaryCoord(1,0) - (O_v,O_w) and then find (y_v,y_w) by computing (y_v,y_w) = toBaryCoord(0,1)-(O_v,O_w).
This computation requires calling toBaryCoord three times, but actually the coefficients are computed inside that routine every time, so you could modify it to compute at once all six values.
The value of your function vp can be computed as follows. I will use f instead of v because we are using v for a baricenter coordinate. Hence in the following I mean f(x,y) = vp, fa = va, fb = vb, fc = vc.
You have:
f(v,w) = fa + (fb-fa)*v + (fc-fa)*w
i.e.
f(x,y) = fa + (fb-fa) (O_v + x_v * x + y_v * y) + (fc-fa) (O_w + x_w * x + y_w * y)
where (x,y) are the coordinates of your point p. You can check the validity of this equation by inserting the coordinates of the three vertices a, b, c and verify that you obtain the three values fa, fb and fc. Remember that the barycenter coordinates of a are (0,0) hence O_v + x_v * a_x + y_v * a_y = 0 and so on... (a_x and a_y are the x,y coordinates of the point a).
If you let
q = fa + (fb_fa)*O_v + (fc-fa)*O_w
fx = (fb-fa)*x_v + (fc-fa) * x_w
fy = (fb-fa)*y_v + (fc-fa) * y_w
you get
f(x,y) = q + fx*x + fy * y
Notice that q, fx and fy can be computed once from a,b,c,fa,fb,fc and you can reuse them if you only change the coordinates (x,y) of the point p.
Now if f(x,y)>max, you can easily project (x,y) on the line where max is achieved. The coordinates of the projection are:
(x',y') = (x,y) - [(x,y) * (fx,fy) - max + q]/[(fx,fy) * (fx,fy)] (fx,fy)
Now. You would like to have the code. Well here is some pseudo-code:
toBarycoord(Vector2(0,0),a,b,c,O);
toBarycoord(Vector2(1,0),a,b,c,X);
toBarycoord(Vector2(0,1),a,b,c,Y);
X.sub(O); // X = X - O
Y.sub(O); // Y = Y - O
V = Vector2(fb-fa,fc-fa);
q = fa + V.dot(O); // q = fa + V*O
N = Vector2(V.dot(X),V.dot(Y)); // N = (V*X,V*Y)
// p is the point to be considered
f = q + N.dot(p); // f = q + N*p
if (f > max) {
Vector2 tmp;
tmp.set(N);
tmp.multiply((N.dot(p) - max + q)/(N.dot(N))); // scalar multiplication
p.sub(tmp);
}
if (f < min) {
Vector2 tmp;
tmp.set(N);
tmp.multiply((N.dot(p) - min + q)/(N.dot(N))); // scalar multiplication
p.sum(tmp);
}
We think of the problem as follows: The three points are interpreted as a triangle floating in 3D space with the value being the Z-axis and the cartesian coordinates mapped to the X- and Y- axes respectively.
Then the question is to find the gradient of the plane that is defined by the three points. The lines where the plane intersects with the z = min and z = max planes are the lines you want to restrict your points to.
If you have found a point p where v(p) > max or v(p) < min we need to go in the direction of the steepest slope (the gradient) until v(p + k * g) = max or min respectively. g is the direction of the gradient and k is the factor we need to find. The coordinates you are looking for (in the cartesian coordinates) are the corresponding components of p + k * g.
In order to determine g we calculate the orthonormal vector that is perpendicular to the plane that is determined by the three points using the cross product:
// input: px, py, pz,
// output: p2x, p2y
// local variables
var v1x, v1y, v1z, v2x, v2y, v2z, nx, ny, nz, tp, k,
// two vectors pointing from b to a and c respectively
v1x = ax - bx;
v1y = ay - by;
v1z = az - bz;
v2x = cx - bx;
v2y = cy - by;
v2z = cz - bz;
// the cross poduct
nx = v2y * v1z - v2z * v1y;
ny = v2z * v1x - v2x * v1z;
nz = v2x * v1y - v2y * v1x;
// using the right triangle altitude theorem
// we can calculate the vector that is perpendicular to n
// in our triangle we are looking for q where p is nz, and h is sqrt(nx*nx+ny*ny)
// the theorem says p*q = h^2 so p = h^2 / q - we use tp to disambiguate with the point p - we need to negate the value as it points into the opposite Z direction
tp = -(nx*nx + ny*ny) / nz;
// now our vector g = (nx, ny, tp) points into the direction of the steepest slope
// and thus is perpendicular to the bounding lines
// given a point p (px, py, pz) we can now calculate the nearest point p2 (p2x, p2y, p2z) where min <= v(p2z) <= max
if (pz > max){
// find k
k = (max - pz) / tp;
p2x = px + k * nx;
p2y = py + k * ny;
// proof: p2z = v = pz + k * tp = pz + ((max - pz) / tp) * tp = pz + max - pz = max
} else if (pz < min){
// find k
k = (min - pz) / tp;
p2x = px + k * nx;
p2y = py + k * ny;
} else {
// already fits
p2x = px;
p2y = py;
}
Note that obviously if the triangle is vertically oriented (in 2D it's not a triangle anymore actually), nz becomes zero and tp cannot be calculated. That's because there are no more two lines where the value is min or max respectively. For this case you will have to choose another value on the remaining line or point.
I'm trying to generate the Sierpinski triangle (chaos game version) in J. The general iterative algorithm to generate it, given 3 vertices, is:
point = (0, 0)
loop:
v = randomly pick one of the 3 vertices
point = (point + v) / 2
draw point
I'm trying to create the idiomatic version in J. So far this is what I have:
load 'plot'
numpoints =: 200000
verticesx =: 0 0.5 1
verticesy =: 0 , (2 o. 0.5) , 0
rolls =: ?. numpoints$3
pointsx =: -:#+ /\. rolls { verticesx
pointsy =: -:#+ /\. rolls { verticesy
'point' plot pointsx ; pointsy
This works, but I'm not sure I understand what's going on with -:#+ /\.. I think it's only working because of a mathematical quirk. I was trying to make a dyadic average function that would run as an accumulation through the list of points in the same way that + does in +/ \ i. 10, but I couldn't get anything like that to work. How would I do that?
Update:
To be clear, I'm trying to create a binary function avg that I could use in this way:
avg /\ randompoints
avg =: -:#+ doesn't work with this, for some reason. So I think what I'm having trouble with is properly defining an avg function with the proper variadicity.
To be as close to the algorithm as possible, I would probably do something like this:
v =: 3 2$ 0 0 0.5, (2 o. 0.5), 1 0
ps =: 1 2 $ (?3) { v
next =: 4 :'y,((?x){v) -:#+ ({: y)'
ps =: (3&next)^:20000 ps
'point' plot ({.;{:) |: ps
but your version is much more efficient.