ポイントクラウドからNURBS補間曲面を作る on Rhino+Grasshopper

# この記事の目的
[Tokyo AEC Industry Dev Group](https://www.meetup.com/ja-JP/Tokyo-AEC-Industry-Dev-Group/events/) で発表したハンズオンの内容を文書でまとめたものです。

Rhino+Grasshopper 上でポイントクラウドから補間曲面を**NURBS曲面**として生成するコンポーネントを作ることが目的です。


[gitlab レポジトリ](https://gitlab.com/KaiSuto/pcloud2surface)
[ハンズオンのアーカイブ動画](https://youtu.be/HpjyCB7QJxM)

---
This is a hands-on archive of [Tokyo AEC Industry Dev Group](https://www.meetup.com/ja-JP/Tokyo-AEC-Industry-Dev-Group/events/).

The purpose of this article is to create a component on Rhino+Grasshopper that generates interpolated surfaces from point clouds as NURBS surfaces.

[gitlab repository](https://gitlab.com/KaiSuto/pcloud2surface)
[Youtube archive of this handson](https://youtu.be/HpjyCB7QJxM)

# 時間構成目安 / Time Schedule
- レポジトリをクローンして使ってみる（１５分）
- 理論的な背景説明（４５分）
- 実装作業（２時間）

---
- Clone the repository and try to use it (15 min)
- Theoretical background　(45 min)
- Implementation (2 hour)


# このハンズオンで何ができるようになるか？ / What We Learn?
3Dスキャンなどから取得したランダムな点群から元の形状を「Nurbs Surface」として復元する GH コンポーネントを実装します。

---
We implement a GH component that restores the original shape as a "Nurbs Surface" from a random point cloud obtained from a 3D scan, etc.

<img width="1002" alt="image.png (361.5 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/bcf013f3-d1c0-4137-97a7-66cd3286575b.png">


# 必要なもの / Requirements
- Rhino 6
- Visual Studio
- C# の基本的な知識

---
- Rhino 6
- Visual Studio
- Basic knowledge of C#

# まずは使ってみる / Let's use it first

1. [このレポジトリ](https://gitlab.com/KaiSuto/pcloud2surface)を Visual Studio にクローンします。
2. PCloud2Surface.sln をダブルクリックしてプロジェクトを立ち上げます。
3. プロジェクトをビルドします（Nuget が立ち上がり ALGLIB がインストールされつつビルドされます）。
4. ビルドが無事成功したらデバッグモードで Rhino6 を立ち上げます。
5. Rhino のコマンド上で GrasshopperDeveloperSettings と打ち、プロジェクトのバイナリへの参照を追加します。
6. gh ディレクトリ内の example.gh を開きます。
7. 点群から Nurbs Surface が復元されていることが確かめられたら成功です！

---
1. Clone [this repository](https://gitlab.com/KaiSuto/pcloud2surface) into your Visual Studio.
2. Double click the PCloud2Surface.sln and launch its project.
3. Build the project (Nuget will start up and ALGLIB will be installed, then the project will be built).
4. After a successful build, launch Rhino6 in debug mode.
5. On the Rhino command, type "GrasshopperDeveloperSettings" to add a reference to the project's binary.
6. Open the example.gh in gh derectory.
7. If you can confirm that the Nurbs Surface has been recovered from the point cloud, you've succeeded!

# 理論的背景パート / Theoretical Background
## そもそも補間って何？ / What is Interpolation?
[Wiki](https://ja.wikipedia.org/wiki/%E5%86%85%E6%8C%BF) によると
「内挿（ないそう、英: interpolation）や補間（ほかん）とは、ある既知の数値データ列を基にして、そのデータ列の各区間の範囲内を埋める数値を求めること、またはそのような関数を与えること。」
となっています。

<img width="300" alt="image.png (44.5 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/93abf8df-32e1-409d-ad7d-9c2f9225da2f.png">

<img width="300" alt="image.png (46.8 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/15e4071f-ab30-4d3b-8f15-8e9321a4da01.png"><img width="300" alt="image.png (55.2 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/5a0866c9-6ed2-4b7a-9cf2-15505ec87444.png"><img width="300" alt="image.png (36.8 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/037adf4e-8dc0-48b1-8616-c9b19db0b76f.png"><img width="300" alt="image.png (46.7 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/d2454764-f8ae-4fe4-90dc-ad50e2a2ab87.png">

既知の数値データ列からその間を埋められると、どういった利点があるのでしょうか？

一番わかりやすい例は、今回実装する「点群から曲面生成をする」、といった幾何処理ができるということかと思います。

もうちょっと高度な例だと、AEC の文脈で、例えばＧＨ上で定義したパラメトリックモデルに対して何かしらの構造計算や環境評価をした値のパラメータスタディをする、

「幾何パラメータ　－＞　評価値」

という対応関係が離散的に手に入ります。これら離散的な評価点群から間を滑らかな曲面で近似することで、重い計算が必要な構造計算や環境評価を毎回行わなくても、幾何パラメータを入力すると瞬時にそれなりに正しい評価値を算出することが可能になります。また、微分情報も取ることができるので、この対応関係を最適化計算に利用することもできます。

<img width="821" alt="image.png (257.4 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/4455cc24-2f52-4ade-9501-cdcd9267aa99.png">

画像出典：[【はじめての最適化】最適化だけじゃない！応答曲面機能（その1）](https://www.idaj.co.jp/blog/product/optimization/basic-cao-190116)

ここでは「内挿」について説明しましたが、データに無い未知の入力に対してもそれなりに正しい解を出力する「外挿」についてさらに知りたい方は[ここ](https://dajiro.hatenablog.com/entry/2020/04/21/085950)あたりを参考にすると面白いかもしれません。

---
At [Wiki](https://en.wikipedia.org/wiki/Interpolation), it is descrived like this "In the mathematical field of numerical analysis, interpolation is a type of estimation, a method of constructing new data points within the range of a discrete set of known data points. In engineering and science, one often has a number of data points, obtained by sampling or experimentation, which represent the values of a function for a limited number of values of the independent variable. It is often required to interpolate, i.e., estimate the value of that function for an intermediate value of the independent variable."

<img width="300" alt="image.png (42.3 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/31/50027/583ea96c-5cf5-48a1-8dbf-a7e9ffea1c9a.png">

<img width="300" alt="image.png (53.2 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/31/50027/9cad9827-73bd-43e9-ad18-6877b3e116ec.png"><img width="300" alt="image.png (70.2 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/31/50027/01c0301d-1fb1-4509-b832-f2242911f48a.png"><img width="300" alt="image.png (70.3 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/31/50027/438f0b26-7dcf-4011-809a-09b837a1208b.png"><img width="300" alt="image.png (69.6 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/31/50027/0f1e847e-77cb-47cc-b3e3-65e6f8b8943a.png">

What are the benefits of being able to fill in the gaps from a known sequence of numerical data?

The most obvious example is the ability to perform geometric processing, such as "generating a surface from a point cloud".

As a more advanced example, in the context of AEC, you can discretely obtain a correspondence of "geometric parameter - > evaluation value", for example, a parameter study of some structural or environmental evaluation value for a parametric model defined on GH. By approximating the intervals between these discrete evaluation points with smooth surfaces, it is possible to instantly calculate a reasonably correct evaluation value when geometric parameters are entered, without having to perform heavy calculations each time for structural or environmental evaluation. Since it can also take differential information, this correspondence can be used for optimization calculations.

In this section, I explained "interpolation", but if you want to know more about "extrapolation", which outputs a reasonably correct solution to an unknown input that is not in the data, [this](https://dajiro.hatenablog.com/entry/2020/04/21/085950) might be interesting to refer to this section.

## 代表的な補間方法について / Typical interpolation methods
### 多項式補間 / polynomial interpolation
多項式を用いた補間方法で、「すべてのサンプル点を通る」ような補間が可能です。
サンプル点がきれいに滑らかに分布している場合は優秀な手法です。
しかし、すべてのサンプル点を通るように補間が行われる影響で、元データにノイズがのっているとたちまち補間曲面があばれてしまい、そのようなデータには不向きな手法です。

---
It is a method of interpolation using polynomials, which allows the interpolation to "pass through all sample points".
It is an excellent technique if the sample points are clean and smoothly distributed.
However, this method is not suitable for such data because the interpolation is performed so that all sample points pass through the interpolation, and if noise is present in the original data, the interpolated surface is immediately overturned.

### スプライン補間 / spline interpolation
[スプライン曲線](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E6%9B%B2%E7%B7%9A)
領域を区分的に分割し、接続部に何らかの連続性を仮定して各区間毎の補間を求める手法です。

---
It is a method to divide the region into segments and to obtain the interpolation for each interval by assuming some continuity in the connection.

### RBF 補間 / RBF Interpolation
[この記事](http://yuki-koyama.hatenablog.com/entry/2014/05/04/132552)が非常に詳しいです。
Flash Player が再生できるブラウザをお持ちの方は[こちらのデモ](http://www.brain.kyutech.ac.jp/~furukawa/data/rbf.html)がわかりやすいです。

Radial Basis Function を用いた「すべてのサンプル点を通る」補間方法です。
大規模な散布点の補間に強く、点群から穴のないメッシュを生成する手法やシミュレーションデータの内挿によく用いられます。
今回の実装ではこの RBF 補間を利用します。

---
This is an interpolation method that uses the Radial Basis Function to "pass through all sample points".
It is suitable for interpolation of large scatter points and is often used to generate holeless meshes from point clouds and to interpolate simulation data.
In this implementation, this RBF interpolation is used.

### NURBS 補間 / NURBS Interpolation
CAD で多く利用されている NURBS 曲面も、グリッド状に並んだ制御点間を滑らかに補間する曲面であるという意味では一種の補間手法です。

---
NURBS surfaces, which are often used in CAD, are a kind of interpolation method in the sense that they smoothly interpolate between control points arranged in a grid.


# 実装パート / Implementation
## 使うライブラリ / Libraries
- RhinoCommon
- [ALGLIB](https://www.alglib.net/)（.NET のみならず C++、Python、Delphiにも対応しているクロスプラットフォームの数値解析、データ処理ライブラリ。今回は RBF補間機能を利用します。）

---
- RhinoCommon
- [ALGLIB](https://www.alglib.net/)（A cross-platform numerical analysis and data processing library that supports not only .NET but also C++, Python, and Delphi. In this case, we will use the RBF interpolation function.）

## 実装方針 / Implementation Strategy
- 点群から復元するベースとなる座標系決定（今回は XY 座標値 -> Z 座標値という対応関係を用います。円筒状の形状を補間したい場合は円筒座標系を用います。）

---
- Determination of the base coordinate system to be restored from the point cloud (in this case, the correspondence relationship of XY coordinate value -> Z coordinate value is used. If you want to interpolate a cylindrical shape, use the cylindrical coordinate system.)

<img width="500" alt="image.png (181.5 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/b2fba03f-0aba-49d1-bc4d-ae4fe9b95e11.png">

- 決定した座標系上で点群の補間関数を作る（今回のような散布データの補間（内挿）をしたい場合は RBF (Radial Basis Function) が便利です。）

---
- Create the interpolation function of the point cloud on the determined coordinate system (RBF (Radial Basis Function) is useful if you want to interpolate the scattered data as in this case).

<img width="500" alt="image.png (176.8 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/72a4be76-e637-40b0-bab7-558aae446780.png">

- 作った補間関数を元に RhinoCommon の Network Surface を使って NURBS 曲面 を生成する。

---
- Generate a NURBS surface using the interpolation function I created using RhinoCommon's Network Surface.

<img width="500" alt="image.png (267.9 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/12e43e65-49e7-40ad-8890-e50ecf232e0a.png">

##  点群からベースとなる座標系決定 / Determination of the base coordinate system from the point cloud
点群から曲面を復元する方法として、たとえば陰関数表現を用いた方法があります（[Siggraph 論文](https://dl.acm.org/doi/10.1145/383259.383266)）。
この方法は
- 任意の位相構造（球面だったりドーナッツ形状だったりも対応可能）に対応可能。
- 補間されたジオメトリデータはメッシュかボリュームデータとして利用される。
- 実空間をスキャン、メッシュ化してテクスチャを張るみたいな使い方に非常に適していて、SLAM との相性も良い。

などの特徴があります。

---
One way to recover surfaces from point clouds is, for example, to use an implicit function representation ([Siggraph paper](https://dl.acm.org/doi/10.1145/383259.383266)).
This method is characterized by the following
- Any phase structure (spherical or doughnut-shaped) can be used.
- The interpolated geometry data is used as mesh or volume data.
- It's very good for things like scanning real space, meshing and texturing, and it works well with SLAM.

---
しかし、例えばスキャンデータから Nurbs Surface を作りたい場合は上記の方法では難しいです。
そこで、任意の位相構造に対応することはあきらめて、あらかじめ基準となる座標系を決めることで、Nurbs Surface を構築することができるようになります。

今回は XY 座標値 -> Z 座標値という対応関係を用います。
この座標系は例えば滑らかな地形データやあまり暴れない表面形状などに適しています。

---
However, if you want to create a Nurbs Surface from scanned data, for example, the above method is not suitable.
Therefore, it is possible to build a Nurbs Surface by giving up on supporting an arbitrary topological structure and deciding on a reference coordinate system in advance.

In this case, we use the correspondence of XY-coordinate value -> Z-coordinate value.
This coordinate system is suitable, for example, for smooth terrain data and less unevenness surface features.

```csharp
private void SetXYZ(List<Point3d> pts, Plane plane)
{
    this.x = new double[pts.Count];
    this.y = new double[pts.Count];
    this.z = new double[pts.Count];
    for (int i = 0; i < pts.Count; i++)
    {
        double xi = 0;
        double yi = 0;
        double zi = 0;
        // 点の基準平面上への射影点を求める
        Point3d onPlanePoint = plane.ClosestPoint(pts[i]);
        // 射影点での基準平面上での座標を求める
        plane.ClosestParameter(onPlanePoint, out xi, out yi);
        // 点の基準平面上での距離を求める
        zi = onPlanePoint.DistanceTo(pts[i]);
        // 基準平面の裏側にあったら負の値にする
        if ((pts[i] - onPlanePoint) * plane.Normal < 0) zi *= -1;
        this.x[i] = xi;
        this.y[i] = yi;
        this.z[i] = zi;
    }
}
```

人間の腕などの円筒状の自由形状を補間したい場合は円筒座標系を用います。
座標系をもっと工夫することでさらに複雑な形状に対応可能です（一般のＵＶＷ空間を使ってもよい）。

---
If you want to interpolate a cylindrical free-form shape such as a human arm, you can use the cylindrical coordinate system.
It is possible to handle more complex shapes by devising the coordinate system (you can also use the general UVW space).

## 決定した座標系上で点群を補間する関数を作る / Create a function to interpolate a point cloud on a determined coordinate system
座標系が決定されたら、そこ上で点群の X, Y, Z 座標値を計算し、その値を用いて、

補間関数 f: [X, Y] -> Z

を作ります。この補間関数として今回は RBF 補間を使います。.NET で無料で RBF 補間が使えるライブラリ、ALGLIB を使って実装していきます。

---
Once the coordinate system has been determined, the X, Y, and Z coordinate values of the point cloud are calculated on it, and the interpolation function 

f: [X, Y] -> Z

is created using those values. This time, RBF interpolation is used as the interpolation function. NET free RBF interpolation library, ALGLIB is used to implement it.

```csharp
private void SetRBFModel()
{
    // RBF Model を宣言
    alglib.rbfmodel model;
    // 入力が2次元、出力が１次元として RBF Model を初期化
    alglib.rbfcreate(2, 1, out model);
    // サンプル点を RBF Model にセット
    alglib.rbfsetpoints(model, this.samplePlanarCoordsScaled);
    // RBF Model のパイパーパラメータ設定
    alglib.rbfsetalgohierarchical(model, this.rBase, this.nLayers, this.smooth);
    // モデル構築に関する情報を入れるクラスを宣言
    alglib.rbfreport rep;
    // RBF Model 構築
    alglib.rbfbuildmodel(model, out rep);
    // クラスプロパティに代入
    this.Model = model;
}
```

## RBF補間関数を元に RhinoCommon の Network Surface を使って NURBS 曲面を生成する / Generate a NURBS surface using RhinoCommon's Network Surface based on the RBF interpolation function.
RBF 補間関数から NURBS 曲面を生成します。
安易に RBF 補間関数からグリッド点を作ってそれを NURBS 曲面 の制御点にそのまま使ってしまうとアウトです。
NURBS 曲面は制御点を通らないその間を補間された形状となるため、RBF 補間関数との誤差が必ず生じてしまいます。

---
Creates a NURBS surface from the RBF interpolation function.
If you easily create a grid point from the RBF interpolation function and use it as a control point of a NURBS surface, you are out of the game.
Since the NURBS surface is an interpolated shape that does not pass through the control points, there is always an error with the RBF interpolation function.

---
この問題をクリアするために NurbsSurface.CreateNetworkSurface を用います。
この API は UV方向それぞれの曲線群から、それらで構成されるグリッドになるべく近くなるような NURBS 曲面を生成することができます。
RBF 補間関数から UV のグリッド曲線を簡単に作れるので、今回はこの方法を採用します。

---
We use NurbsSurface.CreateNetworkSurface to clear this problem.
The API can generate NURBS surfaces that are as close to the grid as possible to the curves in each UV direction.
Since it is easy to create a UV grid curve from the RBF interpolation function, this method is adopted in this case.

```csharp
public Surface InterpolatedSurface(double xStart, double xEnd, double yStart, double yEnd, int U, int V)
{
    // U 方向のネットワークスプライン曲線群を作る
    Curve[] uCurves = new Curve[U + 1];
    for(int i = 0; i < U+1; i++)
    {
        double stepY = (yEnd - yStart) / V;
        double y = yStart + i * stepY;
        Point3d[] pts = new Point3d[V + 1];
        for(int j = 0; j < V+1; j++)
        {
            double stepX = (xEnd - xStart) / U;
            double x = xStart + j * stepX;
            double z = this.Z(x, y);
            pts[j] = new Point3d(x, y, z);
        }
        uCurves[i] = NurbsCurve.CreateInterpolatedCurve(pts, 3);
    }
    // V 方向のネットワークスプライン曲線群を作る
    Curve[] vCurves = new Curve[V + 1];
    for (int i = 0; i < V + 1; i++)
    {
        double stepX = (xEnd - xStart) / U;
        double x = xStart + i * stepX;
        Point3d[] pts = new Point3d[U + 1];
        for (int j = 0; j < U + 1; j++)
        {
            double stepY = (yEnd - yStart) / V;
            double y = xStart + j * stepY;
            double z = this.Z(x, y);
            pts[j] = new Point3d(x, y, z);
        }
        vCurves[i] = NurbsCurve.CreateInterpolatedCurve(pts, 3);
    }
    // Network Surface の構築
    int error;
    Surface interpolatedSurface = NurbsSurface.CreateNetworkSurface(uCurves, 1, 1, vCurves, 1, 1, 0.1, 0.1, 0.1, out error);
    return interpolatedSurface;
}
```

## 実装 / Implementation

## 主なディレクトリ構成 / Main Directory Structure
<img width="256" alt="image.png (10.7 kB)" src="https://img.esa.io/uploads/production/attachments/11581/2020/05/30/50027/436d87cd-ab56-4a9d-9cd0-1660c77b48d9.png">

## 実装する機能を１つのクラスにまとめる / Combining the functions to be implemented into a single class
RBFInterpolation.cs

```csharp
using Rhino.Geometry;
using System.Collections.Generic;
using System.Linq;

namespace PCloud2Surface.Classes
{
    class RBFInterpolation
    {
        #region Constructros
        public RBFInterpolation()
        {

        }
        public RBFInterpolation(List<Point3d> pts, Plane plane, int nLayers, double rBase, double smooth)
        {
            this.SamplePts = pts;
            this.Plane = plane;
            this.nLayers = nLayers;
            this.rBase = rBase;
            this.smooth = smooth;
            SetProperties(pts, plane);
        }
        #endregion

        #region Properties
        public List<Point3d> SamplePts { get; private set; }
        public Plane Plane { get; private set; }
        public alglib.rbfmodel Model { get; private set; }
        #endregion

        #region Public Methods
        public double Z(double x, double y)
        {
            double xScaled = x / this.xLength;
            double yScaled = y / this.yLength;
            double zScaled = alglib.rbfcalc2(this.Model, xScaled, yScaled);
            double z = zScaled * zLength;

            return z;
        }
        public Point3d P(double x, double y)
        {

            double z = this.Z(x, y);
            Vector3d zDir = this.Plane.Normal;
            Point3d ptOnPlane = this.Plane.PointAt(x, y);
            Point3d p = ptOnPlane + z * zDir;
            return p;
        }
        public Surface InterpolatedSurface(double xStart, double xEnd, double yStart, double yEnd, int U, int V)
        {
            // U 方向のネットワークスプライン曲線群を作る
            Curve[] uCurves = new Curve[U + 1];
            for(int i = 0; i < U+1; i++)
            {
                double stepY = (yEnd - yStart) / V;
                double y = yStart + i * stepY;
                Point3d[] pts = new Point3d[V + 1];
                for(int j = 0; j < V+1; j++)
                {
                    double stepX = (xEnd - xStart) / U;
                    double x = xStart + j * stepX;
                    pts[j] = this.P(x,y)
                }
                uCurves[i] = NurbsCurve.CreateInterpolatedCurve(pts, 3);
            }
            // V 方向のネットワークスプライン曲線群を作る
            Curve[] vCurves = new Curve[V + 1];
            for (int i = 0; i < V + 1; i++)
            {
                double stepX = (xEnd - xStart) / U;
                double x = xStart + i * stepX;
                Point3d[] pts = new Point3d[U + 1];
                for (int j = 0; j < U + 1; j++)
                {
                    double stepY = (yEnd - yStart) / V;
                    double y = xStart + j * stepY;
                    pts[j] = this.P(x,y)
                }
                vCurves[i] = NurbsCurve.CreateInterpolatedCurve(pts, 3);
            }
            // Network Surface の構築
            int error;
            Surface interpolatedSurface = NurbsSurface.CreateNetworkSurface(uCurves, 1, 1, vCurves, 1, 1, 0.1, 0.1, 0.1, out error);
            return interpolatedSurface;
        }
        #endregion

        #region Private Members
        private int numSamplePoints;
        private double[] x;
        private double[] y;
        private double[] z;
        private double xLength;
        private double yLength;
        private double zLength;
        private double[,] samplePlanarCoordsScaled;
        private int nLayers = 3;
        private double rBase = 1.0;
        private double smooth = 0.0;
        #endregion

        #region Private Methods
        private void SetNumSamplePoints(List<Point3d> pts)
        {
            this.numSamplePoints = pts.Count;
        }
        private void SetXYZ(List<Point3d> pts, Plane plane)
        {
            this.x = new double[pts.Count];
            this.y = new double[pts.Count];
            this.z = new double[pts.Count];
            for (int i = 0; i < pts.Count; i++)
            {
                double xi = 0;
                double yi = 0;
                double zi = 0;
                // 点の基準平面上への射影点を求める
                Point3d onPlanePoint = plane.ClosestPoint(pts[i]);
                // 射影点での基準平面上での座標を求める
                plane.ClosestParameter(onPlanePoint, out xi, out yi);
                // 点の基準平面上での距離を求める
                zi = onPlanePoint.DistanceTo(pts[i]);
                // 基準平面の裏側にあったら負の値にする
                if ((pts[i] - onPlanePoint) * plane.Normal < 0) zi *= -1;
                this.x[i] = xi;
                this.y[i] = yi;
                this.z[i] = zi;
            }
        }
        private void SetXYZLength()
        {
            this.xLength = this.x.Max() - this.x.Min();
            this.yLength = this.y.Max() - this.y.Min();
            this.zLength = this.z.Max() - this.z.Min();
        }
        private void SetScalizedPlanarCoordinates()
        {
            this.samplePlanarCoordsScaled = new double[this.numSamplePoints, 3];
            for(int i = 0; i < this.numSamplePoints; i++)
            {
                this.samplePlanarCoordsScaled[i, 0] = this.x[i] / this.xLength;
                this.samplePlanarCoordsScaled[i, 1] = this.y[i] / this.yLength;
                this.samplePlanarCoordsScaled[i, 2] = this.z[i] / this.zLength;
            }
        }
        private void SetRBFModel()
        {
            // RBF Model を宣言
            alglib.rbfmodel model;
            // 入力が2次元、出力が１次元として RBF Model を初期化
            alglib.rbfcreate(2, 1, out model);
            // サンプル点を RBF Model にセット
            alglib.rbfsetpoints(model, this.samplePlanarCoordsScaled);
            alglib.rbfsetalgohierarchical(model, this.rBase, this.nLayers, this.smooth);
            // モデル構築に関する情報を入れるクラスを宣言
            alglib.rbfreport rep;
            // RBF Model 構築
            alglib.rbfbuildmodel(model, out rep);
            // クラスプロパティに代入
            this.Model = model;
        }
        private void SetProperties(List<Point3d> pts, Plane plane)
        {
            SetNumSamplePoints(pts);
            SetXYZ(pts, plane);
            SetXYZLength();
            SetScalizedPlanarCoordinates();
            SetRBFModel();
        }
        #endregion


    }
}
```

## RBFInterpolaltion クラスのインスタンスを生成する GH コンポーネント / GH component that creates an instance of the RBFInterpolaltion class
RBFComponent.cs

```csharp
using System;
using System.Collections.Generic;

using Grasshopper.Kernel;
using Rhino.Geometry;
using PCloud2Surface.Classes;

// In order to load the result of this wizard, you will also need to
// add the output bin/ folder of this project to the list of loaded
// folder in Grasshopper.
// You can use the _GrasshopperDeveloperSettings Rhino command for that.

namespace PCloud2Surface
{
    public class RBFComponent : GH_Component
    {
        /// <summary>
        /// Each implementation of GH_Component must provide a public 
        /// constructor without any arguments.
        /// Category represents the Tab in which the component will appear, 
        /// Subcategory the panel. If you use non-existing tab or panel names, 
        /// new tabs/panels will automatically be created.
        /// </summary>
        public RBFComponent()
          : base("RBF from PCloud", "RBF PCloud",
              "Create a RBF instance.",
              "Interpolation", "RBF")
        {
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddPointParameter("Sample Pts", "Pts", "Sampling point to interpolate.", GH_ParamAccess.list);
            pManager.AddPlaneParameter("Plane", "P", "A reference plane.", GH_ParamAccess.item);
            pManager.AddIntegerParameter("Number of Layers", "nLayers", "A number of layers of a RBF network.", GH_ParamAccess.item);
            pManager.AddNumberParameter("Radius of Basis Function", "rBase", "A radius of the RBF.", GH_ParamAccess.item);
            pManager.AddNumberParameter("Smooth Prameter", "Smooth", "A parameter of smoothing of a RBF network", GH_ParamAccess.item);
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
            pManager.AddGenericParameter("RBF", "RBF", "A RBF Interpolation instance which create interpolated surface from the input point cloud.", GH_ParamAccess.item);
        }

        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object can be used to retrieve data from input parameters and 
        /// to store data in output parameters.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            List<Point3d> samplePts = new List<Point3d>();
            Plane plane = new Plane();
            int nLayers = 3;
            double rBase = 1;
            double smooth = 0;

            DA.GetDataList(0, samplePts);
            DA.GetData(1, ref plane);
            DA.GetData(2, ref nLayers);
            DA.GetData(3, ref rBase);
            DA.GetData(4, ref smooth);

            RBFInterpolation rbf = new RBFInterpolation(samplePts, plane, nLayers, rBase, smooth);

            DA.SetData(0, rbf);
        }

        /// <summary>
        /// Provides an Icon for every component that will be visible in the User Interface.
        /// Icons need to be 24x24 pixels.
        /// </summary>
        protected override System.Drawing.Bitmap Icon
        {
            get
            {
                // You can add image files to your project resources and access them like this:
                //return Resources.IconForThisComponent;
                return null;
            }
        }

        /// <summary>
        /// Each component must have a unique Guid to identify it. 
        /// It is vital this Guid doesn't change otherwise old ghx files 
        /// that use the old ID will partially fail during loading.
        /// </summary>
        public override Guid ComponentGuid
        {
            get { return new Guid("4567ec83-ae6c-4e65-a7bd-bb52300d6c1e"); }
        }
    }
}
```

## RBFInterpolation インスタンスから補間曲面生成する GH コンポーネント / GH component that generates interpolated surfaces from a RBFInterpolation instance
InterpolatedSurface.cs

```csharp
using System;
using System.Collections.Generic;

using Grasshopper.Kernel;
using PCloud2Surface.Classes;
using Rhino.Geometry;

namespace PCloud2Surface.Components
{
    public class InterpolatedSurface : GH_Component
    {
        /// <summary>
        /// Initializes a new instance of the InterpolatedSurface class.
        /// </summary>
        public InterpolatedSurface()
          : base("Interpolated Surface", "IS",
              "Create the interpolated surface form RBF",
              "Interpolation", "Surface")
        {
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddGenericParameter("RBF", "RBF", "A RBF Planar interpolation instance.", GH_ParamAccess.item);
            pManager.AddNumberParameter("Start X", "SX", "A start x coordinate of a cylindrical coordinates", GH_ParamAccess.item);
            pManager.AddNumberParameter("End X", "EX", "A end x coordinate of a cylindrical coordinates", GH_ParamAccess.item);
            pManager.AddNumberParameter("Start Y", "SY", "A start y coordinate of a cylindrical coordinates", GH_ParamAccess.item);
            pManager.AddNumberParameter("End Y", "EY", "A end y coordinate of a cylindrical coordinates", GH_ParamAccess.item);
            pManager.AddIntegerParameter("U Count", "UC", "A count of control points at x coordinate", GH_ParamAccess.item);
            pManager.AddIntegerParameter("V Count", "VC", "A count of control points at y coordinate", GH_ParamAccess.item);
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
            pManager.AddSurfaceParameter("Interpolated Surface", "IS", "A interpolated surface from RBF.", GH_ParamAccess.item);
        }

        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            RBFInterpolation rbf = new RBFInterpolation();
            double xStart = 0;
            double xEnd = 10;
            double yStart = 0;
            double yEnd = 10;
            int uCount = 10;
            int vCount = 10;

            DA.GetData(0, ref rbf);
            DA.GetData(1, ref xStart);
            DA.GetData(2, ref xEnd);
            DA.GetData(3, ref yStart);
            DA.GetData(4, ref yEnd);
            DA.GetData(5, ref uCount);
            DA.GetData(6, ref vCount);

            Surface interpolatedSurface = rbf.InterpolatedSurface(xStart, xEnd, yStart, yEnd, uCount, vCount);

            DA.SetData(0, interpolatedSurface);
        }

        /// <summary>
        /// Provides an Icon for the component.
        /// </summary>
        protected override System.Drawing.Bitmap Icon
        {
            get
            {
                //You can add image files to your project resources and access them like this:
                // return Resources.IconForThisComponent;
                return null;
            }
        }

        /// <summary>
        /// Gets the unique ID for this component. Do not change this ID after release.
        /// </summary>
        public override Guid ComponentGuid
        {
            get { return new Guid("fd5133a7-80e8-4d31-865c-2fefec5e5104"); }
        }
    }
}
```