カーブエディタ作成 c++でグラフデータを読み込むLib作成

前の記事

bravememo.hatenablog.com

完成図

f:id:Brave345:20200116171252p:plain 上のグラフをc++で読んだ時の実行結果です。

x = 0.000000 y = 0.000000
x = 0.100000 y = 0.403000
x = 0.200000 y = 0.532000
x = 0.300000 y = 0.617000
x = 0.400000 y = 0.686000
x = 0.500000 y = 0.746000
x = 0.600000 y = 0.802000
x = 0.700000 y = 0.854000
x = 0.800000 y = 0.905000
x = 0.900000 y = 0.953000
x = 1.000000 y = 1.000000

Lib作成

これを参考にして作成しました。

jprogramer.com

コードは前回のC#で作成したものをc++に移植したものになります。

C#からの変更点

C#の時から少しだけ変更しています、 ・XからYを求めた際の誤差を丸めるようにしました。

誤差は小数点以下第4位で四捨五入して丸めています。

・データにエラーがある場合、OutputDebugStringAでエラーメッセージをだすようにしました

グラフデータチェックはリストのサイズの確認と、

最初の開始点と最後の終了点が不正な値でないかの確認をしています。 (最初の開始点と最後の終了点のxは値が0と1で固定されるため)

ソースコード

pragma once

#include<vector>

namespace CurveEditor
{
     //CSVからグラフデータを読み込み、保存やデータを読み込むクラス
     class BezierPointList
     {
     public:
         struct Vec2
         {
             double x;
             double y;

             Vec2()
             {

             }
             Vec2(double x1, double y2)
             {
                 x = x1;
                 y = y2;
             }
         };
         //3次ベジェ曲線に必要な点
         struct BezierPoint
         {
             Vec2 startPoint;     //開始点
             Vec2 controlPoint1;  //制御点1
             Vec2 controlPoint2;  //制御点2
             Vec2 endPoint;       //終了点

             BezierPoint()
             {

             }
             BezierPoint(Vec2 s, Vec2 c1, Vec2 c2, Vec2 e)
             {
                 startPoint = s;
                 controlPoint1 = c1;
                 controlPoint2 = c2;
                 endPoint = e;
             }
         };
     public:
         BezierPointList();
          ~BezierPointList();

         //CSVからグラフデータを読み込む
         // @param Actor 読み込むCSVのパス
         // @return  difference 誤差の許容値
         bool ReadBezierPointList(std::string CSVpath);

         // グラフのXからYを求める
         // @param x 時間0~1

         double EvaluateY(double x);

         //グラフデータに異常はないか?
         // @return  true なら正常
         bool DateErrorCheck();

         //グラフデータの開放
         void Release();
     private:
         //方程式を求める時に使う
          double Ax;
          double Ay;
          double Bx;
          double By;
          double Cx;
          double Cy;

          std::vector<BezierPoint> m_List;
          BezierPoint m_SelectPoint;
     private:
         BezierPointList(const BezierPointList & cpy);
         BezierPointList& operator=(const BezierPointList &ope);
         //どこの曲線か検索
         // @param x 時間0~1
         int SearchBezier(double x);

         void SetConstants3();
         // xを与えるとtについての方程式を求めてyを返す
         // @param x 
         double CalcYfromX(double x);
         // あるtについてxの微分値を求める
         // @param t
         double sampleCurveDerivativeX(double t);
         // tからxとyを求める
         Vec2 GetPointAtTime(double t);

         //文字列から数字に変換しデータを保存
         void StringToBezierPoint(std::vector<std::string>& val);

     };
}
#include "stdafx.h"
#include "CurveEditor.h"
#include <fstream>
#include <cmath>
#include <string>
#include <Windows.h>
namespace CurveEditor
{
#define EROORCODE_000 "CurveEditoreError[000][CSVのデータがおかしいよ!] \n"
#define EROORCODE_001 "CurveEditoreError[001][ファイルを開けてないよ!] \n"
#define EROORCODE_002 "CurveEditoreError[002][方程式の解がでません] \n"

#ifdef _DEBUG 
#ifdef WINAPI_FAMILY_PARTITION 
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)

#define DEBUGLOG(code) OutputDebugStringA(code);

