ルギア君の戯言

雑多な記事。

5年2月27日 (月)

みんな「みーっつ! みんな笑顔であかるい世界!」
ドンちゃん「よーし、仕事始め!」


・・・


ルギア君「やっとできたよ。」
キリルン「そうですか。」

/* 
 * ルギア君はこのプログラムに対する著作権は破棄したいです。 
 * 研究目的でも商用でも何でも御自由にお使いください。
 */
#include <iostream>
#include <fstream>
#include <sstream>
#include <list>
#include <string>
#include <cstring>
#include <cctype>
#include <cerrno>
#include <locale>

using namespace std;

struct point {
  double x;
  double y;
  double z;
  double r;
  
  bool operator==(point p) {
    return x == p.x && y == p.y && z == p.z && r == p.r;
  }
  
  bool operator!=(point p) {
    return x != p.x || y != p.y || z != p.z || r != p.z;
  }
};

struct svg_path_set {
  char command;
  list<double> coords;
};

int main(int argc, char** argv) {
  setlocale(LC_ALL, "");
  ifstream fin;
  if(argc >= 2 && strcmp(argv[1], "-h") == 0) {
    clog << argv[0] << " [svg command]" << endl;
    clog << "SVG TO POV-Ray Prism 変換器" << endl;
    clog << endl;
    clog << "このプログラムは" << endl;
    clog << "    開始 (m/M) コマンド" << endl;
    clog << "    終端 (z/Z) コマンド" << endl;
    clog << "    3次ベジエ曲線 (c/C/s/S) コマンド" << endl;
    clog << "    直線 (l/L/h/H/v/V) コマンド" << endl;
    clog << "に対応しています。" << endl;
    clog << endl;
    clog << "<path> タグの d 属性の内容を入力してください。" << endl;
    clog << "出力は stdout に行われます。" << endl;
    return 0377;
  } else if(argc >= 3 && strcmp(argv[1], "-i") == 0) {
    clog << "ファイル: " << argv[2] << endl;
    fin.open(argv[2]);
    if(!fin) {
      cerr << strerror(errno) << endl;
      return 0373;
    }
  }
  
  list<string> tmp;
  list<svg_path_set> svg_data;
  // load data.
  if(fin.is_open()) {
    // load from file.
    while(fin) {
      string t;
      fin >> t;
      tmp.push_back(t);
    }
    fin.close();
  } else if(argc > 1) {
    // load from parameter.
    for(int i = 1; i < argc; i++) {
      tmp.push_back(argv[i]);
    }
  } else {
    // load from stdin.
    while(cin) {
      string t;
      cin >> t;
      tmp.push_back(t);
    }
    cin.clear();
  }
  list<string>::iterator pls;    // "l"ist "s"tring.
  svg_path_set tmp_set;
  for(pls = tmp.begin(); pls != tmp.end(); pls++) {
    istringstream istr;
    istr.clear();
    istr.str(*pls);
    while(istr) {
      char c = istr.get();
      if(istr.eof()) break;
      if((c >= '0' && c <= '9') || c == '-') { // numeral
        double d;
        istr.seekg(-1, ios::cur);
        istr >> d;
        clog << "座標値: " << d << endl;
        tmp_set.coords.push_back(d);
      } else if(c == ',') {    // division comma.
        continue;
      } else if(isalpha(c)) {    // command.
        svg_data.push_back(tmp_set);
        tmp_set.command = c;
        clog << "コマンド: " << c << endl;
        tmp_set.coords.clear();
      } else {
        cerr << "不明な文字: 0x" << hex << ((int)c & 0xFF) << endl;
        return 0374;
      }
    }
  }
  svg_data.push_back(tmp_set);    // flush the data.
  tmp_set.coords.clear();    // clear the memory.
  
  // question.
  double height1, height2;
  string dummy;
  string type;
  enum { linear_prism, conic_prism, lathe, sphere_sweep } model_type;
  do {
    clog << "種類 ?" << endl;
    clog << "  角柱 (linear prism)       -> lp" << endl;
    clog << "  角錐 (conic prism)        -> cp" << endl;
    clog << "  回転体 (lathe)            -> la" << endl;
    clog << "  球スイープ (sphere sweep) -> ss" << endl;
    clog << "  キャンセル (cancel)       -> cc" << endl;
    clog << ">";
    cin >> type;
    if(!cin) { cin.clear(); continue; }
    if(type == "cc") return 0376;
    else if(type == "lp") { model_type = linear_prism; break; }
    else if(type == "cp") { model_type = conic_prism; break; }
    else if(type == "la") { model_type = lathe; break; }
    else if(type == "ss") { model_type = sphere_sweep; break; }
  } while(1);
  
  switch(model_type) {
    case linear_prism:
    case conic_prism:
      do {
        clog << "高さ1 ? >";
        cin >> height1;
      } while(!cin && (cin.clear(), cin >> dummy));
      
      do {
        clog << "高さ2 ? >";
        cin >> height2;
      } while(!cin && (cin.clear(), cin >> dummy));
      break;
      
    case lathe:
    case sphere_sweep:
    default:
      break;
  }
  
  double scale_x, scale_y;
  double move_x, move_y;
  
  do {
    clog << "X (U) 方向の拡大率 >";
    cin >> scale_x;
  } while(!cin && (cin.clear(), cin >> dummy));
  
  do {
    clog << "Y (V) 方向の拡大率 >";
    cin >> scale_y;
  } while(!cin && (cin.clear(), cin >> dummy));
  
  do {
    clog << "X (U) 方向の平行移動量 >";
    cin >> move_x;
  } while(!cin && (cin.clear(), cin >> dummy));
  
  do {
    clog << "Y (V) 方向の平行移動量 >";
    cin >> move_y;
  } while(!cin && (cin.clear(), cin >> dummy));
  
  enum { ms, sm } apply_dir;
  do {
    clog << "適用順序 ?" << endl;
    clog << "  移動 後 拡縮 (move -> scale) -> ms" << endl;
    clog << "  拡縮 後 移動 (scale -> move) -> sm" << endl;
    clog << "  キャンセル (cancel)          -> cc" << endl;
    clog << ">";
    cin >> type;
    if(!cin) { cin.clear(); continue; }
    if(type == "cc") return 0376;
    else if(type == "ms") { apply_dir = ms; break; }
    else if(type == "sm") { apply_dir = sm; break; }
  } while(1);
  
  // analyze the data.
  list<svg_path_set>::iterator plv;
  list<double>::iterator pld;
  point p, old_p = {0.0, 0.0, 0.0, 0.0};
  list<point> tmp_pts;
  list<list<point> > out_pts;
  
  tmp_pts.clear();
  
  for(plv = svg_data.begin(); plv != svg_data.end(); plv++) {
    int c_mode_count = 0;
    int s_mode_count = 0;
    
    if(plv->command == 'z' || plv->command == 'Z') {
      switch(model_type) {
        case linear_prism:
        case conic_prism:
          if(tmp_pts.front() != tmp_pts.back()) {
            tmp_pts.push_back(tmp_pts.back());
            tmp_pts.push_back(tmp_pts.back());
            tmp_pts.push_back(tmp_pts.front());
            tmp_pts.push_back(tmp_pts.front());
          }
          break;
        case lathe:
        case sphere_sweep:
          break;
      }
      out_pts.push_back(tmp_pts);
      tmp_pts.clear();
      continue;
    }
    for(pld = plv->coords.begin(); pld != plv->coords.end(); ) {
      if(plv->command != 'v' && plv->command != 'V') {
        p.x = *pld;
        pld++;    // first point use as x.
      } else if(plv->command == 'v') {
        p.x = 0.0;
      } else {
        p.x = old_p.x;
      }
      if(plv->command != 'h' && plv->command != 'H') {
        if(pld == plv->coords.end()) {
          cerr << "座標が奇数個しかありません。X 座標と Y 座標をそれぞれ指定してください。" << endl;
          return 0376;
        }
        p.y = *pld;
        pld++;    // second point use as y.
      } else if(plv->command == 'h') {
        p.y = 0.0;
      } else {
        p.y = old_p.y;
      }
      if(model_type == sphere_sweep) {
        do {
          clog << "点 (" << p.x << ", " << p.y << ") での Z 座標 ? >";
          cin >> p.z;
        } while(!cin && (cin.clear(), cin >> dummy));
        
        do {
          clog << "点 (" << p.x << ", " << p.y << ") での球半径 ? >";
          cin >> p.r;
        } while(!cin && (cin.clear(), cin >> dummy));
      } else {
        p.z = 0.0;
        p.r = 0.0;
      }
      if(islower(plv->command)) {
        // relative.
        p.x = old_p.x + p.x;
        p.y = old_p.y + p.y;
      }
      
      clog << "コマンド: " << plv->command;
      clog << " / 現在点: (" << p.x << ", " << p.y << ") / 前点: (";
      clog << old_p.x << ", " << old_p.y << ")" << endl;
      switch(plv->command) {
        case 'm':
        case 'M':
          switch(model_type) {
            case linear_prism:
            case conic_prism:
              if((!tmp_pts.empty()) && tmp_pts.front() != tmp_pts.back()) {
                tmp_pts.push_back(tmp_pts.back());
                tmp_pts.push_back(tmp_pts.back());
                tmp_pts.push_back(tmp_pts.front());
                tmp_pts.push_back(tmp_pts.front());
              }
              break;
            case lathe:
            case sphere_sweep:
              break;
          }
          if(!tmp_pts.empty()) {
            out_pts.push_back(tmp_pts);
          }
          tmp_pts.clear();
          old_p = p;
          break;
        case 'l':
        case 'L':
        case 'h':
        case 'H':
        case 'v':
        case 'V':
          tmp_pts.push_back(old_p);
          tmp_pts.push_back(old_p);
          tmp_pts.push_back(p);
          tmp_pts.push_back(p);
          old_p = p;
          break;
        case 'c':
        case 'C':
          if(c_mode_count % 3 == 0) {
            tmp_pts.push_back(old_p);
          }
          tmp_pts.push_back(p);
          if(c_mode_count % 3 == 2) {
            old_p = p;
          }
          c_mode_count++;
          break;
        case 's':
        case 'S':
          if(s_mode_count % 2 == 0) {
            tmp_pts.push_back(old_p);
            tmp_pts.push_back(old_p);
          }
          tmp_pts.push_back(p);
          if(s_mode_count % 2 == 1) {
            old_p = p;
          }
          c_mode_count++;
          break;
        default:
          cerr << "不明なコマンド: " << plv->command << endl;
          return 0374;
      }
    }
  }
  
  // transform
  list<point>::iterator plp;
  list<list<point> >::iterator pllp;
  int total_num = 0;
  for(pllp = out_pts.begin(); pllp != out_pts.end(); pllp++) {
    for(plp = pllp->begin(); plp != pllp->end(); plp++) {
      switch(apply_dir) {
        case sm:
          plp->x = plp->x * scale_x + move_x;
          plp->y = plp->y * scale_y + move_y;
          break;
        case ms:
          plp->x = (plp->x + move_x) * scale_x;
          plp->y = (plp->y + move_y) * scale_y;
          break;
      }
      total_num++;
    }
  }
  
  // output.
  setlocale(LC_ALL, "C");
  int cnt;
  list<point>::iterator pllast;
  list<list<point> >::iterator plllast;
  plllast = out_pts.end();
  plllast--;
  for(pllp = out_pts.begin(); pllp != out_pts.end(); pllp++) {
    cnt = 0;
    switch(model_type) {
      case linear_prism:
        if(pllp == out_pts.begin()) {
          cout << "prism {" << endl;
          cout << "\tbezier_spline\n\tlinear_sweep" << endl;
          cout << "\t" << height1 << "," << height2 << endl;
          cout << "\t" << total_num << ",";
        }
        break;
      case conic_prism:
        if(pllp == out_pts.begin()) {
          cout << "prism {" << endl;
          cout << "\tbezier_spline\n\tconic_sweep" << endl;
          cout << "\t" << height1 << "," << height2 << endl;
          cout << "\t" << total_num << ",";
        }
        break;
      case lathe:
        cout << "lathe {" << endl;
        cout << "\tbezier_spline" << endl;
        cout << "\t" << pllp->size() << ",";
        break;
      case sphere_sweep:
        cout << "sphere_sweep {" << endl;
        cout << "\tb_spline," << endl;
        cout << "\t" << pllp->size() << ",";
        break;
    }
    pllast = pllp->end();
    pllast--;
    for(plp = pllp->begin(); plp != pllp->end(); plp++) {
      if(cnt % 4 == 0 && model_type != sphere_sweep) {
        cout << endl;
        cout << "\t";
      }
      if(model_type == sphere_sweep) {
        cout << "\t";
      } else if(plp != pllp->begin() && cnt % 4 != 0) {
        cout << ", ";
      }
      cout << "<" << plp->x << ", " << plp->y;
      if(model_type == sphere_sweep) {
        cout << ", " << plp->z << ">, " << plp->r << endl;
      } else {
        cout << ">";
      }
      if(cnt % 4 == 3) {
        switch(model_type) {
          case linear_prism:
          case conic_prism:
            if(!(plp == pllast && pllp == plllast)) {
              cout << ",";
            }
            break;
          case lathe:
            if(plp != pllast) {
              cout << ",";
            }
            break;
          case sphere_sweep:
            break;
        }
      }
      cnt++;
    }
    switch(model_type) {
      case lathe:
        cout << endl;
      case sphere_sweep:
        cout << "}" << endl << endl;
        break;
      default:
        break;
    }
  }
  switch(model_type) {
    case linear_prism:
    case conic_prism:
      cout << "\n}" << endl;
      break;
    case lathe:
    case sphere_sweep:
      break;
  }
}

