classdef Cylinder < shape.Shape3D
%SHAPE.CYLINDER Creates a Cylinder on the X-Y-Z plane
% 
%   h = shape.Cylinder returns a cylinder shape centered at the origin
%   in 3D space.
%
%   h = shape.Cylinder('Property1', value1,'Property2', value2,...)
%   returns a cylinder shape, based on the specified properties.
%   Properties you do not specify retain their default values.
%
%   Cylinder properties:
%
%   Name        - Name of the cylinder shape
%   Center      - Cartesian coordinates of the center of the cylinder in
%                 meters specified as a 3-element vector. The default value
%                 is [0 0 0].
%   Radius      - Radius of the cylinder shape in meters. The default value is 1.
%   Height      - Height of the cylinder shape in meters . The default value is 2.
%   Cap         - Specifies the close status of top and bottom caps
%                 of cylinder. The default value is [1 1].
%   Metal       - Metal assigned for the shape. The default object is
%                 'PEC' with infinite conductivity and zero thickness.
%   Dielectric  - Dielectric assigned for the shape. The default object is
%                 'Air'.
%   Color       - Face Color of the shape. The default value is 'y'.
%   Transparency - Transparency of the shape. The default value is 1.
%   EdgeColor   - Edge Color of the shape. The default value is 'k'.
%
%   Cylinder methods:
%
%   add         - Boolean unite operation on two shapes
%   subtract    - Boolean subtraction operation on two shapes
%   intersect   - Boolean intersection operation on two shapes
%   plus        - r1 + r2, boolean unite, equivalent to add
%   minus       - r1 - r2, boolean subtraction, equivalent to subtract
%   and         - r1&r2, boolean intersection, equivalent to intersect
%   show        - Visualize the shape as a filled patch
%   mesh        - Compute/visualize the mesh of the shape
%   rotate      - Rotate the shape about an axis and an angle
%   rotateX     - Rotate the shape about the X-axis with an angle
%   rotateY     - Rotate the shape about the Y-axis with an angle
%   rotateZ     - Rotate the shape about the Z-axis with an angle
%   translate   - Translate the shape to a new location
%   scale       - Scale the size of the shape
%   imprintShape - Imprint a 2-D shape onto a custom 2-D or 3-D shape
%   createHole  - Create a hole of 2-D shape on custom 2-D or 3-D shape
%   extrude     - Extrude a 2d shape on custom shape
%   removeFaces - Remove the selected faces of shape interactively
%   addSubstrate - Add Substrate to the custom shape
%
%   % Examples
%
%   % Example 1: 
%   % Create a cylinder shape, rotate it by 45 degrees about
%   % the z-axis and visualize it.
%   
%   r = shape.Cylinder;
%   r = rotateZ(r,45);
%   show(r);
%
%   % Example 2: 
%   % Create a  cylinder shape, remove top and bottom caps and
%   %  visualize it.
%
%   r = shape.Cylinder(Cap = [0 0]);
%   show(r);
%
%   See also shape.OpenBox, shape.OpenCylinder

