Plot Bezier Curve in Octave - matlab

I'm trying to plot Bezier Curves in Octave. A Bezier curve is defined by a number of control points, for example:
I'm trying to plot something like this. I found out about a function: drawBezierCurve but the function needs four control points and I need to plot a a Bezier curve with just three points. The control points are: a(1,2), b(4,-1), c(8,6). Any ideas?

Take the lines ab and bc:
a = [1 2];
b = [4 -1];
c = [8 6];
Create n equidistant points on them:
npoints = 10;
line1 = linspace(a, b, npoints);
line2 = linspace(b, c, npoints);
For the kth point on the Bézier curve, take k / n parts from first line and (n - k) / n parts from the second line.
weights = linspace(1, 0, npoints);
curve = line1 .* weights + line2 .* (1 - weights);
Connect the obtained points to draw the curve:
plot(curve(1, :), curve(2, :))
The complete code:
a = [1 2];
b = [4 -1];
c = [8 6];
npoints = 10; % modify this
line1 = linspace(a, b, npoints);
line2 = linspace(b, c, npoints);
weights = linspace(1, 0, npoints);
curve = line1 .* weights + line2 .* (1 - weights);
plot(curve(1, :), curve(2, :))
Additional code to draw the figures in this answer:
fig = figure;
hold on
% figure 1
plot([a(1) b(1)], [a(2) b(2)])
plot([b(1) c(1)], [b(2) c(2)])
% figure 2
plot(line1(1, :), line1(2, :), 'ok')
plot(line2(1, :), line2(2, :), 'or')
% figure 3
plot([line1(1,:); line2(1, :)], [line1(2, :); line2(2, :)], 'm--')
plot(curve(1, :), curve(2, :), 'oc')
% figure 4
plot(curve(1, :), curve(2, :), 'b', 'linewidth', 2)

You can always reproduce the bezier curve equation (see here)
P = [1,2; 4,-1; 8,6].';
B = #(n,i,u) nchoosek(n, i) * u .^ i .* (1-u) .^ (n-i);
U = [0:0.1:1];
Bez = B(2,0,U) .* P(:,1) + B(2,1,U) .* P(:,2) + B(2,2,U) .* P(:,3);
plot( Bez(1,:), Bez(2,:) );

Related

Rotate line to arbitary position around unit circle

