From afa7806ade96b763eca8c94e4d618d0bb5600c3a Mon Sep 17 00:00:00 2001
From: Thomas Lambert <dev@tlambert.be>
Date: Tue, 12 Mar 2024 16:46:45 +0100
Subject: [PATCH] feat(Airfoil): add airfoil interpolation

---
 +af_tools/@Airfoil/Airfoil.m       |   5 ++
 +af_tools/@Airfoil/interpairfoil.m | 123 +++++++++++++++++++++++++++++
 CHANGELOG.md                       |  10 ++-
 3 files changed, 137 insertions(+), 1 deletion(-)
 create mode 100644 +af_tools/@Airfoil/interpairfoil.m

diff --git a/+af_tools/@Airfoil/Airfoil.m b/+af_tools/@Airfoil/Airfoil.m
index a03c17c..6eb3a67 100644
--- a/+af_tools/@Airfoil/Airfoil.m
+++ b/+af_tools/@Airfoil/Airfoil.m
@@ -99,4 +99,9 @@ classdef Airfoil < handle
         end
 
     end
+
+    methods (Static)
+        % Create an airfoil by interpolating between two different Airofil objects
+        self = interpairfoil(Airfoil1, Airfoil2, weightAirfoil2)
+    end
 end
diff --git a/+af_tools/@Airfoil/interpairfoil.m b/+af_tools/@Airfoil/interpairfoil.m
new file mode 100644
index 0000000..58fe57e
--- /dev/null
+++ b/+af_tools/@Airfoil/interpairfoil.m
@@ -0,0 +1,123 @@
+function self = interpairfoil(Airfoil1, Airfoil2, weightAirfoil2)
+    % INTERPAIRFOIL Create a new Airfoil object by interpolation of two other Airfoils.
+    %   This static method can be used in place of a normal constructor for the Airfoil class.
+    %   This method can be used when the user wants to create a non-standard airfoil made from
+    %   the interpolation bewteen two known airfoils.
+    %   Consider that the airfoil at x=0 is a NACA 0012, and the one at x=1 is a CLARY-Y.
+    %   Rather than assuming that all segments in [0,1] are NACA 0012 and all segments after 1 are
+    %   CLARK-Y, this function allows to create polars for the [0,1] interval that are a mix between
+    %   the two bounds. If we are at 0.1, the newly created Airfoil object, will then be 90% of NACA
+    %   0012 and 10% of CLARK-Y.
+    % Note:
+    %   The two input Airfoils should contain Polars of similar structure
+    %   (same Reynolds, Mach and nCrit).
+    % -----
+    %
+    % Usage:
+    %   MixedAirfoil = Airfoil.INTERPPOLAR(Airfoil1, Airfoil2, weightAirfoil2) create an new Airofil
+    %   object that is a linear interpolation between Airfoil1 and Airfoil2, with a given weight for
+    %   Airfoil2.
+    %
+    % Inputs:
+    %   Airfoil1       : Airfoil object for one part of the interpolation.
+    %   Airfoil2       : Airfoil object for the second part of the interpolation.
+    %                    Airfoil2.Polar must have the same Re, Mach and nCrit values as
+    %                    Airfoil1.Polar.
+    %   weightAirfoil2 : Scalar in [0-1] to skew the interpolation  properly between Airfoil1 and
+    %                    Airfoil2.
+    %
+    % Output:
+    %   self : Airfoil object that results from the interpolation.
+    %
+    % Example:
+    %   MixedAirfoil = af_tools.interpairfoil(AfNACA0012, AfClarkY, 0.6); Creates an interpolated
+    %                Airfoil object MixedAirfoil that results from the interpolation of AfNACA0012
+    %                and AfClarkY, asssuming that the MixedPolar is made of 60% ClarkY and 40%
+    %                NACA0012.
+    %
+    % See also: af_tools.Airfoil, af_tools.Polar.
+    %
+    % -----
+    % <a href="https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox">Documentation (online)</a>
+
+    % ----------------------------------------------------------------------------------------------
+    % (c) Copyright 2022-2024 University of Liege
+    % Author: Thomas Lambert <t.lambert@uliege.be>
+    % ULiege - Aeroelasticity and Experimental Aerodynamics
+    % MIT License
+    % Repo: https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox
+    % Issues: https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox/-/issues
+    % ----------------------------------------------------------------------------------------------
+
+    % --- Defaults and constants
+    DEBUG = false;
+
+    % --- Input checks
+    % Check if both Airfoils are Airfoil objects and weightAirfoil2 is in [0,1]
+    validateattributes(Airfoil1, {'af_tools.Airfoil'}, {'scalar'}, mfilename(), 'Airfoil1', 1);
+    validateattributes(Airfoil2, {'af_tools.Airfoil'}, {'scalar'}, mfilename(), 'Airfoil2', 2);
+    validateattributes(weightAirfoil2, {'double'}, {'scalar', 'nonnegative', '<=', 1}, ...
+                       mfilename(), 'weightAirfoil2', 3);
+
+    % --- Interpolate the Airfoils
+
+    self = af_tools.Airfoil;
+
+    if Airfoil1 == Airfoil2
+        self.name = Airfoil1.name;
+        self.coord = Airfoil1.Polar;
+        self.upper = Airfoil1.upper;
+        self.lower = Airfoil1.lower;
+        self.Polar = Airfoil1.Polar;
+        return
+    end
+
+    % Private properties to prevent accidental re-assignment
+    wAf2 = weightAirfoil2;
+    wAf1 = 1 - wAf2;
+
+    % Set base Airfoil information
+    self.name = sprintf('MIX-%d%%%s-%d%%%s', round(wAf1 * 100), Airfoil1.name, ...
+                        round(wAf2 * 100), Airfoil2.name);
+
+    % Interpolate coordinates and Polars
+    self.upper = interpsurf(wAf1, Airfoil1.upper, wAf2, Airfoil2.upper);
+    self.lower = interpsurf(wAf1, Airfoil1.lower, wAf2, Airfoil2.lower);
+    self.coord = [flipud(self.upper); self.lower(2:end, :)];
+
+    self.Polar = af_tools.Polar.interppolar(Airfoil1.Polar, Airfoil2.Polar, wAf2);
+
+    % --- DEBUGGING
+    if DEBUG
+        % Plot the original Airfoil Coordinates as well as the interpolated one
+        figure('Name', 'Interpolated Airfoil');
+        hold on;
+        plot(Airfoil1.coord(:, 1), Airfoil1.coord(:, 2));
+        plot(Airfoil2.coord(:, 1), Airfoil2.coord(:, 2));
+        plot(self.coord(:, 1), self.coord(:, 2));
+        hold off;
+        legend(Airfoil1.name, Airfoil2.name, self.name, 'Location', 'NorthWest');
+        grid on;
+        pause;
+    end
+
+end
+
+function newSurf = interpsurf(weight1, surfCoords1, weight2, surfCoords2)
+    % INTERPSURF Interpolate airfoil surfaces (ONE SURFACE AT A TIME, NOT FULL COORDS)
+
+    % Reinterpolate original data based on the longest chord vector
+    if numel(surfCoords1(:, 1)) > numel(surfCoords2(:, 1))
+        newX = surfCoords1(:, 1);
+        newY1 = surfCoords1(:, 2);
+        newY2 = interp1(surfCoords2(:, 1), surfCoords2(:, 2), newX);
+    else
+        newX = surfCoords2(:, 1);
+        newY1 = interp1(surfCoords1(:, 1), surfCoords1(:, 2), newX);
+        newY2 = surfCoords2(:, 2);
+    end
+
+    newY = weight1 * newY1 + weight2 * newY2;
+    newSurf = [newX, newY];
+
+end
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 81b1f34..70f8f80 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+## [4.4.0] - 2024-03-13
+
+### Added
+
+- Creation of interpolated Polar between two reference Polars
+- Creation of interpolated Airfoil between two reference Airfoils
+
 ## [4.3.0] - 2023-12-14
 
 ### Changed
@@ -145,7 +152,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Initial release
 
-[Unreleased]: https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox/-/compare/v4.3.0...master
+[Unreleased]: https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox/-/compare/v4.4.0...master
+[4.4.0]: https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox/-/compare/4.3.0...v4.4.0
 [4.3.0]: https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox/-/compare/4.2.1...v4.3.0
 [4.2.1]: https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox/-/compare/4.2.0...v4.2.1
 [4.2.0]: https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox/-/compare/4.1.0...v4.2.0
-- 
GitLab