#else
#define DEBUGLOG(code)
#endif
#else
#define DEBUGLOG(code)
#endif
#else // DEBUG
#define DEBUGLOG(code)
#endif
    //べき乗 繰り返し二乗法
    long Pow(long a, long n)
    {
        long ret = 1;
        for (; n>0; n >>= 1, a = a*a)
        {
            if (n % 2 == 1)ret = ret*a;
        }
        return ret;
    }

    /* 小数点n以下で四捨五入する */
    double round_n(double number, long n)
    {
        double num = (double)(Pow(10, n - 1));
        number = number *num; //四捨五入したい値を10の(n-1)乗倍する。
        number = round(number); //小数点以下を四捨五入する。
        number /= num; //10の(n-1)乗で割る。
        return number;
    }

    BezierPointList::BezierPointList()
        :Ax(0),
        Ay(0),
        Bx(0),
        By(0),
        Cx(0),
        Cy(0)
    {

    }

    BezierPointList::~BezierPointList()
    {

    }
    //CSVからグラフデータを読み込む
    bool BezierPointList::ReadBezierPointList(std::string CSVpath)
    {
        //念のためデータを開放
        Release();

        std::ifstream ifs;
        ifs.open(CSVpath.c_str());

        std::string value;
        std::vector<std::string > values;

        bool isOpen = ifs.is_open();//正常なデータを開けたか

        if (isOpen)
        {
            //一行読み込みテスト
        // std::getline(ifs, value);
        // if (value != "/n") values.push_back(value);
            //全データを読み込む
            while (std::getline(ifs, value, ','))
            {
                if(value != "/n") values.push_back(value);
            }
            //データを保存
            StringToBezierPoint(values);

            ifs.close();
            if (!DateErrorCheck())
            {
                DEBUGLOG(EROORCODE_000)
                return false;
            }
        }
        else
        {
            DEBUGLOG(EROORCODE_001)
        }

        return isOpen;
    }
    // グラフのXからYを求める
    double BezierPointList::EvaluateY(double x, bool isRound)
    {
        //データが問題ないか
        if(!DateErrorCheck())
        {
            DEBUGLOG(EROORCODE_000)

            return -100;
        }

        int num = 0;
        int pontcnt = m_List.size();
        //どこのベジェ曲線か検索
        num = SearchBezier(x);

        m_SelectPoint = m_List[num];

        double d = CalcYfromX(x);
        //誤差を丸める
        if (isRound)
        {
            d = round_n(d, 4);
        }
        return d;
    }
    //グラフデータに異常はないか? trueなら正常
    bool BezierPointList::DateErrorCheck()
    {
        //グラフデータがおかしいかの判定
        //データの数値チェック
        if (m_List.size() <= 0) return false;
        if (m_List[0].startPoint.x != 0) return false;
        if (m_List[m_List.size() - 1].endPoint.x != 1) return false;;

        return true;
    }

    int BezierPointList::SearchBezier(double x)
    {
        int num = 0;
        int pontcnt = m_List.size();
        //どこのベジェ曲線か検索
        for (int i = 0; i < pontcnt; i++)
        {
            if (m_List[i].endPoint.x > x)
            {
                num = i;
                return num;
            }
        }
        // 0~1の間でない値なら端の曲線を返す
        if (x >= 1) return pontcnt - 1;
        if (x <= 0) return  0;
        return 0;
    }

    void BezierPointList::SetConstants3()
    {
        Vec2 b0(m_SelectPoint.startPoint.x, m_SelectPoint.startPoint.y) ;
        Vec2 b1(m_SelectPoint.controlPoint1.x, m_SelectPoint.controlPoint1.y);
        Vec2 b2(m_SelectPoint.controlPoint2.x, m_SelectPoint.controlPoint2.y);
        Vec2 b3(m_SelectPoint.endPoint.x, m_SelectPoint.endPoint.y);

        //公式に当てはめる
        /*ベジェ曲線は媒介変数表示で定義される。tを0~1で変化させると、曲線上の点を取得できる。
       (x1,y1)始点
       (x2,y2)制御点
       (x3,y3)制御点
       (x4,y4)終点

       x = x(t) = t^3*x4 + 3*t^2*(1-t)*x3 + 3*t*(1-t)^2*x2 + (1-t)^3*x1
       y = y(t) = t^3*y4 + 3*t^2*(1-t)*y3 + 3*t*(1-t)^2*y2 + (1-t)^3*y1

       tについて降べきの順に整理すると

       x = (x4-3*(x3+x2)-x1)*t^3 + 3*(x3-2*x2+x1)*t^2 + 3*(x2-x1)*t + x1
       y = (y4-3*(y3+y2)-y1)*t^3 + 3*(y3-2*y2+y1)*t^2 + 3*(y2-y1)*t + y1

       t以外の値を求める?
       */
        Ax = b3.x - 3 * (b2.x - b1.x) - b0.x;
        Bx = 3 * (b2.x - 2 * b1.x + b0.x);
        Cx = 3 * (b1.x - b0.x);

        Ay = b3.y - 3 * (b2.y - b1.y) - b0.y;
        By = 3 * (b2.y - 2 * b1.y + b0.y);
        Cy = 3 * (b1.y - b0.y);
    }
    double BezierPointList::CalcYfromX(double x)
    {
        double epsilon = 0.001f; // 閾値
        double x2, t0, t1, t2, d2, i;
        for (t2 = x, i = 0; i < 8; i++)
        {
            x2 = GetPointAtTime(t2).x - x;
            if (abs(x2) < epsilon)
            {
                return GetPointAtTime(t2).y;
            }
            d2 = sampleCurveDerivativeX(t2);
            if (abs(d2) < 1e-6f)
            {
                break;
            }
            t2 = t2 - x2 / d2;
        }
        t0 = 0;
        t1 = 1;
        t2 = x;
        //tが0~1の間でないなら
        if (t2 < t0)
        {
            return GetPointAtTime(t0).y;
        }
        //tが0~1の間でないなら
        if (t2 > t1)
        {
            return GetPointAtTime(t1).y;
        }
        while (t0 < t1)
        {
            x2 = GetPointAtTime(t2).x;
            if (abs(x2 - x) < epsilon)
            {
                return GetPointAtTime(t2).y;
            }
            if (x > x2)
            {
                t0 = t2;
            }
            else
            {
                t1 = t2;
            }
            t2 = (t1 - t0) * 0.5f + t0;
        }

        DEBUGLOG(EROORCODE_002)

        return GetPointAtTime(t2).y; // 失敗
    }
    // あるtについてxの微分値を求める
    double BezierPointList::sampleCurveDerivativeX(double t)
    {
        // //3次ベジェ
        return (3.0f * Ax * t + 2.0f * Bx) * t + Cx;
    }
    // tからxとyを求める
    BezierPointList::Vec2 BezierPointList::GetPointAtTime(double t)
    {
        SetConstants3();
        //参考プログラムのやつ こっちのほうが計算が早いかも?
        /* Vec2 p0 = new Vec2((float)m_SelectPoint.startPoint[0], (float)m_SelectPoint.startPoint[1]);
       float t2 = t * t;
       float t3 = t * t * t;
       float x = this.Ax * t3 + this.Bx * t2 + this.Cx * t + p0.x;
       float y = this.Ay * t3 + this.By * t2 + this.Cy * t + p0.y;
       */

        double x1 = m_SelectPoint.startPoint.x;
        double y1 = m_SelectPoint.startPoint.y;
        double x2 = m_SelectPoint.controlPoint1.x;
        double y2 = m_SelectPoint.controlPoint1.y;
        double x3 = m_SelectPoint.controlPoint2.x;
        double y3 = m_SelectPoint.controlPoint2.y;
        double x4 = m_SelectPoint.endPoint.x;
        double y4 = m_SelectPoint.endPoint.y;

        double t1 = t;
        double t2 = t1 * t1;
        double t3 = t1 * t1 * t1;
        double tp1 = 1 - t1;
        double tp2 = tp1 * tp1;
        double tp3 = tp1 * tp1 *tp1;
        //公式に当てはめる
        /*ベジェ曲線は媒介変数表示で定義される。tを0~1で変化させると、曲線上の点を取得できる。
       (x1,y1)始点
       (x2,y2)制御点
       (x3,y3)制御点
       (x4,y4)終点

       x = x(t) = t^3*x4 + 3*t^2*(1-t)*x3 + 3*t*(1-t)^2*x2 + (1-t)^3*x1
       y = y(t) = t^3*y4 + 3*t^2*(1-t)*y3 + 3*t*(1-t)^2*y2 + (1-t)^3*y1

       媒介変数[t]を用いてるのでxとyの両方の解がでる
       */

        double tx = (tp3 * x1) + (x2 * 3 * tp2 * t1) + (x3 * 3 * tp1 * t2) + (t3 * x4);
        double ty = (tp3 * y1) + (y2 * 3 * tp2 * t1) + (y3 * 3 * tp1* t2) + (t3 * y4);


        return  Vec2(tx, ty);
    }
    //文字列から数字に変換しデータを保存
    void BezierPointList::StringToBezierPoint(std::vector<std::string>& val)
    {
        int loopcnt = val.size() / 8;
        for (int i = 0; i < loopcnt; i++)
        {
            //文字列を数字に変換
            Vec2 s(std::stod(val[i * 8]), std::stod(val[1 + i * 8]));        //開始点
            Vec2 c1(std::stod(val[ 2 +i * 8]), std::stod(val[3  + i * 8])); //制御点1
            Vec2 c2(std::stod(val[4 + i * 8]), std::stod(val[5  + i * 8])); //制御点2
            Vec2 e(std::stod(val[6 + i * 8]), std::stod(val[7  + i * 8]));  //終了点
            //保存
            BezierPoint bp(s, c1, c2, e);
            m_List.push_back(bp);
        }
    }
    void BezierPointList::Release()
    {
        std::vector<BezierPoint> List;
        List.swap(m_List);
        m_List.clear();

    }
}

終わりに

最初はdllで作成してたんですがdll作成だとSTLが使えないっぽいので一旦保留にしました。

ついにこれで実装予定だったものは実装が終了しました。 ただ作成してる過程でいくつか入れたい機能が出たのでそれを実装していこうと思います。

現時点でのカーブエディタをgitで公開します。 気になった方はぜひ使ってみてください。

github.com