茂加部珈琲店

主にtech関連のメモ置き場です

NURBS曲線の特徴

CGの分野では解像度に非依存な形状を保存するために、パラメトリック曲線を利用することがあります. 今回は、その中でも特に自由度の高いNURBS曲線の基本的な事項をおさらいします.

ベクタ形式ではBezier曲線が有名で、古くから使われていますが、Bezier曲線にはいくつかの欠点があります. その中でも代表的なものを纏めてみると

  • 次数が制御点数に影響される
  • 重み付けができない
  • 局所的な変更が難しい

これらの問題点を解決するために考案されたのが、BSpline曲線です. NURBS曲線(Non-Uniform Rational BSpline curve)とは、名前の通りBSpline曲線に便利な特性(重み付けと非一様ノット列)を追加した形のことを言います.
NURBS曲線はBezier曲線とBspline曲線の上位版にあたり、高度な表現力をもちながら扱いやすい表現です

NURBS曲線の定義

NURBS曲線は以下の式で表現されます.
{ \displaystyle
C(u) = \frac{\sum_{i=0}^{k-1} N_{i,n}(u)w_iP_i } {\sum_{i=0}^{k-1} N_{i,n}(u)w_i}
}
ただし、kは制御点の個数, nは次数です {N_{i,n}}(u)はBSpline基底関数と呼ばれ、以下のように定義されます

