diff --git a/BezierExample.m b/BezierExample.m new file mode 100644 index 0000000..d4e7f22 --- /dev/null +++ b/BezierExample.m @@ -0,0 +1,95 @@ +% Bezier example + +% This goes in a file named BezierExample.m + +close all +clear all + +x_nodes = []; +y_nodes = []; + +% Initialize figure window + +figure +hold on +xlim([0 1]) +ylim([0 1]) + +% Collect nodes + +disp('Click several points on curve. Hit enter to exit') +index = 1; +while(true) + [x,y,button] = ginput(1); + + if (isempty(x)) + break + end + + if (button ~= 1) + continue + end + + % Add the new point and plot the new line segment + + x_nodes(index) = x(1); + y_nodes(index) = y(1); + if (index > 1) + plot(x_nodes(index-1:index),y_nodes(index-1:index)) + end + + index = index + 1; +end + +% Points have been entered. Build the collection of nodes and control +% points that define the curve + +number_curves = length(x_nodes) - 1; +bezier_points = cell(number_curves,4); +for k = 1:number_curves + + % Insert the nodes + + bezier_points{k,1} = [x_nodes(k) y_nodes(k)]; + bezier_points{k,4} = [x_nodes(k+1) y_nodes(k+1)]; + + % Create the control points + + if (k == 1) + bezier_points{k,2} = [x_nodes(1) y_nodes(1)] + ([x_nodes(2) y_nodes(2)] - [x_nodes(1) y_nodes(1)])/3; + else + + % Compute positions for control points. Arbitrarily, they will be + % collinear with the node in between and 1/3 of the way to the next + % node + + vector_1 = [x_nodes(k) y_nodes(k)] - [x_nodes(k-1) y_nodes(k-1)]; + length_1 = norm(vector_1); + vector_1 = vector_1/length_1; + + vector_2 = [x_nodes(k) y_nodes(k)] - [x_nodes(k+1) y_nodes(k+1)]; + length_2 = norm(vector_2); + vector_2 = vector_2/length_2; + + vector_sum = vector_1 + vector_2; + n = vector_sum/norm(vector_sum); + + % If vector_1 X vector_2 is negative, reverse the direction of n + + if ((vector_1(1)*vector_2(2) - vector_2(1)*vector_1(2)) < 0) + n = -n; + end + n_p = [-n(2) n(1)]; + + bezier_points{k-1,3} = [x_nodes(k) y_nodes(k)] + n_p*length_1/3; + bezier_points{k,2} = [x_nodes(k) y_nodes(k)] - n_p*length_2/3; + + if (k == (number_curves)) + + bezier_points{k,3} = [x_nodes(number_curves+1) y_nodes(number_curves+1)] + ... + ([x_nodes(number_curves) y_nodes(number_curves)] - [x_nodes(number_curves+1) y_nodes(number_curves+1)])/3; + end + end +end + +PlotBezier(bezier_points) diff --git a/PlotBezier.m b/PlotBezier.m new file mode 100644 index 0000000..4a8c5cc --- /dev/null +++ b/PlotBezier.m @@ -0,0 +1,184 @@ +function PlotBezier( bezier_points ) +% Display the Bezier curve + +% This goes in a file named PlotBezier.m + +% Clear the children of the current axes + +cla + +% Define the array of parametervectors as a column vector, so calculation +% of points on the Bezier curve can be done in one statement + +t = (0:0.05:1)'; + +number_segments = size(bezier_points,1); +for k = 1:number_segments + P0 = bezier_points{k,1}; + P1 = bezier_points{k,2}; + P2 = bezier_points{k,3}; + P3 = bezier_points{k,4}; + + % Compute the points on this curve segment and plot them + + bezier = ((1 - t).^3)*P0 + (3*t.*(1 - t).^2)*P1 + (3*t.^2.*(1 - t))*P2 + t.^3*P3; + plot(bezier(:,1),bezier(:,2)) + hold on + + % Plot dotted lines first so they will be behind the nodes and control + % points and thus not intercept the clicks on the nodes and control + % points + + plot([P0(1) P1(1)],[P0(2) P1(2)],':') + plot([P2(1) P3(1)],[P2(2) P3(2)],':') + + % Plot the nodes and control points + + plot(P0(1),P0(2),'o','MarkerFaceColor','k','ButtonDownFcn',@ClickMarker) + + plot(P1(1),P1(2),'o','ButtonDownFcn',@ClickMarker) + plot(P2(1),P2(2),'o','ButtonDownFcn',@ClickMarker) +end + +% Plot the last node + +plot(P3(1),P3(2),'o','MarkerFaceColor','k','ButtonDownFcn',@ClickMarker) + +% Set the limits + +xlim([0 1]) +ylim([0 1]) +function ClickMarker(source,event) + set(ancestor(source,'figure'),'WindowButtonMotionFcn',{@DragMarker,source}) + set(ancestor(source,'figure'),'WindowButtonupFcn',@StopDragging) +end + +function DragMarker(figure,event,source) + +% Get current axes and coordinates of the screen point + +h1=gca; +coordinates=get(h1,'currentpoint'); +coordinates = coordinates(1,1:2); + +% Find the closest node or control point. This code is simplistic in the +% sense that it always takes the nearest node or control point. If a node +% or control point is dragged near another node or control point, then the +% other one will be moved. + +segment_index = -1; +node_control_index = -1; +distance = Inf; +for m = 1:size(bezier_points,1) + for n = 1:4 + if (isinf(distance)) + distance = norm(bezier_points{m,n}-coordinates); + segment_index = m; + node_control_index = n; + else + new_distance = norm(bezier_points{m,n}-coordinates); + if (new_distance < distance) + distance = new_distance; + segment_index = m; + node_control_index = n; + end + end + end +end + +% Modify the nodes and control points + +old_point = bezier_points{segment_index,node_control_index}; +bezier_points{segment_index,node_control_index} = coordinates; + +% For an intermediate node, the coodinates are stored twice. Update the +% other copy + +if (segment_index < number_segments && node_control_index == 4) + bezier_points{segment_index+1,1} = coordinates; +end + +if (1 < segment_index && node_control_index == 1) + bezier_points{segment_index-1,4} = coordinates; +end + +% Moving a node or control point causes movement of other control points. +% If a node is moved, the control points on either side need to be moved. If a +% control point is moved, its partner acros the node must be moved + +if (node_control_index == 1 || node_control_index == 4) + + % Node was moved. Move attached control points + + translation_vector = bezier_points{segment_index,node_control_index} - old_point; + + if (segment_index == 1 && node_control_index == 1) + bezier_points{segment_index,2} = bezier_points{segment_index,2} + translation_vector; + elseif (segment_index == number_segments && node_control_index == 4) + bezier_points{segment_index,3} = bezier_points{segment_index,3} + translation_vector; + else + if (node_control_index == 1) + bezier_points{segment_index,2} = bezier_points{segment_index,2} + translation_vector; + bezier_points{segment_index-1,3} = bezier_points{segment_index-1,3} + translation_vector; + else + bezier_points{segment_index+1,2} = bezier_points{segment_index+1,2} + translation_vector; + bezier_points{segment_index,3} = bezier_points{segment_index,3} + translation_vector; + end + end +else + + % Control point was moved. Rotate its partner + + if ((segment_index == 1 && node_control_index == 2) || (segment_index == number_segments && node_control_index == 3)) + + % No partner. Nothing else to do + + else + new_point = bezier_points{segment_index,node_control_index}; + if (node_control_index == 2) + + % Rotate previous control point + + rotation_center = bezier_points{segment_index,1}; + other_control_point = bezier_points{segment_index-1,3}; + else + + % Rotate next control point + + rotation_center = bezier_points{segment_index,4}; + other_control_point = bezier_points{segment_index+1,2}; + end + + new_vector = new_point - rotation_center; + new_vector = new_vector/norm(new_vector); + old_vector = old_point - rotation_center; + old_vector = old_vector/norm(old_vector); + + cos_theta = sum(old_vector.*new_vector); + sin_theta = old_vector(1)*new_vector(2) - new_vector(1)*old_vector(2); + rotation_matrix = [cos_theta -sin_theta;sin_theta cos_theta]; + + other_control_point = other_control_point - rotation_center; + + rotated_other_control_point = (rotation_matrix*(other_control_point'))' + rotation_center; + + if (node_control_index == 2) + bezier_points{segment_index-1,3} = rotated_other_control_point; + else + bezier_points{segment_index+1,2} = rotated_other_control_point; + end + + end +end + +% Plot the updated curve + +PlotBezier(bezier_points); + +end +function StopDragging(figure,event) + set(figure,'WindowButtonMotionFcn','') + set(figure,'WindowButtonUpFcn','') +end + +end \ No newline at end of file