キリルン「長いですね。」
ルギア君「案外長くなっちゃった。」
キリルン「じゃあ、説明お願いします。」
ルギア君「まずはコンパイルから。四則演算しか使っていないので、libm は不要だ。ただ、gcc のバージョンによっては勝手にリンクするものもあるがな。」

$ g++ -o svg2pov svg2pov.cpp -Wall

ルギア君「じゃあ、実行だ。この3つのどれかでやってくれ。」

$ ./svg2pov >[出力ファイル名]
$ ./svg2pov -i [入力ファイル名] >[出力ファイル名]
$ ./svg2pov [SVG コマンド] >[出力ファイル名]

ルギア君「入力にファイルを使う時はあらかじめ変換したい SVG の中の変換したい タグの d 属性の内容だけを取りだしておいてくれ。」
キリルン「はい。」
ルギア君「そしたら最初の方法で起動した場合は、データをここで入力して終わったら Ctrl + D を押すんだ。」
キリルン「はい。」
ルギア君「そうすると、質問が出てくる。まず、あなたが作りたいのは 角柱 か 角錐 か 回転体 か選んでくれ。球スイープはベジエには対応していないからこの実装ではまだ対応してない。」
キリルン「あ、そうですか。」

(前略: データの読み込み)
座標値: -11.125
座標値: 12.7188
コマンド: C
座標値: -11.125
座標値: 12.3668
座標値: -10.8533
座標値: 12.2188
座標値: -10.2812
座標値: 12.2188
コマンド: z
種類 ?
  角柱 (linear prism)       -> lp
  角錐 (conic prism)        -> cp
  回転体 (lathe)            -> la
  球スイープ (sphere sweep) -> ss
  キャンセル (cancel)       -> cc
