function [nodes2, faces2] = clipConvexPolyhedronHP(nodes, faces, plane)
%CLIPCONVEXPOLYHEDRONHP Clip a convex polyhedron by a plane
%
%   [NODES2, FACES2] = clipConvexPolyhedronHP(NODES, FACES, PLANE)
%
%   return the new (convex) polyhedron whose vertices are 'below' the
%   specified plane, and with faces clipped accordingly. NODES2 contains
%   clipped vertices and new created vertices, FACES2 contains references
%   to NODES2 vertices.
%
%   Example
%   [N E F] = createCube;
%   P = createPlane([.5 .5 .5], [1 1 1]);
%   [N2 F2] = clipConvexPolyhedronHP(N, F, P);
%   drawPolyhedra(N2, F2);
%
%   See also
%   meshes3d, polyhedra, planes3d
%

% ------
% Author: David Legland
% e-mail: david.legland@nantes.inra.fr
% Created: 2007-09-14,    using Matlab 7.4.0.287 (R2007a)
% Copyright 2007 INRA - BIA PV Nantes - MIAJ Jouy-en-Josas.


%% Preprocessing

% used for identifying identical vertices
tol = 1e-10;

% if faces is a numeric array, convert it to cell array
if isnumeric(faces)
    faces2 = cell(size(faces, 1), 1);
    for f = 1:length(faces2)
        faces2{f} = faces(f,:);
    end
    faces = faces2;
end

% find vertices below the plane
b = isBelowPlane(nodes, plane);

% initialize results
Nn  = size(nodes, 1);
nodes2 = zeros(0, 3);   % list of new nodes
faces2 = faces;         % list of new faces. Start with initial list, and remove some of them


%% Main iteration on faces

% iterate on each face, and test if either:
%   - all points below plane -> keep all face
%   - all points up plane -> remove face
%   - both -> clip the polygon
keep = true(length(faces), 1);
for f = 1:length(faces)
    % current face
    face = faces{f};
    bf = b(face);

    % face totally above plane
    if sum(bf) == 0
        keep(f) = false;
        continue;
    end

    % face totally below plane
    if sum(bf == 1) == length(bf)
        continue;
    end

    % clip polygon formed by face
    poly = nodes(face, :);
    clipped = clipConvexPolygon3dHP(poly, plane);

    % identify indices of polygon vertices
    inds = zeros(1, size(clipped, 1));
    faceb = face(bf==1); % indices of vertices still in clipped face
    [minDists, I] = minDistancePoints(nodes(faceb,:), clipped); %#ok<ASGLU>
    for i = 1:length(I)
        inds(I(i)) = faceb(i);
    end

    % indices of new points in clipped polygon
    indNews = find(inds == 0);
    
    if size(nodes2, 1) < 2
        nodes2 = [nodes2; clipped(indNews, :)]; %#ok<AGROW>
        inds(indNews(1)) = Nn + 1;
        inds(indNews(2)) = Nn + 2;
        faces2{f} = inds;
        continue;
    end
     
    % distances from new vertices to already added vertices
    [minDists, I] = minDistancePoints(clipped(indNews, :), nodes2);
    
    % compute index of first vertex
    if minDists(1) < tol
        inds(indNews(1)) = Nn + I(1);
    else
        nodes2 = [nodes2; clipped(indNews(1), :)]; %#ok<AGROW>
        inds(indNews(1)) = Nn + size(nodes2, 1);
    end
    
    % compute index of second vertex
    if minDists(2) < tol
        inds(indNews(2)) = Nn + I(2);
    else
        nodes2 = [nodes2; clipped(indNews(2), :)]; %#ok<AGROW>
        inds(indNews(2)) = Nn + size(nodes2, 1);
    end
    
    % stores the modified face
    faces2{f} = inds;
end


%% Postprocessing

% creates a new face formed by the added nodes
[tmp, I] = angleSort3d(nodes2); %#ok<ASGLU>
newFace = I' + Nn;

% remove faces outside plane and add the new face
faces2 = {faces2{keep}, newFace};

% remove clipped nodes, and add new nodes to list of nodes
N2 = size(nodes2, 1);
nodes2 = [nodes(b, :); nodes2];

% new nodes are inside half-space by definition
b = [b; ones(N2, 1)];

% create look up table between old indices and new indices
inds = zeros(size(nodes2, 1), 1);
indb = find(b);
for i = 1:length(indb)
    inds(indb(i)) = i;
end

% update indices of faces
for f = 1:length(faces2)
    face = faces2{f};
    faces2{f} = inds(face)';
end
