function [path,vlist,plist] = compute_geodesic_mesh(D, vertex, face, x, options)
% compute_geodesic_mesh - extract a discrete geodesic on a mesh
%
% [path,vlist,plist] = compute_geodesic_mesh(D, vertex, face, x, options);
%
% D is the set of geodesic distances.
%
% path is a 3D curve that is the shortest path starting at x.
% You can force to use a fully discrete descent using
% options.method='discrete'.
%
% Copyright (c) 2007 Gabriel Peyre
options.null = 0;
verb = getoptions(options, 'verb', 1);
if length(x)>1
path = {}; vlist = {}; plist = {};
for i=1:length(x)
if length(x)>5
if verb
progressbar(i,length(x));
end
end
[path{i},vlist{i},plist{i}] = compute_geodesic_mesh(D, vertex, face, x(i), options);
end
return;
end
method = getoptions(options, 'method', 'continuous');
[vertex,face] = check_face_vertex(vertex,face);
if strcmp(method, 'discrete')
if isfield(options, 'v2v')
vring = options.v2v;
else
vring = compute_vertex_ring(face);
end
% path purely on edges
vlist = x;
vprev = D(x);
while true
x0 = vlist(end);
r = vring{x0};
[v,J] = min(D(r));
x = r(J);
if v>=vprev || v==0
break;
end
vprev = v;
vlist(end+1) = x;
end
path = vertex(:,vlist);
plist = vlist*0+1;
return;
end
%%% gradient descent on edges
% retrieve adjacency lists
m = size(face,2); n = size(vertex,2);
% precompute the adjacency datasets
if isfield(options, 'e2f')
e2f = options.e2f;
else
e2f = compute_edge_face_ring(face);
end
if isfield(options, 'v2v')
v2v = options.v2v;
else
v2v = compute_vertex_ring(face);
end
% initialize the paths
[w,f] = vertex_stepping(face, x, e2f, v2v, D);
vlist = [x;w];
plist = [1];
Dprev = D(x);
while true;
% current triangle
i = vlist(1,end);
j = vlist(2,end);
k = get_vertex_face(face(:,f),i,j);
a = D(i); b = D(j); c = D(k);
% adjacent faces
f1 = get_face_face(e2f, f, i,k);
f2 = get_face_face(e2f, f, j,k);
% compute gradient in local coordinates
x = plist(end); y = 1-x;
gr = [a-c;b-c];
% twist the gradient
u = vertex(:,i) - vertex(:,k);
v = vertex(:,j) - vertex(:,k);
A = [u v]; A = (A'*A)^(-1);
gr = A*gr;
nx = gr(1); ny = gr(2);
% compute intersection point
etas = -y/ny;
etat = -x/nx;
s = x + etas*nx;
t = y + etat*ny;
if etas<0 && s>=0 && s<=1 && f1>0
%%% CASE 1 %%%
plist(end+1) = s;
vlist(:,end+1) = [i k];
% next face
f = f1;
elseif etat<0 && t>=0 && t<=1 && f2>0
%%% CASE 2 %%%
plist(end+1) = t;
vlist(:,end+1) = [j k];
% next face
f = f2;
else
%%% CASE 3 %%%
if a<=b
z = i;
else
z = j;
end
[w,f] = vertex_stepping( face, z, e2f, v2v, D);
vlist(:,end+1) = [z w];
plist(end+1) = 1;
end
Dnew = D(vlist(1,end))*plist(end) + D(vlist(2,end))*(1-plist(end));
if Dnew==0 || (Dprev==Dnew && length(plist)>1)
break;
end
Dprev=Dnew;
end
v1 = vertex(:,vlist(1,:));
v2 = vertex(:,vlist(2,:));
path = v1.*repmat(plist, [3 1]) + v2.*repmat(1-plist, [3 1]);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [w,f] = vertex_stepping(face, v, e2f, v2v, D)
% adjacent vertex with minimum distance
[tmp,I] = min( D(v2v{v}) ); w = v2v{v}(I);
f1 = e2f(v,w);
f2 = e2f(w,v);
if f1<0
f = f2; return;
end
if f2<0
f = f1; return;
end
z1 = get_vertex_face(face(:,f1),v,w);
z2 = get_vertex_face(face(:,f2),v,w);
if D(z1)<D(z2);
f = f1;
else
f = f2;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function k = get_vertex_face(f,v1,v2)
if nargin==2
v2 = v1(2); v1 = v1(1);
end
k = setdiff(f, [v1 v2]);
if length(k)~=1
error('Error in get_vertex_face');
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function f = get_face_face(e2f, f, i,j)
f1 = e2f(i,j); f2 = e2f(j,i);
if f==f1
f = f2;
else
f = f1;
end