>

ルギア君「作りたいものを右に表示されている英字2字で入力してくれ。次は、角柱、角錐を選んだ場合だけある、高さの入力だ。浮動小数点数で入力してくれ。」

高さ1 ? >0
高さ2 ? >1

ルギア君「まあ、どちらの方が大きく無ければいけないということはないが、[高さ1],[高さ2] の順で書き出される。」
キリルン「逆でも同じですか。」
ルギア君「少なくとも単色塗りでは・・・」
キリルン「へぇ。」
ルギア君「次は線形変換だ。入力したデータそのものを線形変換できるぞ。」

X (U) 方向の拡大率 >0.1
Y (V) 方向の拡大率 >0.1
X (U) 方向の平行移動量 >0
Y (V) 方向の平行移動量 >0

ルギア君「回転は必要性を感じなかったのでないぞ。SVG はデフォルトでピクセル単位だな。1ピクセルをそのまま POV-Ray のデータにしたら大きすぎるよな。そのときは、拡大率を 0 から 1 の間を指定してくれ。どちらも同じ値をな。」
キリルン「へぇ。」
ルギア君「あと、実は、SVG は左手系、POV-Ray は右手系である。」
キリルン「ということは、Y 方向を -1 倍しないといけないのですね。」
ルギア君「そうだ。まあ、こっちはあとで POV-Ray の scale コマンドを使って修正してもらってもいいんだがね。」