{n=0}のとき
 { \displaystyle
\begin{equation}N_{i,n}(u)= \left \{\begin{array}{l} 1 (u_i \leq u \lt u_{i+1} ) \\ 0 (otherwise) \end{array} \right.\end{equation}
}

{n\neq0}のとき

 { \displaystyle
N_{i,n}(u)= \frac{u-u_i}{u_{i+n}-u_i}N_{i,n-1}(u)+\frac{u_{i+n+1}-u}{u_{i+n+1}-u_{i+1}}N_{i+1,n-1}(u)
}

ここで、{n=0}のときに1を返す範囲は、

{  \displaystyle
(u_i \leq u \lt u_{i+1})
}

{  \displaystyle
(u_i \lt u \leq u_{i+1})
}

のどちらかを採用します.
区間になっているほうの端は範囲に含まれなくなるので、エラー処理を行う必要があることに注意してください. また、

{  \displaystyle
(u_i \leq u \leq u_{i+1})
}

を採用して、{n=1}で例外処理を行う方法もあります

以下に基底関数の素直な実装を載せておきます.(改良の余地はあります.エラー処理はお好みで追加してください)
今度はDeBoorのアルゴリズムとかをやりたいですね

/** @fun
 * @brief BSpline基底関数を計算します
 * @param t 独立変数t
 * @param i 制御点の番号i
 * @param n 次数n
 * @param knots ノットベクトル
 */
double NURBS::basis(double t, size_t i, size_t n,
                    const std::vector<double> &knots) {

  // N[i][0]
  if (n == 0)
    return (knots[i] <= t && t < knots[i + 1])
               ? 1
               : 0;

  double a1 = knots[i + n] - knots[i];
  double a2 = knots[i + n + 1] - knots[i + 1];

  double b1 = t - knots[i];
  double b2 = knots[i + n + 1] - t;

  // "ゼロで割られたらゼロ"という決まりです
  double r1 = (b1 == 0) ? 0 : basis(t, i, n - 1, knots) * b1 / a1;
  double r2 = (b2 == 0) ? 0 : basis(t, i + 1, n - 1, knots) * b2 / a2;

  return r1 + r2;
}


型情報の保存

あけましておめでとうございます.

突然ですが、基底クラスから元の型を復元したいと思うことはないでしょうか.C++ではそのような型情報の保存を簡単に行えるみたいです.
具体的には、仮想関数を利用して型情報を返す関数を定義します.

#include <iostream>
#include <typeinfo>

struct Base{
    //型情報を返す仮想関数
    virtual const std::type_info& type() const{
        return typeid(*this);
    }
};

struct ClassA : public Base{
    
};


int main(){
    //基底クラス
    Base base;
    //派生クラス
    ClassA classA;
    //基底クラスのポインタに格納された派生クラス
    Base* base_classA = &classA;

    std::cout << "type of base is " << base.type().name() << std::endl;
    std::cout << "type of classA is " << classA.type().name() << std::endl;
    std::cout << "type of base_classA is " << base_classA->type().name() << std::endl;

    return 0;
}

/* 実行結果:
type of base is 4Base
type of classA is 6ClassA
type of base_classA is 6ClassA
*/

このようにして、仮想関数を使うことで、基底クラスのポインタからも元の型名が取り出せます.
typeinfoが致すればキャストするようにすれば、比較的安全に型が復元できるはずですね.
このテクニックはboostライブラリのboost::anyでも使用されているようです.

virtualなデストラクタ

C++では、ポリモーフィズムを利用するために作成した基底クラスのデストラクタにはvirtualを付けることがあるようです.
virtualがないと、基底クラスのポインタで管理している場合は、基底クラスのデストラクタのみが呼ばれてしまうためです.
初心者は「そんなこと聞いてないよ」となりそうで、不親切だとは思いますが、様々な理由でこうなっているはずなので仕方ないでしょう.

以下のようなコードで確認してみましょう

#include <iostream>
#include <memory>

struct Base{
    Base(){
        std::cout << "  Base constructor" << std::endl;
    }
    ~Base(){
        std::cout << "  Base destructor" << std::endl;
    }
};

struct Child : public Base{
    Child(){
        std::cout << "  Child constructor" << std::endl;
    }
    ~Child(){
        std::cout << "  Child destructor" << std::endl;    
    }
};

int main(){
    {
    std::cout << "unique_ptr:" << std::endl;
    std::unique_ptr<Base> pBase = std::make_unique<Child>();
    }
    {
    std::cout << "shared_ptr" << std::endl;
    std::shared_ptr<Base> pBase = std::make_shared<Child>();
    }
    std::cout << "Raw pointer" << std::endl; 
    Base* pBaseRaw = new Child();
    delete(pBaseRaw);

    return 0;
} 

実行結果はこうなります

unique_ptr:
  Base constructor
  Child constructor
  Base destructor
shared_ptr
  Base constructor
  Child constructor
  Child destructor
  Base destructor
Raw pointer
  Base constructor
  Child constructor
  Base destructor

Rawポインタとunique_ptrでは、Childのデストラクタが呼ばれていないのが確認できます.
基底クラスのデストラクタにvirtualをつければ、全てChildのデストラクタを呼ぶようになります
また、shared_ptrはvirtualデストラクタがないにもかかわらず、Childのデストラクタが呼ばれているのも面白いですね.
これは、shared_ptrが作成時のクラスを利用してポインタを破棄してくれるからみたいです.
しかし、unique_ptrではやはりvirtualが必要となるので、あまり安心はできなさそうですね.

単純な三角メッシュ

はてなのマークダウンの練習
CGの入門書に書いてあるようなやつです

メッシュ構造

メッシュ構造は3DCGで利用される3Dモデルの表現法です.
ここでは、シンプルなobjファイルで使われるような、頂点+三角形で構成されるメッシュ構造を書いてみました

template <typename T>
struct Vertex{  //頂点
    //...コンストラクタなど...//
    tvec3<T> position;  //位置座標
};

template <typename T>
struct Face{  //面
    //...コンストラクタなど...//
    std::shared_ptr<Vertex<T>> verticies[3]; //三頂点へのポインタ
    tvec3<T> normal;  //法線
};

template <typename T>
struct Mesh{  //メッシュ
    //...コンストラクタなど..//
    std::vector<std::shared_ptr<Vertex<T>>> verticies; //頂点へのポインタリスト
    std::vector<std::shared_ptr<Face<T>>> faces;  //面へのポインタリスト
};

各面はその三角形を構成する3頂点へのポインタを持ちます.
ついでに法線まで計算する気まんまんですが、不要な場合はなくても良いですね.
関数を全部削ってしまいましたが、簡単なので.
リソース開放はshared_ptrとかに任せちゃうのがナウなヤングのやり方じゃないでしょうか.
もっと複雑なハーフエッジ構造などは少し凝った構築をしないといけなくて大変で、それに対する最適化手法も色々あります.
CGは奥が深いですね.

Fiio D03Kで遊ぶ

Fiio D03K

Fiio D03Kは安価なSPDIF入力のDACです.
入力は192KHz,24bitで光、同軸入力をサポートします.
どこかに192KHzは同軸のみと書いてありましたが、私の環境では光でも192KHz/24bitを受けれています.
オヤイデが代理店をやってるみたいです.高級オーディオの印象がありますが、こんなところで名前を見るとは思いませんでした.

D03KのIC構成は
CS8416 -> CS8416 -> LVM358
となっております.オペアンプが汎用品ですので、このあたりを変えて遊ぶことができます.

詳しい情報はこのページに載っています. よくできているので是非参考にしてみてください.

コンデンサオペアンプの交換

回路の定数を変更しない簡単な強化を行ってみました.

電源部の470ufをOSコン(470uf)に変更.元のコンデンサロープロファイル品なので、高さに注意してください.
私は頑張ってねじ込みました...

オペアンプはLVM358からOPA2353,OPA2211を試しました.
どちらも聴いた感じちゃんと動いているようですが、OPA2211では電源からノイズが乗りやすくなった気もします.
結局、音質面で高性能なOPA2211をチョイスしました. 動作電圧的にはギリギリかもしれません.
その他にも、AD8656やLME49721なんかも動くのかな?試してないですが.
パッケージはSOですのでMSOP版を買わないように注意しましょう.

5千円以内でこの音質が楽しめるなら十分コストパフォーマンスに優れると言えそうです.

本格的に電子工作に手を出すならオシロスコープがほしいものです.入門機でも結構いい値段するんですよね.
ではでは.