ぬの部屋(仮)
nu-no-he-ya
  •     123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
       1234
    567891011
    12131415161718
    19202122232425
    26272829   
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728     
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
     123456
    78910111213
    14151617181920
    21222324252627
    28      
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
    1234567
    891011121314
    15161718192021
    22232425262728
           
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
         12
    3456789
    10111213141516
    17181920212223
    242526272829 
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
        123
    45678910
    11121314151617
    18192021222324
    25262728   
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    15161718192021
    293031    
           
         12
    3456789
    10111213141516
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728     
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
          1
    2345678
    9101112131415
    16171819202122
    232425262728 
           
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
  • Gimp Script-Fuでxcf保存(gimp-xcf-save)

    保存にはgimp-xcf-saveを使う。

    ; 画像番号は (gimp-image-list) で取得できる数字。今回は「1」
    (gimp-xcf-save 
      0                                        ; ダミーパラメータ
      1                                        ; 画像番号
      (car (gimp-image-get-active-drawable 1)) ; 有効にしたいレイヤー(現在選択中のレイヤー)
      "c:/data/test.xcf"                       ; 保存ファイル名 URI形式
      "test.xcf"                               ; 保存ファイルのベース名
    )
    

    第一引数はダミーパラメータ。0でも入れておけばいい

    第二引数は保存したい画像。一度に複数の画像を開いていたり、開いた画像のいくつかを閉じたりすると「1」の画像がなくなる可能性がある。ので、「gimp-image-list」で確認するといい

    第三引数はアクティブにしたいレイヤー。(gimp-image-get-active-drawable image)で取得するのが無難。

    第四引数は保存するファイル名。URI形式で指定。

    第五引数はベースファイル名(ファイル名+拡張子)だが色々指定したが無視されているようにしか思えない。

    画素番号と画像幅からx,y座標を求める…のをScript-Fuでやる

    式自体は何のことはない。問題はGimpのScript-Fuでやりたいので結構手間取った。

    さすがにGimpのScript-Fuコンソールから実験するのは環境が悪すぎたので、RacketをインストールしてScheme環境を作った。

    https://racket-lang.org/

    Scheme版

    remainderは剰余。

    ;; 画素番号と画像幅からx座標を計算
    (define (getx pos width)
      (remainder pos width)
    )
    
    ;; x座標と画素番号と画像幅からy座標を計算し、x,yで返す
    (define (getxy x pos width)
      (list
        x
        (/ (- pos x) width)
      )
    )
    
    ;; 画素番号と画像幅からx,y座標を計算する
    (define (2dcoord pos width)
        (getxy (getx pos width) pos width)
    )
    

    Gimp Script-Fu上で実行

    width=50の画像の1162番のx,y座標は(12,23)。

    C++版

    // x,yから画素番号を算出
    inline int calcpos(int x, int y,int width) {
      return
        y * width + x;
    }
    
    // 画素番号と画像幅からx座標を計算
    int getx(int pos, int width) {
      return pos % width;
    }
    
    // 画素番号とx座標と画像幅からy座標を計算
    int gety(int x, int pos, int width) {
      return (pos - x) / width;
    }
    
    
    int main()
    {
      int width = 50;
      int height = 25;
    
      int x = 12;
      int y = 23;
    
      int pos = calcpos(x, y, width); //1162
    
      printf("pos %d\n", pos);
    
      int xx = getx(pos, width);
      int yy = gety(xx, pos, width);
    
      printf("xx %d\n", xx);
      printf("yy %d\n", yy);
    
    }
    

    Gimp Script-Fu 画像レイヤー,テキストレイヤー , パスをそれぞれ取得

    パス一覧

    パス一覧は (gimp-image-get-vectors 画像番号) で取得できる

    > (gimp-image-get-vectors 1)
    (2 #(10 9))
    > (gimp-layer-get-name 10)
    ("パス(曲線)")
    > (gimp-layer-get-name 9)
    ("パス(写真1)")

    レイヤー一覧

    gimp-image-get-layersで取得できるのだが、vectorではなくlistでほしいのでラップする。

    ; レイヤー一覧
    (define (get-layer-list image)
      (vector->list 
        (car 
          (cdr 
            (gimp-image-get-layers image)
          )
        )
      )
    )
    
    > (get-layer-list 1)
    (8 7 2 22)

    レイヤー一覧から、テキストレイヤーの一覧を取得する

    レイヤーに対して、gimp-drawable-is-text-layerでテキストレイヤーかどうかを判定できる。

    ; imagelistの中からテキストレイヤーだけを抽出する
    (define (get-text-layers imagelist)
      (if ( null? imagelist )
        '()
         (if (= 1 (car (gimp-drawable-is-text-layer (car imagelist) ) ) )
           (cons
             (car imagelist)
             (get-text-layers (cdr imagelist) )
           )
           (get-text-layers (cdr imagelist) )
         )
      )
    )
    
    > ( get-text-layers (get-layer-list 1) )
    (8 7)
    > (gimp-layer-get-name 8)
    ("青")
    > (gimp-layer-get-name 7)
    ("写真1")

    GimpのScript-Fuで画素を書き換える

    Script-Fuは フィルター → Script-Fu → Script-Fu コンソール からコンソールのウィンドウを開き一行ずつ入力できる。

    初めてのGimpのスクリプト。

    スクリプト

    ;; 画像一覧
    (define Images (vector->list (car (cdr (gimp-image-list)))))
    (define ImageIndex (car Images) )
    
    ;; ImageIndex内のレイヤー一覧
    (define Layers (vector->list (car (cdr (gimp-image-get-layers ImageIndex)))))
    (define LayerIndex (car Layers) )
    
    ;; 編集対象は以後Drawable
    (define Drawable LayerIndex)
    
    ;; 画像サイズ確認
    (gimp-drawable-width Drawable)
    (gimp-drawable-height Drawable)
    
    ;; 座標
    (define x 10)
    (define y 10)
    
    ;; ピクセルの更新
    (gimp-drawable-set-pixel Drawable x x 3 #(255 0 0) )
    (gimp-displays-flush)
    (gimp-drawable-update Drawable x y 1 1)
    

    実行結果

    参考

    https://developer.gimp.org/api/2.0/libgimp/libgimp-gimpdrawable.html

    std::variant

    C++でバリアント型を使う。C++17以降。

    基本的な使い方

    #include <cstdio>
    #include <string>
    #include <variant>
    
    enum VARIANT_TYPES {
      V_INT = 0,
      V_STR = 1,
      V_DBL = 2
    };
    
    int main()
    {
    
      std::variant<int, std::string, double> val;
    
      val = 2;
      //val = 5.5;
      //val = "abc";
    
      if (val.index() == V_INT) {
        int& x = std::get<V_INT>(val);
        printf("--- %d\n", x);
      }
    
    else if(val.index()==V_STR){ std::string& s = std::get<V_STR>(val); printf("--- %s\n", s.c_str()); }
    else if (val.index() == V_DBL) { double& d = std::get<V_DBL>(val); printf("--- %lf\n", d); } int k = getchar(); }

    std::visit

    std::variant型を引数で取る関数を作ってもいいが、std::visit関数でoperator()を定義したクラスのインスタンスを渡して呼び出す方が美しいらしい。

    #include <cstdio>
    #include <string>
    
    #include <variant>
    
    
    
    struct Print {
    
      // int が代入されたときに呼び出される
      void operator()(const int& data) {
        printf("--- %d\n", data);
      }
    
      // std::string が代入されたときに呼び出される
      void operator()(const std::string& data) {
        printf("--- %s\n", data.c_str());
      }
    
      // double が代入されたときに呼び出される
      void operator()(const double& data) {
        printf("--- %lf\n", data);
      }
    
    };
    int main()
    {
    
      std::variant<int, std::string, double> val;
    
      val = 2;
      std::visit(Print(), val);
    
      val = 5.5;
      std::visit(Print(), val);
    
      val = "abc";
      std::visit(Print(), val);
    
      int k = getchar();
    }
    

    std::visitは第一引数に関数、第二引数以降はstd::variant型でなければならない(らしい)。

    std::visit( 関数() , variant1 , variant2 , variant3 , … )

    なので、std::visitの引数で渡す場合、std::variantで渡さなければいけない。

    #include <cstdio>
    #include <string>
    
    #include <variant>
    
    
    struct Print {
    
      // int が代入されたときに呼び出される
      void operator()(const int& data) {
        printf("--- %d\n", data);
      }
    
      // std::string が代入されたときに呼び出される
      void operator()(const std::string& data) {
        printf("--- %s\n", data.c_str());
      }
    
      // double が代入されたときに呼び出される
      void operator()(const double& data) {
        printf("--- %lf\n", data);
      }
    
    };
    
    
    struct Add {
    
      // int が代入されたときに呼び出される
      void operator()(int& data, const int val) {
        data += val;
      }
    
      // std::string が代入されたときに呼び出される
      void operator()(std::string& data, const int val) {
        data += std::to_string(val);
      }
    
      // double が代入されたときに呼び出される
      void operator()(double& data, const int val) {
        data += val;
      }
    
    };
    int main()
    {
    
      std::variant<int, std::string, double> val;
    
    
      val = 2;
      std::visit( Add(), val,std::variant<int>(4) );
      std::visit(Print(),val);
    
      val = 5.5;
      std::visit( Add(), val, std::variant<int>(4));
      std::visit(Print(), val);
    
      val = "abc";
      std::visit( Add(), val, std::variant<int>(4));
      std::visit(Print(), val);
    
      int k = getchar();
    }
    

    引数を渡すならコンストラクタに渡した方がスマートだと思う。

    #include <cstdio>
    #include <string>
    
    #include <variant>
    
    
    
    struct Print {
    
      std::string _sign;
    public:
      Print(const std::string& sign):_sign(sign) {}
    
      // int が代入されたときに呼び出される
      void operator()(const int& data) {
        printf("%s %d\n",_sign.c_str(), data);
      }
    
      // std::string が代入されたときに呼び出される
      void operator()(const std::string& data) {
        printf("%s %s\n", _sign.c_str(), data.c_str());
      }
    
      // double が代入されたときに呼び出される
      void operator()(const double& data) {
        printf("%s %lf\n", _sign.c_str(), data);
      }
    
    };
    int main()
    {
    
      std::variant<int, std::string, double> val;
    
    
      val = 2;
      std::visit(Print(">>"),val);
    
      val = 5.5;
      std::visit(Print(">>"), val);
    
      val = "abc";
      std::visit(Print(">>"), val);
    
      int k = getchar();
    }
    

    二次元画像をクリップするクラスの作成

    PPM入出力:

    https://www.study.suzulang.com/cppppm-readerwriter

    NByteDataを扱うクラス

    https://www.study.suzulang.com/2dcg-functions/nbyte-data-type

    #include <cstdio>
    #include <vector>
    #include <cassert>
    #include "NByteData.hpp"
    
    #include "ppmP3_read.hpp"
    
    namespace szl{
    
      inline int calc_position2d(const int x, const int y, const int width) {
        return x + y * width;
      }
    
    
      //canvasの一部に矩形でアクセスできるためのクラス
      class D2CanvasWindow {
      public:
    
        //! @brief start ~ end の範囲の画素番号一覧
        std::vector<size_t> m_imgp;
        int startx,starty;
        int endx,endy;
        int canvaswidth; //!< 元画像の範囲
        int canvasheight;//!< 元画像の範囲
        inline int width() {return endx - startx + 1;}
        inline int height() {return endy - starty + 1;}
    
        //! @brief 元画像サイズとクリップサイズの設定
        //! @param [in] canvaswidth  元画像のサイズ
        //! @param [in] canvasheight 元画像のサイズ
        //! @param [in] sx 始点
        //! @param [in] sy 始点
        //! @param [in] ex 終点
        //! @param [in] ey 終点
        //! @return なし
        void set_size(const int canvaswidth, const int canvasheight, const int sx, const int sy, const int ex, const int ey) {
    
          startx = sx;
          starty = sy;
          endx = ex;
          endy = ey;
    
          const int Width = width();
          const int Height = height();
    
          m_imgp.resize(Width * Height);
    
          int wx, wy;// クリップする範囲の画素indexの一覧を作成
          for (int cx = sx; cx <= ex; cx++) {
            for (int cy = sy; cy <= ey; cy++) {
              wx = cx - sx;
              wy = cy - sy;
    
              int ci = calc_position2d(cx, cy, canvaswidth);
              int wi = calc_position2d(wx, wy, Width);
    
              m_imgp[wi] = ci;
            }
    
          }
        }
    
        //! @brief 画像を切り出す
        //! @param [in] dst 結果画像
        //! @param [in] src 元画像
        //! @return なし
        template<typename Canvas>
        void Clip(Canvas* dst, const Canvas& src) {
          
          assert(iwidth(*dst) == width());
          assert(iheight(*dst) == height());
    
          for(size_t i =0;i<m_imgp.size();i++){
            (*dst)[i] = src[m_imgp[i]];
          }
        }
    
      };
    }
    
    /////////////////////////////////////////////////////
    /////////////////////////////////////////////////////
    
    using PixelT = NByteData<3>;
    
    //画像データ
    struct Img2d {
      std::vector<PixelT> m_img;
      int width;
      int height;
      Img2d(int w, int h) {
        m_img.resize(w*h);
        width = w;
        height = h;
      }
      PixelT& operator[](const size_t index) {return m_img[index];}
      const PixelT& operator[](const size_t index)const {return m_img[index];}
    };
    
    //! @brief 画像の幅の取得関数
    //! @param [in] i 画像
    //! @return 画像の幅
    int iwidth(const Img2d& i) { return i.width; }
    
    //! @brief 画像の高さ取得関数
    //! @param [in] i 画像
    //! @return 画像の幅
    int iheight(const Img2d& i) { return i.height; }
    
    /////////////////////////////////////////////////////
    /////////////////////////////////////////////////////
    
    //! @brief 画像の書き込み
    void ppmP3_write(
      const char* const fname,
      const int width,
      const int height,
      const unsigned char* const p,
      const int vmax
    );
    
    
    int main()
    {
      // 画像読み込み
      PixelT* pimg;
      int width;
      int height;
      int vmax;
    
      ppmP3_read(
        "C:\\test\\p.ppm",
        &width,
        &height,
        &vmax,
        (unsigned char**)&pimg
      );
    
      Img2d img(width, height);
    
      for (size_t i = 0; i < width*height; i++) {
        img.m_img[i] = pimg[i];
      }
      delete[]pimg;
    
      /////////////////////////////////
      /////////////////////////////////
      // 画像のクリップ
    
      szl::D2CanvasWindow win;
    
      win.set_size(img.width,img.height, 2, 3, 8, 10);
      
      Img2d clip(win.width(), win.height());
      win.Clip(&clip, img);
    
      /////////////////////////////////
      /////////////////////////////////
      ppmP3_write(
        "C:\\test\\q.ppm",
        clip.width,
        clip.height,
        (unsigned char*)clip.m_img.data(), 255);
    
    }
    //! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む
    //! @param [in] fname ファイル名
    //! @param [in] width 画像の幅
    //! @param [in] height 画像の高さ
    //! @param [in] p 画像のメモリへのアドレス
    //! @param [in] vmax 全てのRGBの中の最大値。普通の画像なら255
    //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む
    void ppmP3_write(
      const char* const fname,
      const int width,
      const int height,
      const unsigned char* const p,
      const int vmax
    ) {
    
      FILE* fp = fopen(fname, "wb");
      fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax);
    
      size_t k = 0;
      for (size_t i = 0; i < (size_t)height; i++) {
        for (size_t j = 0; j < (size_t)width; j++) {
          fprintf(fp, "%d %d %d ",
            p[k * 3 + 0],
            p[k * 3 + 1],
            p[k * 3 + 2]
          );
          k++;
        }
        fprintf(fp, "\n");
      }
    
      fclose(fp);
    }
    

    Blender のスクリプトで拡散反射のベクトルを可視化する

    diffuse反射はPhongの反射モデルの一部で、ADSのDの部分。やってることは面法線と光のベクトルの内積を光の強さとしているといっていい(だろう。たぶん。)。ベクトルの内積は、その角度が小さくなると大きくなり、角度が大きくなると小さくなる。

    ただこの現象を文章で説明されてもぱっと思い浮かばないので可視化する

    これは面ごとにEmissionで色を付けている。

    角度が小さいほど明るい色になっている。

    コード

    実行すると、光源の位置に該当する球と、対象物に対する球、光源から物体の方向のArrowと各面の面法線、その位置からの光源へのベクトルが生成される。

    import bpy
    import mathutils
    import numpy
    import math
    
    
    
    # @brief オブジェクトを指定座標へ向けるための回転軸と回転角を計算する
    # @param [in] オブジェクト
    # @param [in] 向ける方向(座標)
    # @return 回転軸,回転角(ラジアン)
    def calc_lookat_param(obj,lookat):
        
        # オブジェクトのローカルのZ軸のベクトルを取得
        mat = obj.matrix_world
        localZ = mathutils.Vector((mat[0][2],mat[1][2],mat[2][2]))
        
        #print(localZ)
        
        va = mathutils.Vector(lookat) - obj.location
        vb = mathutils.Vector(localZ)
        
        va.normalize()
        vb.normalize()
        
        # 外積
        axis = mathutils.Vector.cross(va,vb)
        axis.normalize()
        
        # 内積
        th = mathutils.Vector.dot(va,vb)
        
        # 角度算出
        rad = -math.acos( numpy.clip(th,-1.0,1.0) )
    
        return axis , rad
        
    
        
    # @brief 任意軸回転
    # @param [in,out] obj 回転するオブジェクト
    # @param [in] axis 回転軸
    # @param [in] radian 回転角をラジアンで指定
    # @return なし
    # @sa https://www.study.suzulang.com/bpy-calculation/bpy-arbitrary-axis-rotation
    def rotate_object(obj,axis,radian):
        
        
        rot_mat= mathutils.Matrix.Rotation( radian, 4, axis ) 
        
        # decompose world_matrix's components, and from them assemble 4x4 matrices
        orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose()
        #
        orig_loc_mat   = mathutils.Matrix.Translation(orig_loc)
        orig_rot_mat   = orig_rot.to_matrix().to_4x4()
        orig_scale_mat = (mathutils.Matrix.Scale(orig_scale[0],4,(1,0,0)) @ 
                          mathutils.Matrix.Scale(orig_scale[1],4,(0,1,0)) @ 
                          mathutils.Matrix.Scale(orig_scale[2],4,(0,0,1)))
        #
        # assemble the new matrix
        obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat 
        
        
    
    def fix_arrow_dir(aobj,_to):
        # 方向を一致させるためのパラメータ取得
        axis , rad = calc_lookat_param(aobj,_to)
    
        # 回転
        rotate_object(aobj,axis,rad)
        
    def fix_arrow_fromto(aobj,_from,_to):
        
        aobj.location=_from
        
        fix_arrow_dir(aobj,_to)
    
        aobj.location=_from
    
        # _from → _to の距離を計算
        objlen = ( _from - _to ).length
    
        # 矢印の長さ==スケール
        aobj.scale[0] = objlen
        aobj.scale[1] = objlen
        aobj.scale[2] = objlen
        
    # @brief オブジェクトの指定したpolygonの法線を取得
    # @param [in] obj オブジェクト
    # @param [in] polyIndex ポリゴン番号
    # @return 法線(グローバル座標系)
    def get_polygon_normal(obj,polyIndex):
        # オブジェクトの行列を取得
        local44 = obj.matrix_world
        
        ll_inv = local44.inverted()
        ll_inv_norm = ll_inv.transposed().to_3x3()    
        
        
        vn = obj.data.polygons[polyIndex].normal
        
        return ll_inv_norm @ vn
    
    
    # @brief _fromから_toを指すEmptyArrowを作成
    # @param [in] 矢印の根元の座標
    # @param [in] 矢印の先端の座標
    # @return 作成したオブジェクト
    def make_empty_arrow(_from,_to):
    
        # 新しいEmpty ObjectのArrowを作成
        bpy.ops.object.empty_add(type="SINGLE_ARROW",location=_from)
        aobj = bpy.context.object
        fix_arrow_fromto(aobj,_from,_to)
    
        return aobj
    
    #####################################################
    #####################################################
    # 矢印一個のオブジェクト。
    class Arrow:
        _from = mathutils.Vector((0,0,0))
        _to = mathutils.Vector((0,0,0))
        _object = None
        
        def __init__(self,name):
            
            exist = bpy.context.scene.objects.get(name)
            if exist is None:
                bpy.ops.object.empty_add(type="SINGLE_ARROW",location=(0,0,0))
                self._object = bpy.context.object
                self._object.name = name
                
            else:
                self._object = exist
                
            #obj = bpy.context.active_object
            # オブジェクトのローカルのZ軸のベクトルを取得
            mat = self._object.matrix_world
            localZ = mathutils.Vector((mat[0][2],mat[1][2],mat[2][2]))
            
            _to = localZ + self._object.location
            
        
        def get_direction(self,normalize):
            v = _to - _from
            if normalize == True:
                v.normalize
            return v
        
        def get_arrow(self,normalize):
            pos = get_direction(normalize)
            __to = _from+pos
            return _from , __to
        
        def set_arrow_from_to(self,_from,_to):
    
            fix_arrow_fromto(self._object,_from,_to)
            self._from = _from
            self._to = _to
            
        def get_vnorm(self):
            v = self._to - self._from
            v.normalize()
            return v
        
        def set_length(self,length):
            self._object.scale[0]=length
            self._object.scale[1]=length
            self._object.scale[2]=length
    
    #####################################################
    #####################################################
    
    class TheLight:
        light = None
        arrowToTarget = None
    
        def __init__(self,_pos):
            self.light = bpy.context.scene.objects.get('TheLight')
            if self.light is None:
                # 新しいEmpty ObjectのArrowを作成
                bpy.ops.mesh.primitive_ico_sphere_add(location=_pos,radius =0.3)
                self.light = bpy.context.object
                
                self.light.name = 'TheLight'
                
        def update(self,target):
            self.arrowToTarget = Arrow('LightToTarget')
    
            self.arrowToTarget.set_arrow_from_to(
                self.light.location,
                target.target.location
            )
            self.arrowToTarget.set_length(2)
                
            
            
        def get_location(self):
            return self.light.location
    
    
    ###############################################
    ###############################################
    # 各面ごとに、面法線と光源の方向を示すための矢印を定義する
    class Plane:
        normal = None
        material_name = None
        plane_index = 0
        toLightArrow = None
        normalArrow_name = ""
        Ld = 0.0
        
        def __init__(self,obj,pindex):
            
            self.normalArrow_name = 'PlaneNormal-' + str(pindex)
            
            self.normal = Arrow(self.normalArrow_name)
        
            mat_name = "mat" + str(pindex)
            self.material_name = mat_name
            mat = bpy.data.materials.get(mat_name)
                
            # マテリアルがなければ作成する
            if mat is None:
                mat = bpy.data.materials.new(mat_name)
                    
                # マテリアルをオブジェクトに設定する
                obj.data.materials.append(mat)        
                mat.use_nodes = True
    
                self.material_name = mat_name
                self.plane_index = pindex
                obj.data.polygons[pindex].material_index = pindex
    
            
        def update(self,origin,dir,light):
            
            self.normal.set_arrow_from_to(
                origin,
                origin+dir)
                
                
            # direction
            ldir = light.get_location()-origin
            ldir.normalize()
            
            # 面に光が当たったときの明るさを計算
            # 面法線とライト方向へのベクトルの内積
            Kd = 1.0
            I = 1.0
            self.Ld = Kd*I*numpy.dot(
                self.normal.get_vnorm(),
                -light.arrowToTarget.get_vnorm()
                )
                
            if self.Ld < 0:
                self.Ld = 0
            else:
                # arrow-to-line
                lightarrowname = self.normalArrow_name +'-tolight'        
                self.toLightArrow = Arrow(lightarrowname)
                
                tolight = origin-light.arrowToTarget.get_vnorm()
                self.toLightArrow.set_arrow_from_to(origin,tolight)
                self.toLightArrow.set_length(0.3)
                    
        
            self.normal.set_length(self.Ld*0.5)
            
            
            
        def set_material(self,polyindex):
            
            material =bpy.data.materials.get(self.material_name)
            
            material_output = material.node_tree.nodes.get('Material Output')
    
            #emission = new_mat.node_tree.nodes.get('ShaderNodeEmission')
            emission = material.node_tree.nodes.get('Emission')
            if emission is None:
                print( 'Emission:',emission )
                emission = material.node_tree.nodes.new('ShaderNodeEmission')
            
                material.node_tree.links.new(material_output.inputs[0], emission.outputs[0])
            
            ntree = material.node_tree#.new('ShaderNodeEmission')
                    
            print( self.Ld )
            material.node_tree.nodes["Emission"].inputs["Color"].default_value[0] = 0.5 * self.Ld
            material.node_tree.nodes["Emission"].inputs["Color"].default_value[1] = 0.9 * self.Ld
            material.node_tree.nodes["Emission"].inputs["Color"].default_value[2] = 0.8 * self.Ld  
            
    
    
    ###############################################
    ###############################################
    class TheTarget:
        
        target = None # 光が当たるオブジェクト
        planes = [] # 各面の設定を面ごとに入れる
        
        def create_planes(self):
    
            mesh = self.target.data
            
            k = len(mesh.polygons)
            # 全てのポリゴンに対するループ
            for i in range(0,k):        
                                
                self.planes.append( Plane(self.target,i) )
            
            
        def update(self,light):
            
            mesh = self.target.data
            vcoords = mesh.vertices
            
            # オブジェクトの行列を取得
            local44 = self.target.matrix_world
                
            
            
            k = len(self.planes)
            for i in range(0,k):        
    
                n = get_polygon_normal(self.target,i)
            
                ## origin of polygon
                grav = mathutils.Vector((0,0,0))
                
                for v in mesh.polygons[i].vertices:
    
                    # v はpolyの各頂点のID
                    grav += vcoords[ v ].co
                    
                # 頂点座標の合計を頂点数で割る
                grav /= len(mesh.polygons[i].vertices)
                
                origin = local44 @ grav
            
                self.planes[i].update(
                    origin,
                    n,
                    light)
                    
                self.planes[i].set_material(self.target)
                
                
        def __init__(self,_pos):
            
            self.target = bpy.context.scene.objects.get('TargetMesh')
    
    
            if self.target is None:
                #bpy.ops.mesh.primitive_plane_add(location=_pos)
                bpy.ops.mesh.primitive_ico_sphere_add(
                    location=_pos,
                    radius =1.0
                )
    
                self.target = bpy.context.object
                
                self.target.name = 'TargetMesh'   
                
                
            self.create_planes()       
                
        
    ###############################################
    ###############################################
    ###############################################
    def my_function():
    
        target = TheTarget( (-1,2,1) )
        thelight = TheLight( (1,5,5) )
        thelight.update(target)
        target.update(thelight)
        
    ###############################################
    ###############################################
    ###############################################
    
    
    my_function()
    

    ppmP3_readのメモリ確保関数指定版

    度々使う自前の画像読み込み関数。メモリ確保を関数側にやらせると呼び出し側の処理が煩雑になるので、いっそのことメモリ確保関数を渡してしまうことにした。

    ppmP3_read.hpp

    #pragma once
    
    #include <stdio.h>
    #include <string>
    #include <functional>
    
    #pragma warning(disable:4996)
    
    //! @brief PPM(RGB各1byte,カラー,テキスト)を読み込む
    //! @param [in] fname
    //! @param [out] width 画像幅
    //! @param [out] height 画像高さ
    //! @param [out] vmax 最大値
    //! @param [in] memalloc メモリ確保用の関数
    //! @retval true 読み込み成功
    //! @retval false 読み込み失敗
    bool ppmP3_read(
      const char* const fname,
      int *width,
      int *height,
      int *vmax,
      std::function<unsigned char*(const size_t pixelcount)> memalloc
    ) {
      *width = -1;
      *height = -1;
      *vmax = -1;
    
      FILE* fp;
      fp = fopen(fname, "rb");
      char tmp[2048];
    
    
      char c;
      while (c = fgetc(fp)) {
        if (isspace(c))
          continue;
    
        if (c == 'P') { //フォーマットを特定する
          c = fgetc(fp) - '0';
          if (c != 3) {
            fclose(fp);
            return false;
          }
          continue;
        }
    
        if (c == '#') { //コメントを読み飛ばす
          while (c != '\r' && c != '\n')
            c = fgetc(fp);
          continue;
        }
    
        //幅取得
        if (*width < 0) {
          int s = 0;
          while (1) {
            if (isdigit(c)) {
              tmp[s++] = c;
              c = fgetc(fp);
            }
            else {
              tmp[s] = '\0';
              *width = atoi(tmp);
              break;
            }
          }
          continue;
        }
    
        //高さ取得
        if (*height < 0) {
          int s = 0;
          while (1) {
            if (isdigit(c)) {
              tmp[s++] = c;
              c = fgetc(fp);
            }
            else {
              tmp[s] = '\0';
              *height = atoi(tmp);
              break;
            }
          }
          continue;
        }
    
        //最大値(白)取得
        if (*vmax < 0) {
          int s = 0;
          while (1) {
            if (isdigit(c)) {
              tmp[s++] = c;
              c = fgetc(fp);
            }
            else {
              tmp[s] = '\0';
              *vmax = atoi(tmp);
              break;
            }
          }
          break;
        }
        else {
          break;
        }
      }
    
      if (*width < 0 || *height < 0 || *vmax < 0) {
        return false;
      }
    
    
      ////////////////////////////////////////
      //メモリ確保
      size_t count = *width* *height;
      size_t memsize = count*3;
      unsigned char* pimg = memalloc(count);
      ////////////////////////////////////////
      int read = 0;
    
      char text[100];
      int index = 0;
    
      //メモリサイズ分まで読み込む
      while (read < memsize) {
    
        int c = fgetc(fp);//一文字取得
    
        if (c == EOF)//入力できなかったらループから出る
          break;
    
        //数字ならストックする
        if (isdigit(c)) {
          text[index] = (char)c;
          index++;
        }
        else {
          //数字以外が入力されたら、
          //ストックした数字を数値化
          //ただし数字が何もストックされていないなら
          //なにもしない
          if (index) {
            text[index] = '\0';
            pimg[read] = atoi(text);
            read++;
            index = 0;
          }
        }
    
      }
      return read == memsize;
    }
    

    呼び出し例

    #include <vector>
    
    // https://www.study.suzulang.com/2dcg-functions/nbyte-data-type
    #include "NByteData.hpp"
    
    // 以下をベースに変更:
    // https://www.study.suzulang.com/cppppm-readerwriter/ppm-p3-reader-cpp
    #include "ppmP3_read.hpp"
    
    /////////////////////////////////////////////////////
    
    void ppmP3_write(
      const char* const fname,
      const int width,
      const int height,
      const unsigned char* const p,
      const int vmax
    );
    
    
    int main()
    {
      using PixelT = NByteData<3>;
    
      std::vector<PixelT> mem;
    
      // 画像読み込み
      int width;
      int height;
      int vmax;
    
      ppmP3_read(
        "C:\\test\\p.ppm",
        &width,
        &height,
        &vmax,
        [&mem](const size_t pixelcount) {
          mem.resize(pixelcount);
          return (unsigned char*)&mem[0];
        }
      );
    
      // 色を反転
      for(auto& p : mem){
        p.data()[0] = 255 - p.data()[0];
        p.data()[1] = 255 - p.data()[1];
        p.data()[2] = 255 - p.data()[2];
      }
    
      /////////////////////////////////
      /////////////////////////////////
      ppmP3_write(
        "C:\\test\\q.ppm",
        width,
        height,
        (unsigned char*)&mem[0], 255);
    
    }
    
    //! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む
    //! @param [in] fname ファイル名
    //! @param [in] width 画像の幅
    //! @param [in] height 画像の高さ
    //! @param [in] p 画像のメモリへのアドレス
    //! @param [in] vmax 全てのRGBの中の最大値。普通の画像なら255
    //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む
    void ppmP3_write(
      const char* const fname,
      const int width,
      const int height,
      const unsigned char* const p,
      const int vmax
    ) {
    
      FILE* fp = fopen(fname, "wb");
      fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax);
    
      size_t k = 0;
      for (size_t i = 0; i < (size_t)height; i++) {
        for (size_t j = 0; j < (size_t)width; j++) {
          fprintf(fp, "%d %d %d ",
            p[k * 3 + 0],
            p[k * 3 + 1],
            p[k * 3 + 2]
          );
          k++;
        }
        fprintf(fp, "\n");
      }
    
      fclose(fp);
    }
    

    C++でOpen3DのVisualizerを使う

    C++の例が少ないので載せておく。

    #include <Open3D/IO/ClassIO/TriangleMeshIO.h>
    #include<Open3D/Geometry/TriangleMesh.h>
    
    // 表示に必要
    #include<Open3D/Visualization/Visualizer/Visualizer.h>
    
    // 表示に必要
    #pragma comment(lib,"glew.lib")
    #pragma comment(lib,"glfw3.lib")
    #pragma comment(lib,"opengl32.lib")
    
    int main()
    {
    
      auto mesh = std::make_shared<open3d::geometry::TriangleMesh>();
      open3d::io::ReadTriangleMeshFromPLY("bunny.ply",*mesh,true);
    
      //これがないとライトで照らせない
      mesh->ComputeTriangleNormals(true);
    
    
      open3d::visualization::Visualizer vis;
    
      vis.CreateVisualizerWindow("O3D");
    
    
      //CreateVisualizerWindowを呼び出してからオブジェクトを登録
      vis.AddGeometry(mesh);
    
      Eigen::Vector3d center = mesh->GetCenter();
    
      // Change view
      open3d::visualization::ViewControl& view_control = vis.GetViewControl();
      auto view_params = open3d::visualization::ViewParameters();
      view_control.ConvertToViewParameters(view_params);
    
      view_params.front_ = Eigen::Vector3d(0, -1, 0);
      view_params.lookat_ = center;
      view_params.up_ = Eigen::Vector3d(0, 0, 1);
      //view_params.zoom_ = 1.0;
      view_control.ConvertFromViewParameters(view_params);
    
      // PollEventsはウィンドウが閉じられるときにfalseを返す
      while (vis.PollEvents() == true)
      {
        vis.UpdateGeometry();
        vis.UpdateRender();
      }
    
      vis.DestroyVisualizerWindow();
    }
    

    C++でモデルを表示するためのコードをBlender Pythonから出力する。

    試験的なプログラムを書くだけでいちいちモデルを読み込むライブラリを使うのが面倒なので、Blender PythonでC++OpenGLのコードをprintする。まだやってなかったっけという感じなんだがやっていたら申し訳ない。

    import bpy
    import sys
    
    def as_cpp_array(outputto):
        obj = bpy.context.active_object
    
        mesh = obj.data
        
        pvecname = "points"   # 頂点データの配列の変数名
        vnvecname = "vnorms"  # 頂点法線の配列の変数名
        fvecname = "faces"    # 面データの配列の変数名
        fnvecname = "fnorms"  # 面法線の配列の変数名
    
        print("/////////////////////////////////////")
    
        if not pvecname is None:
            print("//std::vector<std::array<float, 3> > ",pvecname,";",file=outputto)
    
        if not vnvecname  is None:
            print("//std::vector<std::array<float, 3> > ",vnvecname,";",file=outputto)
    
        if not fvecname  is None:
            print("//std::vector<std::array<int, 3> > ",fvecname,";",file=outputto)
    
        if not fnvecname  is None:
            print("//std::vector<std::array<float, 3> > ",fnvecname,";",file=outputto)
        
        
        if not pvecname is None:
            for v in mesh.vertices:
                print(
                    pvecname +
                    ".push_back({",
                    v.co.x,",",
                    v.co.y,",",
                    v.co.z,
                    "});",
                    file=outputto
                    )
                    
        if not vnvecname is None:
            for v in mesh.vertices:
                print(
                    vnvecname +
                    ".push_back({",
                    v.normal.x,",",
                    v.normal.y,",",
                    v.normal.z,
                    "});",
                    file=outputto
                    )
    
    
    
        if not fvecname is None:
            for f in mesh.polygons:
                print(
                    fvecname + 
                    ".push_back({",
                    f.vertices[0],",",
                    f.vertices[1],",",
                    f.vertices[2],
                    "});",
                    file=outputto                
                    )
                    
        if not fnvecname is None:
            for f in mesh.polygons:
                print(
                    fnvecname +
                    ".push_back({",
                    f.normal.x,",",
                    f.normal.y,",",
                    f.normal.z,
                    "});",
                    file=outputto
                    )
                        
            
    
    as_cpp_array(sys.stdout)
    

    これをactive_objectに対して走らせると、例えば以下のような結果が得られる。

    /////////////////////////////////////
    //std::vector<std::array<float, 3> > points ;
    //std::vector<std::array<float, 3> > vnorms ;
    //std::vector<std::array<int, 3> > faces ;
    //std::vector<std::array<float, 3> > fnorms ;
    points.push_back({ 0.0 , 0.0 , -0.57135009765625 });
    points.push_back({ 0.4134289026260376 , -0.3003701865673065 , -0.25551632046699524 });
    points.push_back({ -0.15791259706020355 , -0.4860132336616516 , -0.25551632046699524 });
    points.push_back({ -0.5110297799110413 , 0.0 , -0.25551632046699524 });
    points.push_back({ -0.15791259706020355 , 0.4860132336616516 , -0.25551632046699524 });
    points.push_back({ 0.4134289026260376 , 0.3003701865673065 , -0.25551632046699524 });
    points.push_back({ 0.15791259706020355 , -0.4860132336616516 , 0.25551632046699524 });
    points.push_back({ -0.4134289026260376 , -0.3003701865673065 , 0.25551632046699524 });
    points.push_back({ -0.4134289026260376 , 0.3003701865673065 , 0.25551632046699524 });
    points.push_back({ 0.15791259706020355 , 0.4860132336616516 , 0.25551632046699524 });
    points.push_back({ 0.5110297799110413 , 0.0 , 0.25551632046699524 });
    points.push_back({ 0.0 , 0.0 , 0.57135009765625 });
    vnorms.push_back({ 0.0 , 0.0 , -1.0 });
    vnorms.push_back({ 0.7235938310623169 , -0.5257118344306946 , -0.44718772172927856 });
    vnorms.push_back({ -0.2763756215572357 , -0.8506424427032471 , -0.44718772172927856 });
    vnorms.push_back({ -0.8944059610366821 , 0.0 , -0.44718772172927856 });
    vnorms.push_back({ -0.2763756215572357 , 0.8506424427032471 , -0.44718772172927856 });
    vnorms.push_back({ 0.7235938310623169 , 0.5257118344306946 , -0.44718772172927856 });
    vnorms.push_back({ 0.2763756215572357 , -0.8506424427032471 , 0.44718772172927856 });
    vnorms.push_back({ -0.7235938310623169 , -0.5257118344306946 , 0.44718772172927856 });
    vnorms.push_back({ -0.7235938310623169 , 0.5257118344306946 , 0.44718772172927856 });
    vnorms.push_back({ 0.2763756215572357 , 0.8506424427032471 , 0.44718772172927856 });
    vnorms.push_back({ 0.8944059610366821 , 0.0 , 0.44718772172927856 });
    vnorms.push_back({ 0.0 , 0.0 , 1.0 });
    faces.push_back({ 0 , 1 , 2 });
    faces.push_back({ 1 , 0 , 5 });
    faces.push_back({ 0 , 2 , 3 });
    faces.push_back({ 0 , 3 , 4 });
    faces.push_back({ 0 , 4 , 5 });
    faces.push_back({ 1 , 5 , 10 });
    faces.push_back({ 2 , 1 , 6 });
    faces.push_back({ 3 , 2 , 7 });
    faces.push_back({ 4 , 3 , 8 });
    faces.push_back({ 5 , 4 , 9 });
    faces.push_back({ 1 , 10 , 6 });
    faces.push_back({ 2 , 6 , 7 });
    faces.push_back({ 3 , 7 , 8 });
    faces.push_back({ 4 , 8 , 9 });
    faces.push_back({ 5 , 9 , 10 });
    faces.push_back({ 6 , 10 , 11 });
    faces.push_back({ 7 , 6 , 11 });
    faces.push_back({ 8 , 7 , 11 });
    faces.push_back({ 9 , 8 , 11 });
    faces.push_back({ 10 , 9 , 11 });
    fnorms.push_back({ 0.18759654462337494 , -0.5773536562919617 , -0.7946510910987854 });
    fnorms.push_back({ 0.6070646643638611 , 0.0 , -0.7946524620056152 });
    fnorms.push_back({ -0.4911220967769623 , -0.35682904720306396 , -0.7946522235870361 });
    fnorms.push_back({ -0.4911220967769623 , 0.35682904720306396 , -0.7946522235870361 });
    fnorms.push_back({ 0.18759654462337494 , 0.5773536562919617 , -0.7946510910987854 });
    fnorms.push_back({ 0.9822461009025574 , 0.0 , -0.18759679794311523 });
    fnorms.push_back({ 0.3035355508327484 , -0.9341715574264526 , -0.1875891536474228 });
    fnorms.push_back({ -0.7946491241455078 , -0.5773593187332153 , -0.1875869631767273 });
    fnorms.push_back({ -0.7946491241455078 , 0.5773593187332153 , -0.1875869780778885 });
    fnorms.push_back({ 0.3035355508327484 , 0.9341715574264526 , -0.1875891238451004 });
    fnorms.push_back({ 0.7946491241455078 , -0.5773593187332153 , 0.1875869780778885 });
    fnorms.push_back({ -0.3035355508327484 , -0.9341715574264526 , 0.1875891238451004 });
    fnorms.push_back({ -0.9822461009025574 , 0.0 , 0.18759679794311523 });
    fnorms.push_back({ -0.3035355508327484 , 0.9341715574264526 , 0.1875891536474228 });
    fnorms.push_back({ 0.7946491241455078 , 0.5773593187332153 , 0.1875869631767273 });
    fnorms.push_back({ 0.4911220967769623 , -0.35682904720306396 , 0.7946522235870361 });
    fnorms.push_back({ -0.18759654462337494 , -0.5773536562919617 , 0.7946510910987854 });
    fnorms.push_back({ -0.6070646643638611 , 0.0 , 0.7946524620056152 });
    fnorms.push_back({ -0.18759654462337494 , 0.5773536562919617 , 0.7946510910987854 });
    fnorms.push_back({ 0.4911220967769623 , 0.35682904720306396 , 0.7946522235870361 });

    これをC++で書いたOpenGLの表示コードに貼り付ける。

    #include <iostream>
    #include <cstdlib>
    
    #include <Windows.h>
    #include <gl/GL.h>
    #include <gl/glfw/glfw3.h>
    
    #pragma comment(lib,"opengl32.lib")
    #pragma comment(lib,"glfw3.lib")
    
    #include <gl/GLU.h>
    #pragma comment(lib,"glu32.lib")
    
    #include<vector>
    #include<array>
    
    
    struct d3item {
      std::vector<std::array<float, 3> >  points;
      std::vector<std::array<float, 3> >  vnorms;
      std::vector<std::array<int, 3> >  faces;
      std::vector<std::array<float, 3> >  fnorms;
    
    public:
      void init() {
    
        points.push_back({ 0.0 , 0.0 , -0.57135009765625 });
        points.push_back({ 0.4134289026260376 , -0.3003701865673065 , -0.25551632046699524 });
        points.push_back({ -0.15791259706020355 , -0.4860132336616516 , -0.25551632046699524 });
        points.push_back({ -0.5110297799110413 , 0.0 , -0.25551632046699524 });
        points.push_back({ -0.15791259706020355 , 0.4860132336616516 , -0.25551632046699524 });
        points.push_back({ 0.4134289026260376 , 0.3003701865673065 , -0.25551632046699524 });
        points.push_back({ 0.15791259706020355 , -0.4860132336616516 , 0.25551632046699524 });
        points.push_back({ -0.4134289026260376 , -0.3003701865673065 , 0.25551632046699524 });
        points.push_back({ -0.4134289026260376 , 0.3003701865673065 , 0.25551632046699524 });
        points.push_back({ 0.15791259706020355 , 0.4860132336616516 , 0.25551632046699524 });
        points.push_back({ 0.5110297799110413 , 0.0 , 0.25551632046699524 });
        points.push_back({ 0.0 , 0.0 , 0.57135009765625 });
        vnorms.push_back({ 0.0 , 0.0 , -1.0 });
        vnorms.push_back({ 0.7235938310623169 , -0.5257118344306946 , -0.44718772172927856 });
        vnorms.push_back({ -0.2763756215572357 , -0.8506424427032471 , -0.44718772172927856 });
        vnorms.push_back({ -0.8944059610366821 , 0.0 , -0.44718772172927856 });
        vnorms.push_back({ -0.2763756215572357 , 0.8506424427032471 , -0.44718772172927856 });
        vnorms.push_back({ 0.7235938310623169 , 0.5257118344306946 , -0.44718772172927856 });
        vnorms.push_back({ 0.2763756215572357 , -0.8506424427032471 , 0.44718772172927856 });
        vnorms.push_back({ -0.7235938310623169 , -0.5257118344306946 , 0.44718772172927856 });
        vnorms.push_back({ -0.7235938310623169 , 0.5257118344306946 , 0.44718772172927856 });
        vnorms.push_back({ 0.2763756215572357 , 0.8506424427032471 , 0.44718772172927856 });
        vnorms.push_back({ 0.8944059610366821 , 0.0 , 0.44718772172927856 });
        vnorms.push_back({ 0.0 , 0.0 , 1.0 });
        faces.push_back({ 0 , 1 , 2 });
        faces.push_back({ 1 , 0 , 5 });
        faces.push_back({ 0 , 2 , 3 });
        faces.push_back({ 0 , 3 , 4 });
        faces.push_back({ 0 , 4 , 5 });
        faces.push_back({ 1 , 5 , 10 });
        faces.push_back({ 2 , 1 , 6 });
        faces.push_back({ 3 , 2 , 7 });
        faces.push_back({ 4 , 3 , 8 });
        faces.push_back({ 5 , 4 , 9 });
        faces.push_back({ 1 , 10 , 6 });
        faces.push_back({ 2 , 6 , 7 });
        faces.push_back({ 3 , 7 , 8 });
        faces.push_back({ 4 , 8 , 9 });
        faces.push_back({ 5 , 9 , 10 });
        faces.push_back({ 6 , 10 , 11 });
        faces.push_back({ 7 , 6 , 11 });
        faces.push_back({ 8 , 7 , 11 });
        faces.push_back({ 9 , 8 , 11 });
        faces.push_back({ 10 , 9 , 11 });
        fnorms.push_back({ 0.18759654462337494 , -0.5773536562919617 , -0.7946510910987854 });
        fnorms.push_back({ 0.6070646643638611 , 0.0 , -0.7946524620056152 });
        fnorms.push_back({ -0.4911220967769623 , -0.35682904720306396 , -0.7946522235870361 });
        fnorms.push_back({ -0.4911220967769623 , 0.35682904720306396 , -0.7946522235870361 });
        fnorms.push_back({ 0.18759654462337494 , 0.5773536562919617 , -0.7946510910987854 });
        fnorms.push_back({ 0.9822461009025574 , 0.0 , -0.18759679794311523 });
        fnorms.push_back({ 0.3035355508327484 , -0.9341715574264526 , -0.1875891536474228 });
        fnorms.push_back({ -0.7946491241455078 , -0.5773593187332153 , -0.1875869631767273 });
        fnorms.push_back({ -0.7946491241455078 , 0.5773593187332153 , -0.1875869780778885 });
        fnorms.push_back({ 0.3035355508327484 , 0.9341715574264526 , -0.1875891238451004 });
        fnorms.push_back({ 0.7946491241455078 , -0.5773593187332153 , 0.1875869780778885 });
        fnorms.push_back({ -0.3035355508327484 , -0.9341715574264526 , 0.1875891238451004 });
        fnorms.push_back({ -0.9822461009025574 , 0.0 , 0.18759679794311523 });
        fnorms.push_back({ -0.3035355508327484 , 0.9341715574264526 , 0.1875891536474228 });
        fnorms.push_back({ 0.7946491241455078 , 0.5773593187332153 , 0.1875869631767273 });
        fnorms.push_back({ 0.4911220967769623 , -0.35682904720306396 , 0.7946522235870361 });
        fnorms.push_back({ -0.18759654462337494 , -0.5773536562919617 , 0.7946510910987854 });
        fnorms.push_back({ -0.6070646643638611 , 0.0 , 0.7946524620056152 });
        fnorms.push_back({ -0.18759654462337494 , 0.5773536562919617 , 0.7946510910987854 });
        fnorms.push_back({ 0.4911220967769623 , 0.35682904720306396 , 0.7946522235870361 });
      }
      void draw() {
    
        auto color = std::array<float, 4>{1.0, 0, 0, 1};
        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color.data());
        glColor3d(1, 1, 1);
        glBegin(GL_TRIANGLES);
        for (size_t f = 0; f < faces.size(); f++) {
          
          //面法線
          glNormal3fv(fnorms[f].data());
    
          size_t p0 = faces[f][0];
          size_t p1 = faces[f][1];
          size_t p2 = faces[f][2];
    
          //頂点法線
          //glNormal3fv(vnorms[p0].data());
          glVertex3fv(points[p0].data());
    
          //頂点法線
          //glNormal3fv(vnorms[p1].data());
          glVertex3fv(points[p1].data());
    
          //頂点法線
          //glNormal3fv(vnorms[p2].data());
          glVertex3fv(points[p2].data());
        }
        glEnd();
    
      }
    };
    int main()
    {
      ////////////////////////////////////////////////////////////////////////////////
      // GLFW の初期化
      if (glfwInit() == GL_FALSE) {
        // 初期化に失敗したら終了
        return 1;
      }
    
      ////////////////////////////////////////////////////////////////////////////////
      // ウィンドウを作成
      GLFWwindow* window = glfwCreateWindow(
        400,           //width
        400,           //height
        "window title",//title 
        NULL,          //monitor
        NULL           //share
      );
    
      ////////////////////////////////////////////////////////////////////////////////
      // ウィンドウを作成できなければ終了
      if (window == nullptr) {
        glfwTerminate();
        return 1;
      }
    
      d3item item;
      item.init();
    
      glfwMakeContextCurrent(window);
    
      while (glfwWindowShouldClose(window) == GL_FALSE) {
    
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);
    
        const GLfloat lightPos[] = { -3 , 0 , 0 , 1 };
        const GLfloat lightCol[] = { 1 , 1 , 1 , 1 };
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, lightCol);
        glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    
        glEnable(GL_DEPTH_TEST);
    
        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        /////////////////////
        // 描画
        glViewport(0, 0, width, height);
    
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glLoadIdentity();
        gluPerspective(45, width / (double)height, 0.01, 10);
        {
          glMatrixMode(GL_MODELVIEW);
          glPushMatrix();
          glLoadIdentity();
    
          static int angle = 0;
    
          glTranslated(0, 0, -2);
          glRotated(angle++, 1, 1, 1);
    
          item.draw();
    
          glPopMatrix();
        }
        glMatrixMode(GL_PROJECTION);
        glPopMatrix();
    
        //
        /////////////////////
    
        //glFlush();
    
        glfwSwapBuffers(window);
    
    
        // イベント取得
        glfwWaitEvents();
    
      }
    
      glfwTerminate();
    
    }