適用順序 ?
  移動 後 拡縮 (move -> scale) -> ms
  拡縮 後 移動 (scale -> move) -> sm
  キャンセル (cancel)          -> cc
>

ルギア君「さっき入力した拡大縮小と平行移動の適用の順番だ。POV-Ray を知っている人ならどうしてこのようなことを聞くのかは知っているはずだから説明を省略するよ。これも英字2字で答えてくれ。」

(前略)
コマンド: M / 現在点: (-5.375, -3.9375) / 前点: (7.03125, -4)           
コマンド: L / 現在点: (-4.25, -3.9375) / 前点: (-5.375, -3.9375)        
コマンド: C / 現在点: (-4.28201, -3.8215) / 前点: (-4.25, -3.9375)      
コマンド: C / 現在点: (-4.30376, -3.75875) / 前点: (-4.25, -3.9375)     
コマンド: C / 現在点: (-4.34375, -3.71875) / 前点: (-4.25, -3.9375)     
コマンド: C / 現在点: (-4.38375, -3.67875) / 前点: (-4.34375, -3.71875) 
コマンド: C / 現在点: (-4.44, -3.65625) / 前点: (-4.34375, -3.71875)    
コマンド: C / 現在点: (-4.5, -3.65625) / 前点: (-4.34375, -3.71875)     
コマンド: C / 現在点: (-4.62, -3.65625) / 前点: (-4.5, -3.65625)        
コマンド: C / 現在点: (-4.8105, -3.6635) / 前点: (-4.5, -3.65625)       
コマンド: C / 現在点: (-5.0625, -3.6875) / 前点: (-4.5, -3.65625)       
コマンド: L / 現在点: (-5.03125, -3.1875) / 前点: (-5.0625, -3.6875) 
(後略)   