%   Copyright 2022-2023 The MathWorks, Inc.

    properties
        % Name of the cylinder
        Name
        % Center of the cylinder
        Center
        % Radius of the cylinder
        Radius
        % Height of the cylinder
        Height
        % Cap of the cylinder
        Cap
    end

    properties(Hidden)
        % Number of segments along the height of the cylinder
        NumSegments
    end
   
    methods
        function obj = Cylinder(varargin)
            parserObj = inputParser;
            addParameter(parserObj,'Name','myCylinder');
            addParameter(parserObj,'Center',[0,0,0]);
            addParameter(parserObj,'Radius',1);
            addParameter(parserObj,'Height',2);
            addParameter(parserObj,'Cap',[1 1]);
            addParameter(parserObj,'Metal','PEC');
            addParameter(parserObj,'Dielectric','Air');
            addParameter(parserObj,'Color','y');
            addParameter(parserObj,'Transparency',1);
            addParameter(parserObj,'EdgeColor','k');
            parse(parserObj,varargin{:});
            obj             = obj@shape.Shape3D();
            obj.Name        = parserObj.Results.Name;
            obj.Center      = parserObj.Results.Center;
            obj.Radius      = parserObj.Results.Radius;
            obj.Height   = parserObj.Results.Height;
            obj.Cap   = parserObj.Results.Cap;
            obj.Metal = parserObj.Results.Metal;
            obj.Dielectric = parserObj.Results.Dielectric;
            obj.Color = parserObj.Results.Color;
            obj.Transparency = parserObj.Results.Transparency;
            obj.EdgeColor = parserObj.Results.EdgeColor;       
            obj.NumSegments=1;
        end
    end

    methods
        function set.Name(obj,propVal)
            validateattributes(propVal,{'char','string'},{'nonempty',     ...
                },class(obj),'Name') ;
            if isPropertyChanged(obj,obj.Name,propVal)
                obj.Name = propVal;
            end
        end

        function set.Radius(obj,propVal)
            if isscalar(propVal)
                validateattributes(propVal,{'double'},{'nonempty','scalar',     ...
                    'nonnan','finite','real','positive'},class(obj),'Radius') ;
            else
                validateattributes(propVal,{'double'},{'nonempty','vector',     ...
                    'nonnan','finite','real','positive','numel',2},class(obj),'Radius') ;

            end
            if isPropertyChanged(obj,obj.Radius,propVal)
                obj.Radius = propVal;
            end
        end

        function set.Center(obj,propVal)
            validateattributes(propVal,{'double'},{'nonempty',      ...
                'nonnan','finite','real','numel',3},class(obj),'Center');
            if isPropertyChanged(obj,obj.Center,propVal)
                if ~isrow(propVal)
                    propVal = propVal';
                end
                obj.Center = propVal;
            end
        end

        function set.Height(obj,propVal)
            validateattributes(propVal,{'numeric'},{'nonempty','scalar',...
                'nonnan','finite'},class(obj),'Height');
            if isPropertyChanged(obj,obj.Height,propVal)
                obj.Height = propVal;
            end
        end

        function set.Cap(obj,propVal)
             %Validation
              validateattributes(propVal,{'numeric', 'logical'},...
                {'nonempty','nonnan','finite','real','binary','numel',2}...
                , class(obj), 'Cap');
              if isPropertyChanged(obj,obj.Cap,propVal)
                  obj.Cap = propVal;
              end
         end

    end

    methods(Hidden)
        function createGeometry(obj,varargin)
           % Number of points along the circumference of the cylinder
            N=20;
            if ~isrow(obj.Center)
                obj.Center = obj.Center';
            end
          
            % If radius is scalar,top and bottom caps are of same
            % radii, and if vector both are of different radii
            if isscalar(obj.Radius)
                cyl_mesh = Geometry.cylinder(obj.Height,...
                    obj.Radius,string(1),20,true);
                [pp,tt] = Geometry.extractMesh(cyl_mesh);
                tt = tt(:,1:3)+1;
            else
                scale = obj.Radius(2)/obj.Radius(1);
                cyl_mesh = Geometry.cylinder(obj.Height,obj.Radius(1),...
                    string(1),20,true);
                [pp,tt] = Geometry.extractMesh(cyl_mesh);
                pp = em.internal.translateshape(pp',[0 0 -obj.Height/2]);
                pp = pp';
                tt = tt(:,1:3)+1;
                id=find(pp(:,3)==max(pp(:,3)));
                for i=1:numel(id)
                    pp(id(i),1:2) =pp(id(i),1:2)*scale;
                end
                pp = pp';
            end
            t_tri = Geometry.meshToTriangulation(cyl_mesh);
            t_tri_id = faceNormal(t_tri)==[0 0 1];
            b_tri = Geometry.meshToTriangulation(cyl_mesh);
            b_tri_id = faceNormal(t_tri)==[0 0 1];
            % Assign triangulation based on the cap input
            if obj.Cap(1)==1 && obj.Cap(2)==1
                tri= tt;
            elseif obj.Cap(1)==1&&obj.Cap(2)==0
                t_tri = Geometry.meshToTriangulation(cyl_mesh);
                t_tri_id = faceNormal(t_tri)==[0 0 -1];
                new_id = t_tri_id(:,1) == 1 &  t_tri_id(:,2) == 1 & t_tri_id(:,3) ==1;
                tt(new_id,:) = [];
            elseif obj.Cap(1)==0&&obj.Cap(2)==1
                b_tri = Geometry.meshToTriangulation(cyl_mesh);
                b_tri_id = faceNormal(b_tri)==[0 0 1];
                new_id = b_tri_id(:,1) == 1 &  b_tri_id(:,2) == 1 & b_tri_id(:,3) ==1;
                tt(new_id,:) = [];
            else
                t_tri = Geometry.meshToTriangulation(cyl_mesh);
                t_tri_id = faceNormal(t_tri)==[0 0 1];
                new_id1 = t_tri_id(:,1) == 1 &  t_tri_id(:,3) == 1 & t_tri_id(:,3) ==1;
                b_tri = Geometry.meshToTriangulation(cyl_mesh);
                b_tri_id = faceNormal(b_tri)==[0 0 -1];
                new_id2 = b_tri_id(:,1) == 1 &  b_tri_id(:,3) == 1 & b_tri_id(:,3) ==1;
                new_id = new_id1|new_id2;
                tt(new_id,:) = [];
            end

            pt = pp; tri=tt;
            if isscalar(obj.Radius)
                pt=em.internal.translateshape(pt',[0 0 -obj.Height/2]);
            end
            % Translate the points based on the center
            pt=em.internal.translateshape(pt,obj.Center);
            % Orient the geometry
            pt=orientGeom(obj,pt);
            % Get multiplier
            mult=em.internal.authoring.getMultiplier(obj,[obj.Radius,obj.Height,obj.Center],1);
            pt =round(pt,log10(1e4*mult));% as scaled it results in more decimals
            obj.Points=pt';obj.Triangles=tri;
            % Generate final triangulation
            warnState = warning('off','MATLAB:triangulation:PtsNotInTriWarnId');
            trnorm=triangulation(obj.Triangles,obj.Points);
            warning(warnState);
            % Recalculate the normals
            trnorm=em.internal.authoring.recalculateNormals(trnorm);
            if ~isempty(obj.Thickness)
                trnorm=em.internal.authoring.thicken(trnorm,obj.Thickness);
                obj.Points=trnorm.Points;obj.Triangles=trnorm.ConnectivityList;
            end
            be=featureEdges(trnorm,pi/2);
            obj.Points=trnorm.Points;obj.Triangles=trnorm.ConnectivityList;
            obj.MesherStruct.Geometry.BorderVertices = obj.Points;
            geom = getGeometry(obj);
            % Save Geometry
            saveGeometry(obj,geom.BorderVertices,geom.polygons, ...
                geom.MaxFeatureSize,...
                be);
            resetHasStructureChanged(obj);
        end

        function rObj = superLoad(obj,s)
            if nargin>1
                obj.Name            = s.Name;
                obj.Center          = s.Center;
                obj.Radius          = s.Radius;
                obj.Height =s.Height;
                obj.Cap = s.Cap;
                rObj = superLoadShape(obj,s);
            end
        end
    end

    methods(Access=protected)
        function group = getPropertyGroups(obj)      %#ok<MANU>
            title = '';
            propertyList = {'Name'; 'Center'; 'Radius';...
                'Height';'Cap';...
                'Metal';'Dielectric';...
             'Color';'Transparency';'EdgeColor'};
            group = matlab.mixin.util.PropertyGroup(propertyList,title);
        end
        function updateShapeDimension(obj, scalingFactor)
            obj.Radius = obj.Radius.*scalingFactor;
            obj.Height = obj.Height.*scalingFactor;
        end
    end

    methods (Static = true,Hidden) % ----LOADOBJ only IMPLEMENTATION ---%
        function r = loadobj(obj)
            if isobject(obj)&& isObjectFromCurrentVersion(obj)
                r = obj;
            else
                % Create default microstrip patch
                newCylinder = shape.Cylinder;
                % Populate the properties
                r = superLoad(newCylinder,obj);
            end
            r = loadobj@shape.Shape3D(r);
        end
    end

end