I have a unit circle with n roots of unity marked. I would like to be able to rotate, translate, and scale a line resting on the x-axis (between -1 and 1) to connect any pair of marked roots. Currently my code can do this in some cases, but doesn't work in general. I want to avoid hard-coding how the line should move for each possible pair. Here's what I have so far:
clear
%% Roots of unity
n = 10;
roots = zeros(1, n);
for k = 1 : n
roots(k) = exp(2 * k* pi * 1i / n);
end
%% Move line
% Pair of roots the line should connect
point_1 = roots(2);
point_2 = roots(6);
% Coordinates of pair of roots
x1 = real(point_1);
x2 = real(point_2);
y1 = imag(point_1);
y2 = imag(point_2);
d = sqrt((x1-x2)^2+(y1-y2)^2); % Euclidean distance between pair of roots
m = (y1 - y2) / (x1 - x2); % Gradient of line connecting pair of roots
c = y1 - m * x1; % y-intercept of line
int = -c / m; % x-coordinate that the rotation should occur at
shift = [int; 0];
x = linspace(-1, 1, 10); % Initial line lying purely on x-axis
y = 0 * x;
v = [x; y];
theta = atan((y2-shift(2))/(x2-shift(1))); % Angle by which to rotate
rot = [cos(theta), -sin(theta); sin(theta), cos(theta)]; % Rotation matrix
u = v * (d / 2); % Scale initial line
if m < 1e-3 % Horizontal case
shift = [0; 0];
end
w = (rot * (u - shift)) + shift; % Apply rotation
% Another shift that seems necessary
% This is definitely a problematic section
shift_x = w(1, 1) - x2;
shift_y = w(2, 1) - y2;
shift_2 = [shift_x; shift_y];
w = w - shift_2;
%% Plot
fig = figure;
fig.Units = 'inches';
fig.Position = [1, 1, 9, 9];
ax = gca;
tt = title(ax, 'Title');
tt.FontWeight = 'bold';
tt.FontSize = 20;
st = subtitle(ax, sprintf('More text here'));
st.FontAngle = 'italic';
st.FontSize = 15;
hold on
hCircle = viscircles([0, 0], 1, 'Color', 'k');
for i = 1 : n
x_point = real(roots(i));
y_point = imag(roots(i));
hPin = plot(x_point, y_point, 'Marker', 'o', 'MarkerSize', 20, 'MarkerfaceColor', 'red', ...
'MarkerEdgeColor', 'black');
end
% Plot original and shifted line, split into colours so direction is easier to see
plot(v(1,1:4), v(2,1:4), 'b');
plot(v(1,4:7), v(2,4:7), 'r');
plot(v(1,7:end), v(2,7:end), 'g');
plot(w(1,1:4), w(2,1:4), 'b');
plot(w(1,4:7), w(2,4:7), 'r');
plot(w(1,7:end), w(2,7:end), 'g');
For example, keeping point_1 = roots(2); and changing only point_2 = roots(p); works as intended for only p=3, 4, 6, 7, 8.
Any guidance on how to get this working would be greatly appreciated, thanks!
Edit:
To give some more details, basically I have an array of numbers between 0 and 1 (rather than just a line) which I want to plot on the line that would connect two roots. E.g. if my array is x=[0.2, 0.5, 0.9], then I want three points between point_1 and point_2, the first being 0.2d down the connecting line away from point_1, the second 0.5d (i.e. halfway), and the final being 0.9d away.
First of all, since the points you want to connect are complex numbers, it is easier to work with complex coordinate directly.
Roots of unity
I have simplified your code a bit.
Some may raise a flag on naming a variable roots since roots is a built-in matlab function. I am fine with it, as long as the usage does not cause any confusion, namely, don't use roots as a variable and as a function in the same context.
As matlab provides so many built-in functions, it is impossible to avoid name collision unless one knows them all by heart or searches before naming every single variable.
n = 10;
k = 1:n;
roots = exp(2 * k * pi * 1i / n);
Scaling, rotating, and translating
% Pair of roots the line should connect
point_1 = roots(2);
point_2 = roots(6);
d = abs(point_2 - point_1); % distance between pair of roots
theta = angle(point_2 - point_1); % rotation angle
p_on_line = linspace(-1, 1, 10); % Initial line lying on x-axis
p_on_line = p_on_line * d/2; % scale
p_on_line = p_on_line * exp(1i*theta); % rotate
p_on_line = p_on_line + (point_1 - p_on_line(1)); % translate
Plot
I added some scatter points and removed irrevelant parts (e.g. title, fonts).
fig = figure;
fig.Units = 'inches';
fig.Position = [1, 1, 9, 9];
hold on
hCircle = viscircles([0, 0], 1, 'Color', 'k');
hPin = plot(roots, 'o', 'MarkerSize', 20, 'MarkerfaceColor', 'red', ...
'MarkerEdgeColor', 'black');
% Plot line connecting roots
plot(p_on_line(1:4), 'b.-', 'MarkerSize', 20);
plot(p_on_line(4:7), 'r.-', 'MarkerSize', 20);
plot(p_on_line(7:end), 'g.-', 'MarkerSize', 20);
% Plot original line
original_x = linspace(-1, 1, 10);
original_y = zeros(1, 10);
plot(original_x(1:4), original_y(1:4), 'b.-', 'MarkerSize', 20);
plot(original_x(4:7), original_y(4:7), 'r.-', 'MarkerSize', 20);
plot(original_x(7:end), original_y(7:end), 'g.-', 'MarkerSize', 20);
hold off
This should work for all combinations of root pairs.

Random Points in an n-Dimensional Hypersphere

This Matlab code,
creates a set of random points defined by Cartesian coordinates and
uniformly distributed over the interior of an n-dimensional
hypersphere of radius r with center at the origin.
the source is here.
clear all
clc
m = 20000;
n = 2;
r = 2;
%// generate circle boundary
C = [3 4]; %// center [x y]
t = linspace(0, 2*pi, 100);
x = r*cos(t) + C(1);
y = r*sin(t) + C(2);
C_rep = repmat( C,m,1);
X = randn(m,n);
s2 = sum(X.^2,2);
X = X.*repmat(r*(rand(m,1).^(1/n))./sqrt(s2),1,n)+ C_rep;
%% Plot
figure(1), clf
plot(x,y,'b')
hold on
plot(C(1),C(2),'r.', 'MarkerSize', 50) % center point
hold on
plot(X(:,1),X(:,2),'g.','markersize',2);
axis equal;zoom off; zoom on;drawnow;shg;
ax = axis;
This is the output:
which is not what I want.
How to make the points distributed around a center point C?
When n = 2, 3, 4, k dimentions
What does s2 mean?