ルギア君「そうすると、このプログラムは、コマンドの処理を行って POV-Ray 用に座標を構成し直す作業をする。SVG には相対指定もあるから一筋縄ではいかなったんだ。」
キリルン「なるほど。」
ルギア君「最後に、POV-Ray のデータが出力される。僕が最初に提示したコマンドを使った場合、画面には表示されず、そのファイルにかかれるよ。」

prism {
        bezier_spline
        linear_sweep
        0,1
        3832,
        <-1.96875, -1.375>, <-1.96875, -1.375>, <-1.96875, -1.31875>, <-1.96875, -1.31875>,
        <-1.96875, -1.31875>, <-1.96875, -1.31875>, <-2.0625, -1.31875>, <-2.0625, -1.31875>,
        <-2.0625, -1.31875>, <-2.0625, -1.31875>, <-2.0625, -1.25313>, <-2.0625, -1.25313>,
        <-2.0625, -1.25313>, <-2.0625, -1.25313>, <-2.03438, -1.25313>, <-2.03438, -1.25313>,
        <-2.03438, -1.25313>, <-2.02958, -1.23752>, <-2.01978, -1.2134>, <-2.00937, -1.175>,
(後略)
}

ルギア君「データとしてはまちがっていないが、このレンダリングにどれくらいかかるかは知らないよ。」
キリルン「無責任ですね。」
f:id:lugia:20090902180306p:image