function [V,F,b,bc] = triangulate_curves(P,varargin)
  % TRIANGULATE_CURVES Construct a (properly slitted) mesh for a given set of curves and
  % determine corresponding boundary conditions for solving diffusion curves.
  % 
  % [V,F,b,bc] = triangulate_curves(P,varargin)
  %
  % Inputs:
  %    #curve by 1 list of lists of curve points
  %  Optional:
  %    'BoundingBox' followed by whether to include bounding box (otherwise
  %    there better be an outer loop.
  %
  % Example:
  %     clf;
  %     % get curve (user draws)
  %     Praw = {};
  %     while true
  %       [Prawc,p] = get_pencil_curve();
  %       if size(Prawc,1) == 1 || sum((max(Prawc)-min(Prawc)).^2,2)<1e-5
  %         fprintf('continuing\n');
  %         break;
  %       end
  %       Praw{end+1} = Prawc;
  %       %% remove plot
  %       %delete(p);
  %     end
  %     % Simplify
  %     P = cell(numel(Praw),1);
  %     for c = 1:numel(Praw)
  %       P{c} = dpsimplify(Praw{c},0.001);
  %     end
  %     [V,F,b,bc] = triangulate_curves(P);
  %     W = harmonic(V,F,b,bc);
  %     colors = random_color(4*numel(P));
  %     RGB = W * colors;
  %     tsurf(F,[V W(:,4)],'FaceVertexCData',clamp(RGB),fphong,'EdgeColor','none');axis equal;view(2)
  %

  % default values
  use_bounding_box = true;
  % Map of parameter names to variable names
  params_to_variables = containers.Map( ...
    {'BoundingBox'}, ...
    {'use_bounding_box'});
  v = 1;
  while v <= numel(varargin)
    param_name = varargin{v};
    if isKey(params_to_variables,param_name)
      assert(v+1<=numel(varargin));
      v = v+1;
      % Trick: use feval on anonymous function to use assignin to this workspace 
      feval(@()assignin('caller',params_to_variables(param_name),varargin{v}));
    else
      error('Unsupported parameter: %s',varargin{v});
    end
    v=v+1;
  end

  E = [];
  % list of all points
  PP = [];
  % list of curve sizes to determine from boundary markers which curve edge came
  % from
  curve_pos_ind = 0;
  for c = 1:numel(P)
    if isstruct(P{c})
      Pc = P{c}.P;
      Ec = P{c}.E;
    else
      Pc = P{c};
      Ec = [1:size(Pc,1)-1;2:size(Pc,1)]';
    end
    % inefficient appending
    curve_pos_ind = [curve_pos_ind curve_pos_ind(end)+size(Ec,1)];
    E = [E;size(PP,1)+Ec];
    PP = [PP;Pc];
  end
  l = normrow(PP(E(:,1),:)-PP(E(:,2),:));

  [PP,~,J] = remove_duplicate_vertices(PP,0);
  E = J(E);
  Facets = [];
  Facets.facets = mat2cell(E,ones(size(E,1),1),2);
  Facets.boundary_marker = (1:size(E,1))'+1;

  % need unique set of points
  %assert(size(PP,1) == size(unique(PP,'rows'),1));

  max_area = 1e16;min(l)^2;
  for pass = 1:2
    prefix = tempprefix();
    if use_bounding_box
      BB = bounding_box(PP);
      BB = bsxfun(@plus,1.1*bsxfun(@minus,BB,mean(BB)),mean(BB));
      bb_flag = 'c';
    else 
      BB = [];
      bb_flag = '';
    end
    writePOLY_triangle([prefix '.poly'],[PP;BB],Facets,[]);
    % Careful, triangle does not understand scientific notation
    flags = sprintf('-q30pa%0.17f%s',max_area,bb_flag);
    %flags = '-cp';
    command = [path_to_triangle ' ' flags ' ' prefix];
    command
    [status, result] = system( command );
    if(status ~= 0)
       error(result);
    end
    [V,I] = readNODE([prefix '.1.node']);
    F = readELE([prefix '.1.ele']);
    max_area = mean(doublearea(V,F))*2;
  end
  [~,BE,BM] = readPOLY_triangle([prefix '.1.poly']);
  BE = BE(BM>1,:);
  BM = BM(BM>1,:)-1;
  % Re-orient edges to follow original curve
  BEdotE = sum((V(BE(:,1),:)-V(BE(:,2),:)).*(PP(E(BM,1),:)-PP(E(BM,2),:)),2);
  BE(BEdotE<=0,:) = fliplr(BE(BEdotE<=0,:));
  uBE = unique(sort(BE,2),'rows');

  % boundary conditions (to be thinned)
  % #V by #curves * 2 (left/right) * 2 (source-to-dest/dest-to-source)
  bc = sparse(size(V,1),(numel(curve_pos_ind)-1)*2*2);

  paths = [];
  for c = 1:numel(P)
    % Boundary edges of this curve
    BEp = BE(BM>curve_pos_ind(c) & BM<=curve_pos_ind(c+1),:);
    BMp = BM(BM>curve_pos_ind(c) & BM<=curve_pos_ind(c+1));
    % Assumes E and thus BM are in order 
    % source of curve
    sc = E(curve_pos_ind(c)+1,1);
    path = [sc];
    % loop over original curve
    for e = (curve_pos_ind(c)+1):curve_pos_ind(c+1)
      % dest of last original edge is source of next
      se = path(end);
      assert(se == E(e,1));
      de = E(e,2);
      BEe = BEp(BMp==e,:);
      A = adjacency_matrix(BEe);
      [~,pathe] = graphshortestpath(A,se,de);
      path = [path pathe(2:end)];
    end
    % Arc length parameterization of path
    path_edge_lens = sqrt(sum((V(path(2:end),:)-V(path(1:end-1),:)).^2,2));
    path_t = [0;cumsum(path_edge_lens)./sum(path_edge_lens)];

    bc(path,(c-1)*2*2+([1 3])) = [path_t path_t];
    bc(path,(c-1)*2*2+([2 4])) = 1-[path_t path_t];
    % inefficient append
    paths = [paths path];
  end

  % remove intersection points
  [upaths,~,IC] = unique(paths);
  counts = histc(paths,upaths);
  bc(upaths(counts>1),:) = 0;

  % disconnected mesh with each corner as distinct vertex
  W = V(F(:),:);
  m = size(F,1);
  G = bsxfun(@plus,[0 m 2*m],[1:m]');
  % remap boundary conditions to new disconnected mesh
  bc = bc(F(:),:);

  % boundary
  left = reshape( ...
    ismember([F(:,[2 3]);F(:,[3 1]);F(:,[1 2])],BE,'rows'),m,3);
  right = reshape( ...
    ismember([F(:,[2 3]);F(:,[3 1]);F(:,[1 2])],fliplr(BE),'rows'),m,3);
  [touch,touchloc] = ismember(F,BE(:));
  touch = reshape(touch,m,3);
  % definitely left or right
  b_left  = G(  left(:,[2 3 1])| left(:,[3 1 2]));
  b_right = G(right(:,[2 3 1])|right(:,[3 1 2]));
  b_left = b_left(:);
  b_right = b_right(:);


  %Z = repmat(1:size(G,1),1,3)';
  %tsurf(G,[W Z],'FaceColor','g');
  %hold on;plot(P(:,1),P(:,2),'-o','LineWidth',4); hold off;
  %hold on;plot_edges(V,BE,'--r','LineWidth',4); hold off;
  %hold on; plot3(W(b_right,1),W(b_right,2),Z(b_right,1),'or','LineWidth',4);hold off
  %hold on; plot3(W(b_left,1),W(b_left,2),Z(b_left,1),'ob','Linewidth',2);hold off
  %error


  % vertex to face incidence
  %V2F = sparse( ...
  %  F(:), ...
  %  repmat(1:size(F,1),1,3)', ...
  %  [ones(size(F,1),1); 2*ones(size(F,1),1); 3*ones(size(F,1),1)], ...
  %  size(V,1),size(F,1));
  % Finding on the transpose is faster
  V2FT = sparse( ...
    repmat(1:size(F,1),1,3)', ...
    F(:), ...
    [ones(size(F,1),1); 2*ones(size(F,1),1); 3*ones(size(F,1),1)], ...
    size(F,1), ...
    size(V,1));

  %J = 1:size(W,1);
  W2V = sparse((1:size(F,1)*3)',F(:),1,size(W,1),size(V,1));
  % http://stackoverflow.com/a/15719703/148668
  [~,first] = max((W2V*W2V')~=0,[],2);
  assert(numel(first)==size(W,1));
  assert(all(first));
  % default is to map to first occurrence
  J = first;

  VB = unique(BE)';
  % loop over original vertices on curves
  for v = VB
    % Faces incident on v and index of corner in face
    %[~,IFv,CFv] = find(V2F(v,:));
    [IFv,~,CFv] = find(V2FT(:,v));
    Fv = F(IFv,:);
    [~,uE2Fv,uE] = edge_adjacency_matrix(Fv);
    isBE = ismember(uE,uBE,'rows');
    A = uE2Fv(~isBE,:)' * uE2Fv(~isBE,:);
    [~,C] = conncomp(A);
    % loop over components
    for c = 1:max(C)
      min_fv = find(C==c,1);
      % first index in w for this component
      min_w = G(IFv(min_fv),CFv(min_fv));
      % map all intances in this component to first 
      J(G(sub2ind(size(G),IFv(C==c),CFv(C==c)))) = min_w;
    end
  end

  % remap all at once
  G = J(G);

  b_left = unique(J(b_left)');
  b_right = unique(J(b_right)');
  [W,I] = remove_unreferenced(W,G);
  % Remap boundary conditions
  bc(I,:) = bc;
  % Only keep referenced
  bc = bc(1:size(W,1),:);
  % Remap faces
  G = I(G);
  % remap left/right
  b_left = I(b_left);
  b_right = I(b_right);

  % Let shared vertices---internal curve endpoints---"float"
  % But really this should be left up to implementation
  b_shared = intersect(b_left,b_right); 
  b_left = setdiff(b_left,b_shared);
  b_right = setdiff(b_right,b_shared);
  % zap left, right and shared accordingly
  bc(b_right,[1:4:end 2:4:end]) = 0;
  bc(b_left,[3:4:end 4:4:end]) = 0;
  bc(b_shared,:) = 0;

  has_b = any(bc,2);
  b = find(has_b);
  bc = bc(b,:);

  % rename
  V = W;
  F = G;

end