Optim-nonlinear equation in matlab code

I updated the question to clarify it more. Here is a graph:
For the curve in the attached photo, I hope to draw the curve. I have its equation and it is after simplification will be like this one
% Eq-2
(b*Y* cos(v) + c - k*X*sin(v))^2 + ...
sqrt(k*X*(cos(v) + 1.0) + b*Y*sin(v))^2) - d = 0.0
Where:
v = atan((2.0*Y)/X) + c
and b, c, d and k are constants.
from the attached graph,
The curve is identified in two points:
p1 # (x=0)
p2 # (y=0)
I a new on coding so accept my apologize if my question is not clear.
Thanks
So, after your edit, it is a bit more clear what you want.
I insist that your equation needs work -- the original equation (before your edit) simplified to what I have below. The curve for that looks like your plot, except the X and Y intercepts are at different locations, and funky stuff happens near X = 0 because you have numerical problems with the tangent (you might want to reformulate the problem).
But, after checking your equation, the following code should be helpful:
function solve_for_F()
% graininess of alpha
N = 100;
% Find solutions for all alphae
X = zeros(1,N);
options = optimset('Display', 'off');
alpha = linspace(0, pi/2, N);
x0 = linspace(6, 0, N);
for ii = 1:numel(alpha)
X(ii) = fzero(#(x)F(x, alpha(ii)), x0(ii), options);
end
% Convert and make an X-Y plot
Y = X .* tan(alpha);
plot(X, Y,...
'linewidth', 2,...
'color', [1 0.65 0]);
end
function fval = F(X, alpha)
Y = X*tan(alpha);
% Please, SIMPLIFY in the future
A = 1247745517111813/562949953421312;
B = 4243112111277797/4503599627370496;
V = atan2(2*Y,X) + A;
eq2 = sqrt( (5/33*( Y*sin(V) + X/2*(cos(V) + 1) ))^2 + ...
(5/33*( Y*cos(V) - X/2* sin(V) ))^2 ) - B;
fval = eq2;
end
Results:
So, I was having fun with this (thanks for that)!
Different question, different answer.
The solution below first searches for the constants causing the X and Y intercepts you were looking for (p1 and p2). For those constants that best fit the problem, it makes a plot, taking into account numerical issues.
In fact, you don't need eq. 1, because that's true always for any curve -- it's just there to confuse you, and problematic to use.
So, here it is:
function C = solve_for_F()
% Points of interest
px = 6;
py = 4.2;
% Wrapper function; search for those constants
% causing the correct X,Y intercepts (at px, py)
G = #(C) abs(F( 0, px, C)) + ... % X intercept at px
abs(F(py, 0, C)); % Y intercept at py
% Initial estimate, based on your original equation
C0 = [5/33
1247745517111813/562949953421312
4243112111277797/4503599627370496
5/66];
% Minimize the error in G by optimizing those constants
C = fminsearch(G, C0);
% Plot the solutions
plot_XY(px, py, C);
end
function plot_XY(xmax,ymax, C)
% graininess of X
N = 100;
% Find solutions for all alphae
Y = zeros(1,N);
X = linspace(0, xmax, N);
y0 = linspace(ymax, 0, N);
options = optimset('Display', 'off',...,...
'TolX' , 1e-10);
% Solve the nonlinear equation for each X
for ii = 1:numel(X)
% Wrapper function for fzero()
fcn1 = #(y)F(y, X(ii), C);
% fzero() is probably the fastest and most intuitive
% solver for this problem
[Y(ii),~,flag] = fzero(fcn1, y0(ii), options);
% However, it uses an algorithm that easily diverges
% when the function slope is large. For those cases,
% solve with fminsearch()
if flag ~= 1
% In this case, the minimum of the absolute value
% is searched for (which should be zero)
fcn2 = #(y) abs(fcn1(y));
Y(ii) = fminsearch(fcn2, y0(ii), options);
end
end
% Now plot the X,Y solutions
plot(X, Y,...
'linewidth', 2,...
'color', [1 0.65 0]);
xlabel('X'), ylabel('Y')
axis([0 xmax+.1 0 ymax+.1])
end
function fval = F(Y, X, C)
% Unpack constants
b = C(1); d = C(3);
c = C(2); k = C(4);
% pre-work
V = atan2(2*Y, X) + c;
% Eq. 2
fval = sqrt( (b*Y*sin(V) + k*X*(cos(V) + 1))^2 + ...
(b*Y*cos(V) - k*X* sin(V) )^2 ) - d;
end

How to vectorize a pair-wise point inside rectangle (bounding box) check?

P is an m*2 matrix of m points([X Y]), and R is an n*4 matrix of n rectangles ([X1 Y1 X2 Y2]). I want to form an m*n matrix, C, in a way that C(i, j) indicates if i-th point in P lies inside j-th rectangle in R.
Single point single rect:
This is the way I check if the point lies inside the rectangle:
c = (P(1)>=R(1))&(P(1)<=R(3))&(P(2)>=R(2))&(P(2)<=R(4));
for more readability:
c = (Px>=Rxmin)&(Px<=Rxmax))&(Py>=Rymin)&(Py<=Rymax);
In the code above I’m sure that R(1)<=R(3) and R(2)<=R(4):
R(:, [1 3]) = sort(R(:, [1 3]), 2);
R(:, [2 4]) = sort(R(:, [2 4]), 2);
Multiple points multiple rects:
It's easy to mange m*1 and 1*n cases (like second answer here). But I don't know how to vectorize m*n case. I’m currently doing it using for loops:
m = size(P, 1);
n = size(R, 1);
C = false(m, n);
for i=1:n
C(:, i) = (P(:, 1)>=R(i, 1))&(P(:, 1)<=R(i, 3))& ...
(P(:, 2)>=R(i, 2))&(P(:, 2)<=R(i, 4));
end
How can I vectorize it for more efficiency?
I suggest you work with each column of P and R individually and then bitwise & them at the end:
Px = P(:,1);
Rx1 = R(:,1).';
Rx2 = R(:,3).';
X1 = bsxfun(#ge, Px, Rx1);
X2 = bsxfun(#le, Px, Rx2);
Py = P(:,2);
Ry1 = R(:,2).';
Ry2 = R(:,4).';
Y1 = bsxfun(#ge, Py, Ry1);
Y2 = bsxfun(#le, Py, Ry2);
C = X1 & X2 & Y1 & Y2
Or if you prefer:
C = bsxfun(#ge, P(:,1), R(:,1).')& ... % X lower bound
bsxfun(#le, P(:,1), R(:,3).')& ... % X upper bound
bsxfun(#ge, P(:,2), R(:,2).')& ... % Y lower bound
bsxfun(#le, P(:,2), R(:,4).'); % Y upper bound
OP has provided this timing comparison

Connecting two points in multidimensional matrix

I'm trying to create a 3d matrix of 1's and 0's. I want to connect 2 points together (shortest distance) by forming a line of 1's in between them.
It would look something like this, but in 3d
path_pixels = [0,0,1,0,0,0,0,0,0,0,0,0,0,0,0;
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0;
0,0,0,0,1,0,0,0,0,0,0,0,0,0,0];
I'm able to do it in 2d using this code
clc;
clear;
%matrix size
A = zeros(70);
A(20,20) = 1; %arbitrary point
B = zeros(70);
B(40,40) = 1; %arbitrary point
D1 = bwdist(A);
D2 = bwdist(B);
D_sum = D1 + D2 ;
path_pixels = imregionalmin(D_sum);
spy(path_pixels)
How can I expand this method to 3d?
It depends what you mean by "connect", exactly. But, if the goal is a one-cell wide region between starting and end points, then that looks a lot like a "one-pixel wide" line, which could be defined as follows.
% start and end point of line
a = [1 10 2];
b = [4 1 9];
% get diffs
ab = b - a;
% find number of steps required to be "one pixel wide" in the shorter
% two dimensions
n = max(abs(ab)) + 1;
% compute line
s = repmat(linspace(0, 1, n)', 1, 3);
for d = 1:3
s(:, d) = s(:, d) * ab(d) + a(d);
end
% round to nearest pixel
s = round(s);
% if desired, apply to a matrix
N = 10;
X = zeros(N, N, N);
X(sub2ind(size(X), s(:, 1), s(:, 2), s(:, 3))) = 1;
% or, plot
clf
plot3(s(:, 1), s(:, 2), s(:, 3), 'r.-')
axis(N * [0 1 0 1 0 1])
grid on
Please forgive ugly code, I did this in a rush ;).