カーブエディタ作成 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

C# カーブエディタ作成 c++にグラフデータを読み込むための準備

前の記事

bravememo.hatenablog.com

完成図


CurveEditor 外部からグラフデータを読みこむための準備

C++でグラフデータを読み込みたい

カーブエディタで作成したグラフデータをc++でこんな感じで取得できるようにしたい

#include "CurveEditor.h"
#include <windows.h>

int main()
{
    CurveEditor::BezierPointList bp;
    bp.ReadBezierPointList("new.csv");

    for (int i = 0; i <= 100; i++)
    {
        //グラフデータを表示
        printf("x = %5lf y = %5lf \n", i * 0.01f, bp.EvaluateY(i * 0.01f));
    }
    return 0;
}

カーブエディタ側で読み込む機能を作成

いきなりc++側で作ってもよかったのですが、ビジュアルで確認するための機能を開発するのがめんどかったので、今あるカーブエディタをつかって、詠み込んだデータを表示するようにしました。

f:id:Brave345:20200116171252p:plain

 /// <summary>
        /// pictureBox内の描画
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TestPaint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.Clear(Color.FromArgb(20, 230, 230, 230));
        
            //線の描画
            LinePaint(g);
            //ここで描画!
            if (checkBox2.Checked)
            {
                m_PointRender.SetList(m_CurvePointControl.GetGraph());
                m_PointRender.Paint(e);
                return;
            }

            m_CurvePointControl.OrganizeControlPoint(); //制御点の整理
            BezierPaint(e);    //3次ベジェ曲線描画
            if (checkBox1.Checked)
            {
                ControlPaint(e);   //選択している点の制御点描画
                PointrPaint(e);    //3次ベジェ曲線を結ぶ点描画
            }
        }

これは読み込んだデータからXを指定してYを座標を取得して点で描画しています。 Xは0.01づつ指定してます。

 //保存されている点を描画
        public void PaintGraph(PaintEventArgs e)
        {
            var pontcnt = m_list.Count();

            for (int i = 0; i <= 100; i++)
            {
                int x = CMath.ChageNomalPosX((decimal)(0.01f * i));
                int y = CMath.ChageNomalPosY(EvaluateY((decimal)(0.01f * i)));
                //点の描画
                e.Graphics.FillEllipse(m_PointColor, x - m_cpSize / 2, y - m_cpSize / 2, m_cpSize, m_cpSize);
            }
            TestPrint();
        }

ベジェ曲線のXからYを求めたい

これがとても難関でした。

まずベジェ曲線は媒介変数(t)を使用してるため、 f(x) = yの形に変換できません

ですのでxからtを求めてそのtからyを求めるといいはずです。

xからtを求めるには3次方程式の解を使ったやり方があるのですが、 自分がいまいち理解できなかったので(虚数を考慮するとこあたり)

このサイトのものを参考にして作成しました。

umeru.hatenablog.com

こちらではニュートン法を使用してxからtを求めてます

tを求めればあと公式である

y = (y4-3(y3+y2)-y1)t3 + 3(y3-2y2+y1)t2 + 3(y2-y1)*t + y1

に当てはめればyを求めれます。

以下参考サイト

複数のベジェ曲線の扱いについて書かれてるサイト

https://shspage.hatenadiary.org/entry/20140625/1403702735

数式が書いてあるやつ

http://geom.web.fc2.com/geometry/bezier/cubic.html

英語だけど色々書いてある。結構いい

https://pomax.github.io/bezierinfo/#yforx

ベジェ曲線についてのスライド

https://www.slideshare.net/yuukiiwabuchi9/ss-59756703

Swift で3次方程式の解でやってみたやつ

https://blog.dnpp.org/cubic_equation_solver

ソースコード

複数のベジェ曲線を使用しているためまずxからどのベジェ曲線か求めるのがポイントです。複数のベジェ曲線でもyを求めるときは全体の座標データは必要なく、一つのベジェ曲線のデータがあればYを求めれます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
namespace CurveEditor
{
    //他の環境でグラフを読み込めるかをテストするためのクラス
    class PointRender
    {
        struct Vec2
        {
            public float x;
            public float y;

            public Vec2(float x, float y)
            {
                this.x = x;
                this.y = y;
            }
        }
        //3次ベジェ曲線に必要な点
        public struct CSVPoint
        {
            //配列サイズは2でXとYを保存させる
            //本番の環境はCSVから読み込む
            public decimal[] startPoint;     //開始点
            public decimal[] controlPoint1;  //制御点1
            public decimal[] controlPoint2;  //制御点2
            public decimal[] endPoint;       //終了点
        }
        
        List<CurvePointControl.BezierPoint> m_list = new List<CurvePointControl.BezierPoint>(); //線を引くための点を格納する場所
        Brush m_PointColor = new SolidBrush(Color.FromArgb(255, 0, 255, 0));                    //点の色
        List<CSVPoint> m_Csvlist = new List<CSVPoint>();    //線を引くための点を格納する場所
        CSVPoint m_SelectPoint = new CSVPoint();            //選択している曲線

        const float m_cpSize = 5; //点のサイズ
        bool m_isOntimeTest = true;//一回だけテスト出力フラグ

        private float Ax;
        private float Ay;

        private float Bx;
        private float By;

        private float Cx;
        private float Cy;

        public void Paint(PaintEventArgs e)
        {
            PaintGraph(e);
        }
        //保存されている点を描画
        public void PaintGraph(PaintEventArgs e)
        {
            var pontcnt = m_list.Count();


            for (int i = 0; i <= 100; i++)
            {
                int x = CMath.ChageNomalPosX((decimal)(0.01f * i));
                int y = CMath.ChageNomalPosY(EvaluateY((decimal)(0.01f * i)));

                e.Graphics.FillEllipse(m_PointColor, x - m_cpSize / 2, y - m_cpSize / 2, m_cpSize, m_cpSize);
            }
            TestPrint();
        }

        /// <summary>
        /// テストで書き出し後のデータを出力
        /// </summary>
        public void TestPrint()
        {
            if (!m_isOntimeTest) return;
            for (int i = 0; i <= 100; i++)
            {
                //int x = CMath.ChageNomalPosX((decimal)(0.001f * i));
                decimal x = ((decimal)(0.01f * i));
                decimal y = EvaluateY((decimal)(0.01f * i));
                Console.Write("x = {0:f3}, y = {1:f3} \n", x, y); // 文字や数値の出力
            }
            m_isOntimeTest = false;
        }
        public void SetList(List<CurvePointControl.BezierPoint> list)
        {
            m_list = list;
            m_Csvlist.Clear();
            foreach (CurvePointControl.BezierPoint item in m_list)
            {
                CSVPoint cp = new CSVPoint();
                cp.startPoint = new decimal[2];
                cp.controlPoint1 = new decimal[2];
                cp.controlPoint2 = new decimal[2];
                cp.endPoint = new decimal[2];
                cp.startPoint[0] = CMath.ChageDecimalPosX(item.startPoint.X);
                cp.startPoint[1] = CMath.ChageDecimalPosY(item.startPoint.Y);
                cp.controlPoint1[0] = CMath.ChageDecimalPosX(item.controlPoint1.X);
                cp.controlPoint1[1] = CMath.ChageDecimalPosY(item.controlPoint1.Y);
                cp.controlPoint2[0] = CMath.ChageDecimalPosX(item.controlPoint2.X);
                cp.controlPoint2[1] = CMath.ChageDecimalPosY(item.controlPoint2.Y);
                cp.endPoint[0] = CMath.ChageDecimalPosX(item.endPoint.X);
                cp.endPoint[1] = CMath.ChageDecimalPosY(item.endPoint.Y);

                m_Csvlist.Add(cp);
            }
        }

        /// <summary>
        /// グラフのXからYを求める
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public decimal EvaluateY(decimal x)
        {
            int num = 0;
            int pontcnt = m_list.Count();
            //どこのベジェ曲線か検索
            num = SearchBezier(x);

            m_SelectPoint = m_Csvlist[num];

            return (decimal)CalcYfromX((float)x);
            //return y;
        }
        /// <summary>
        /// どこの曲線か検索 0~1
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public int SearchBezier(decimal x)
        {
            int num = 0;
            int pontcnt = m_list.Count();
            //どこのベジェ曲線か検索
            for (int i = 0; i < pontcnt; i++)
            {
                if (m_Csvlist[i].endPoint[0] > x)
                {
                    num = i;
                    return num;
                }
            }
            // 0~1の間でない値なら端の曲線を返す
            if (x >= 1) return pontcnt - 1;
           if (x <= 0) return  0;
           return 0;
        }
        private void SetConstants3()
        {
            Vec2 b0 = new Vec2((float)m_SelectPoint.startPoint[0], (float)m_SelectPoint.startPoint[1]);
            Vec2 b1 = new Vec2((float)m_SelectPoint.controlPoint1[0], (float)m_SelectPoint.controlPoint1[1]);
            Vec2 b2 = new Vec2((float)m_SelectPoint.controlPoint2[0], (float)m_SelectPoint.controlPoint2[1]);
            Vec2 b3 = new Vec2((float)m_SelectPoint.endPoint[0], (float)m_SelectPoint.endPoint[1]);

            //公式に当てはめる
            /*ベジェ曲線は媒介変数表示で定義される。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以外の値を求める?
              */
            this.Ax = b3.x - 3f * (b2.x - b1.x) - b0.x;
            this.Bx = 3f * (b2.x - 2 * b1.x + b0.x);
            this.Cx = 3f * (b1.x - b0.x);

            this.Ay = b3.y - 3f * (b2.y - b1.y) - b0.y;
            this.By = 3f * (b2.y - 2 * b1.y + b0.y);
            this.Cy = 3f * (b1.y - b0.y);
        }
        /// <summary>
        /// tからxとyを求める
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
         Vec2 GetPointAtTime(float 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;
            */

            decimal x1 = m_SelectPoint.startPoint[0];
            decimal y1 =m_SelectPoint.startPoint[1];
            decimal x2 =m_SelectPoint.controlPoint1[0];
            decimal y2 =m_SelectPoint.controlPoint1[1];
            decimal x3 =m_SelectPoint.controlPoint2[0];
            decimal y3 =m_SelectPoint.controlPoint2[1];
            decimal x4 =m_SelectPoint.endPoint[0];
            decimal y4 = m_SelectPoint.endPoint[1];

            decimal t1 = (decimal) t;
            decimal t2 = t1 * t1;
            decimal t3 = t1 * t1 * t1;
            decimal tp1 = 1 - t1;
            decimal tp2 = tp1 * tp1;
            decimal 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の両方の解がでる
              */

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


            return new Vec2((float)tx, (float)ty);
        }
        /// <summary>
        /// あるtについてxの微分値を求める
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        private float sampleCurveDerivativeX(float t)
        {
             // //3次ベジェ
             return (3.0f * this.Ax * t + 2.0f * this.Bx) * t + this.Cx;
        }
        /// <summary>
        /// xを与えるとtについての方程式を求めてyを返す
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        /// 参考サイト http://umeru.hatenablog.com/entry/2015/12/03/131844
        public float CalcYfromX(float x)
        {
            float epsilon = 0.001f; // 閾値
            float x2, t0, t1, t2, d2, i;
            for (t2 = x, i = 0; i < 8; i++)
            {
                x2 = GetPointAtTime(t2).x - x;
                if (Math.Abs(x2) < epsilon)
                {
                    return GetPointAtTime(t2).y;
                }
                d2 = sampleCurveDerivativeX(t2);
                if (Math.Abs(d2) < 1e-6f)
                {
                    break;
                }
                t2 = t2 - x2 / d2;
            }
            t0 = 0f;
            t1 = 1f;
            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 (Math.Abs(x2 - x) < epsilon)
                {
                    return GetPointAtTime(t2).y;
                }
                if (x > x2)
                {
                    t0 = t2;
                }
                else
                {
                    t1 = t2;
                }
                t2 = (t1 - t0) * 0.5f + t0;
            }

            return GetPointAtTime(t2).y; // 失敗
        }

 
    }
}

終わりに

あとはこれをc++に移植すれば最初に予定していた機能はすべて実装されます。

最初はできないと思ってたけど、やればできるもんなんだなぁ。

次の記事

bravememo.hatenablog.com

C# カーブエディタ作成 進む 戻るの実装

前の記事

bravememo.hatenablog.com

完成図


CurveEditor 進む 戻る機能

外観の作成

この記事と同じやり方でやればできます。

bravememo.hatenablog.com

f:id:Brave345:20200107170113p:plain

外観を作ったらClickイベントを追加させましょう。

ReDo Undoのおおまかなしくみ

これら機能はデザインパターンであるMementoパターンCommandパターンを参考にして制作しました。

techracho.bpsinc.jp

・おおまかなしくみ 戻るの動作は必ずなにか操作をするたびに操作前のデータをスタックに追加し、戻る操作ををしたらスタックからデータをコピーするような感じです。

   /// <summary>
        /// 戻る
        /// </summary>
        public void UnDo()
        {
            //無選択状態に
            m_SelectMode = SelectMode.None;
            CancelMovePoint();
            m_SelectPoint = 0;

            if (m_UnDoStack.Count <= 1)
            {
                //すでに初期状態のグラフデータなら進むスタックに余計なデータを入れさせない
                if (isListMatch(ref m_list, m_UnDoStack.Peek())) return;
                //初期状態のグラフデータ入れる
                m_ReDoStack.Push(new List<BezierPoint>(m_list));
                m_list = new List<BezierPoint>(m_UnDoStack.Peek());//初期状態のグラフ

                return;
            }
            m_ReDoStack.Push(new List<BezierPoint>(m_list));
            m_list = m_UnDoStack.Pop();   //戻る
        }

進むの方は戻るをした際に戻る前のデータをスタックに追加し、進む操作をしたらスタックからデータをコピーします。ほぼ戻ると同じですね。ただ戻ると違い進む前に戻るのスタックに現在のデータを入れる必要があります。でないと一回進むと二度と戻れなくなってしまいます

   /// <summary>
        /// 進む
        /// </summary>
        public void ReDo()
        {
            //一回も戻っていなければ進まない
           if (m_ReDoStack.Count - 1 <= -1) return;
            //無選択状態に
            m_SelectMode = SelectMode.None;
            CancelMovePoint();
            m_SelectPoint = 0;
            m_UnDoStack.Push(new List<BezierPoint>(m_list));//戻るの方にデータを保存
            m_list = m_ReDoStack.Pop();//進む    
        }

・保存データ

保存するデータは 、グラフデータのすべての点情報(List<BezierPoint>)を保存しています。

なぜこれだけにしたかというとこれ以上保存するデータが多いと容量が心配になってくるのとデータの復元作業が複雑になるからです。ようはめんどいから座標データだけにしました。

       /// <summary>
        /// 操作した後のグラフデータをスタックに保存
        /// </summary>
        public void SaveMemento()
        {
            if (m_UnDoStack.Count() != 0)
            {
                //余計なデータを保存していたら削除
                if (isListMatch(ref m_list, m_UnDoStack.Peek()))
                {
                    DeleteMemento();
                    return;
                }
            }
            //データを保存
            m_UnDoStack.Push(new List<BezierPoint>(m_list));
            m_ReDoStack.Clear();
        }

・データを保存するタイミング

これを決めるのが一番大変でした。なぜかというと保存タイミングは基本的にグラフデータに変更があったら保存するのですが、そのタイミングを取得するのが大変でした。 保存タイミングは以下の3つになります。

  1. グラフをクリックしたとき
  2. 点の追加等のボタンが押されたら
  3. ファイルを開くまたは新規作成

クリックしたときの保存が厄介でした。 ダブルクリックで点を追加する機能があるため、ダブルクリック時、同じグラフデータがスタックに貯まってしまう現象がおきました。 なので毎回スタックと現グラフデータを比較して違うときだけ保存させるようにしました。

、がそれではうまくはいきませんでした。 戻るスタックには最新のグラフデータは入ってはいけないため(入ってると2回戻るしないと戻れない)比較してデータが違うときだけ入れるだと最新のグラフデータが戻るスタックに入ってしまい、この実装ではうまくいきませんでした。

そこでデータを保存する際に最新のグラフデータが入ってないかを確認し、入ってたら削除、なければ戻るスタックに入れるようにしました。さらに戻るスタックに入れる際は点を選択しているときだけにすることで(点を選択してないときは必ずグラフデータに変更がかからないため)うまく戻る機能の実現が出来ました。

        /// <summary>
        /// 左マウスクリック後のグラフデータをスタックに保存
        /// </summary>
        public void SaveMemento_Click()
        {
            if (m_UnDoStack.Count() != 0)
            {     //余計なデータを保存していたら削除
                if (isListMatch(ref m_list, m_UnDoStack.Peek())) 
                {
                   DeleteMemento();
                    return;
                }
            }
           //点選択を解除させたときに余計なデータを保存させない
            if (m_SelectMode == SelectMode.None && m_UnDoStack.Count != 0) return;

            m_UnDoStack.Push(new List<BezierPoint>(m_list));
            m_ReDoStack.Clear();
        }

ReDo Undoの実装はめんどい

このカーブエディタで作成で一番めんどかった気がする。 ReDo Undo実装を考えているなら、前もって保存するデータ、保存するタイミングを決めて、設計していくべきだと思いました。

ソースコード

いままで全部ここに書いていて見づらかったので記事の途中にコードを挟んで残りの差分をコードをこちらにのせることにしました。

あと今回のFormはいたるところにセーブ処理を追加して差分だけでも結構長くなってしまうので省略します

       /// <summary>
        /// クリックした瞬間の処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            switch (e.Button)
            {
                case MouseButtons.Left:
                    m_CurvePointControl.SearchSelectPoint(e);
                    //変更した値を保存
                    m_CurvePointControl.SaveMemento_Click();
                    SaveMousePos(e);
                    break;
                case MouseButtons.Middle:
                    break;
                case MouseButtons.Right:
                    SaveMousePos(e);
                    break;
            }
        }
        /// <summary>
        /// 戻る
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MenuUnDo_Click(object sender, EventArgs e)
        {
            m_CurvePointControl.UnDo();
            pictureBox1.Refresh();//再描画
        }
        /// <summary>
        /// 進む
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        private void MenuReDo_Click(object sender, EventArgs e)
        {
            m_CurvePointControl.ReDo();
            pictureBox1.Refresh();//再描画
        }
  /// <summary>
        /// 余計なデータを保存していたら削除させる
        /// </summary>
        public void DeleteMemento()
        {
            //最初に保存してあるデータは削除させない
            if (m_UnDoStack.Count <= 1) return;        
            m_UnDoStack.Pop();        
        }
        /// <summary>
        ///スタックにたまっているデータを破棄
        /// </summary>
        public void ClearSaveMemento()
        {
            m_UnDoStack.Clear();
            m_ReDoStack.Clear();
        }
        /// <summary>
        /// 今のグラフと前の操作時のグラフが一致しているか
        /// </summary>
        /// <param name="list"></param>
        /// <param name="st"></param>
        /// <returns></returns>
        public bool isListMatch(ref List<BezierPoint> list,  List<BezierPoint> st)
        {
            int listSize = list.Count();
            if (listSize != st.Count()) return false;
            for(int i = 0; i < listSize; i++)
            {
                if (!list[i].Equals(st[i])) return false;
            }
            return true;
        }

終わりに

実は一部戻るに対応してないところがあります。 それはマウスホイールで値を動かしたときです。 マウスホイールが終わったタイミングの取得方法がわからず、現時点では実装が大変そうなので、あとまわしにします。

次かその次くらいで取り合ず最低限やりたかったことは終わらせそう。 だが最後一箇所は苦手な数学を使う所なので結構時間かかりそう。

次の記事 bravememo.hatenablog.com

C# カーブエディタ作成 上書き保存

前の記事

bravememo.hatenablog.com

完成図


CurveEditor 上書き保存

上書き保存

上書き保存は一回保存したかファイルを開いてないと出来ないものです。

ですので保存か開くをした際に上書き保存のEnabledプロパティをtrueにしてそれ以外はfalseにして上書き保存できるタイミングを制限します。

保存する際のパスは開くか新規で保存した際に保存データのパスを保持しています。

なおグラフに変更があったかまでは上書き保存できるかの判定には含めてないです(めんどいから)

カーソル変更

上書き保存してるときなにも表示がないとちゃんと保存できているかわからないため保存中はカーソルの表記を変えるようにしました。

カーソルの変更はthis.Cursorプロパティを変更すればできます。 上書き保存してるときは以下のカーソルにしました。

f:id:Brave345:20191219165143p:plain

ただ保存が早くてほぼ見えなかったのでThread.Sleepで少しだけ止めて読み込み中カーソルの表示時間を無理やり伸ばしています。

あとついでに点を移動させてるときもカーソルの変更をさせました。

f:id:Brave345:20191219165131p:plain

ソースコード

Formクラスまで書くと長くなるので追加したクラスだけ掲載します。

  public partial class Form1 : Form
 {
    private string editFilePath = "";  //編集中のファイルのパス
        //データの読み書きが早くてカーソルの変更がわからないので待機時間を設ける
        const int WaitTime = 300;

        /// <summary>
        /// マウスを動かしてる間の処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            m_CurvePointControl.MovePoint(e);
            numericUpDownSync();
            pictureBox1.Refresh();//再描画
            //点を移動してるときはカーソルの見た目を変える
            if (m_CurvePointControl.isMoveSelectPoint())
            {
                this.Cursor = Cursors.SizeAll;
            }
            else
            {
                if (this.Cursor != Cursors.AppStarting) this.Cursor = Cursors.Default;
            }
        }  

        /// <summary>
        /// 開く
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuPoen_Click(object sender, EventArgs e)
        {
            openFileDialog1.FileName = "";
            openFileDialog1.ShowDialog();
        }
        /// <summary>
        /// CSVを開いてグラフを表示
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void openFileDialog1_FileOk(object sender, CancelEventArgs e)
        {
            this.Cursor = Cursors.AppStarting;
            m_CurvePointControl.LoadGraph(openFileDialog1.FileName);
            editFilePath = openFileDialog1.FileName;
            menuSave.Enabled = true;     
            pictureBox1.Refresh();//再描画
            Thread.Sleep(WaitTime);
            this.Cursor = Cursors.Default;
        }
        /// <summary>
        /// 名前を付けて保存
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuSaveAdd_Click(object sender, EventArgs e)
        {
            saveFileDialog1.FileName = "new.csv";
            saveFileDialog1.ShowDialog();
        }
        /// <summary>
        /// ファイルをCSVに保存
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void saveFileDialog1_FileOk(object sender, CancelEventArgs e)
        {
            this.Cursor = Cursors.AppStarting;
            m_CurvePointControl.SaveGraph(saveFileDialog1.FileName);
            editFilePath = saveFileDialog1.FileName;
            menuSave.Enabled = true;
            Thread.Sleep(WaitTime);
            this.Cursor = Cursors.Default;
        }
        /// <summary>
        /// 上書き保存
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuSave_Click(object sender, EventArgs e)
        {
            this.Cursor = Cursors.AppStarting;
            m_CurvePointControl.SaveGraph(editFilePath);
            Thread.Sleep(WaitTime);
            this.Cursor = Cursors.Default;
        }
}

終わりに

これであとは戻る、進む機能とこのグラフをほかのアプリ側で読み込む機能で最初に求めていた機能は完成します。 だがここからがどうすれば実装できるかあまり検討がついてないので実装には時間がかかりそう

次の記事

C# カーブエディタ作成 グラフデータの読み書き

前の記事 bravememo.hatenablog.com

完成図

この動画では点を適当に追加→名前を付けて保存→新規作成でグラフリセット→開くを行っています。


CurveEditor グラフデータの読み書き

データの読み書き

・menuScriptの設定

まずは設定したい項目のクリックイベントを作成します。 (今回は開くと名前を付けて保存) 次にファイルの読み込みと書き込みを行うためにツールバーからsaveFileDialogopenFileDailogをForm上に追加させます。

f:id:Brave345:20191219122819p:plain

次に作成したクリックイベントに~FileDialog1.ShowDialog();を呼べば各項目を選択したとき下記の画像がでるようになります。

f:id:Brave345:20191219122838p:plain

あとはファイルを選択した際に呼ばれるFindOKイベント内で データの読み書きをすれば完成です。 ファイル名は~FileDialog1.FileNameで取得できます。

f:id:Brave345:20191219122909p:plain

・データの保存形式

データはCSVで保存しています。 保存している情報は一行ごとに開始点、制御点、終了点の座標データを保存しています。

保存はSaveでやっており、座標は0~1に変換した状態で保存しています。(今後別のところでグラフデータを読み込みする際データの確認をわかりやすくするため)

クラスを分割

いままでFormクラスに記述してたChageNomalPosXなどの数学系の関数を新しく作成したCMathクラスにまとめました。

ソースコード

Formクラスまで書くと長くなるので追加したクラスだけ掲載します。

class Form1
{
        /// <summary>
        /// 開く
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuPoen_Click(object sender, EventArgs e)
        {
            openFileDialog1.ShowDialog();
        }
        /// <summary>
        /// CSVを開いてグラフを表示
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void openFileDialog1_FileOk(object sender, CancelEventArgs e)
        {            m_CurvePointControl.LoadGraph(openFileDialog1.FileName);
            pictureBox1.Refresh();//再描画
        }
        /// <summary>
        /// 名前を付けて保存
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuSaveAdd_Click(object sender, EventArgs e)
        {
            saveFileDialog1.FileName = "new.csv";
            saveFileDialog1.ShowDialog();
        }
        /// <summary>
        /// ファイルをCSVに保存
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void saveFileDialog1_FileOk(object sender, CancelEventArgs e)
        {
            m_CurvePointControl.SaveGraph(saveFileDialog1.FileName);
        }
}
  class CurvePointControl
{
   //データ読み書き機能
        CurveEditorStream m_stream = new CurveEditorStream();

        /// <summary>
        /// CSVデータからグラフの点データを読み込む
        /// </summary>
        /// <returns></returns>
        public void LoadGraph(string s)
        {
            CurveEditorInit();
            m_list = m_stream.Load(s);
        }

        /// <summary>
        /// CSVデータにグラフの点データを書き込む
        /// </summary>
        /// <returns></returns>
        public void SaveGraph(string s)
        {
            m_stream.Save(ref m_list,s);
        }
}
  //CSVからグラフを読み書きするクラス
    class CurveEditorStream
    {
        /// <summary>
        /// 文字列から座標に変換
        /// </summary>
        /// <param name="values"></param>
        CurvePointControl.BezierPoint ToBezierPoint(string[] values)
        {
            //文字列を数値に変換
            CurvePointControl.BezierPoint bp = new CurvePointControl.BezierPoint();
            bp.startPoint.X = CMath.ChageNomalPosX(decimal.Parse(values[0]));
            bp.startPoint.Y = CMath.ChageNomalPosY(decimal.Parse(values[1]));
            bp.controlPoint1.X = CMath.ChageNomalPosX(decimal.Parse(values[2]));
            bp.controlPoint1.Y = CMath.ChageNomalPosY(decimal.Parse(values[3]));
            bp.controlPoint2.X = CMath.ChageNomalPosX(decimal.Parse(values[4]));
            bp.controlPoint2.Y = CMath.ChageNomalPosY(decimal.Parse(values[5]));
            bp.endPoint.X = CMath.ChageNomalPosX(decimal.Parse(values[6]));
            bp.endPoint.Y = CMath.ChageNomalPosY(decimal.Parse(values[7]));
            return bp;
        }
        /// <summary>
        /// CSVからデータを読み込み
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public List<CurvePointControl.BezierPoint> Load(string s)
        {
            List<CurvePointControl.BezierPoint> list = new List<CurvePointControl.BezierPoint>();
            StreamReader file = new StreamReader(s);

              //末尾まで繰り返す
            while (!file.EndOfStream)
            {
               // CSVファイルの一行を読み込む
                string line = file.ReadLine();
                // 読み込んだ一行をカンマ毎に分けて配列に格納する
                string[] values = line.Split(',');
                CurvePointControl.BezierPoint b = ToBezierPoint(values);
                list.Add(b);
            }
            file.Close();
            return list;
        }
        /// <summary>
        /// CSVにデータを書き込む
        /// </summary>
        /// <param name="List"></param>
        public void Save(ref List<CurvePointControl.BezierPoint> List,string s)
        {
            try
            {
                StreamWriter file = new StreamWriter(s, false, Encoding.UTF8);
                int size = List.Count;
                for(int i = 0; i < size;i++)
                {
                    //座標を0~1の間に変換し文字列化させる
                    string [] name = new string[4];
                    name = ToSting(List[i]);
                    file.Write(name[0]+ "," + name[1] + "," + name[2] + "," + name[3] + "\n");
                }
                file.Close();
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
                Console.WriteLine(e.Message);       // エラーメッセージを表示
            }
        }
        /// <summary>
        ///  //座標を0~1の間に変換し文字列化させる
        /// </summary>
        /// <param name="bs"></param>
        /// <returns></returns>
        public string[] ToSting( CurvePointControl.BezierPoint bs)
        {
            //座標を0~1の間に変換し文字列化させる
            string[] name = new string[4];
            name[0] = CMath.ChageDecimalPosX(bs.startPoint.X).ToString()
                + "," + CMath.ChageDecimalPosY(bs.startPoint.Y).ToString();
            name[1] = CMath.ChageDecimalPosX(bs.controlPoint1.X).ToString()
                + "," + CMath.ChageDecimalPosY(bs.controlPoint1.Y).ToString();
            name[2] = CMath.ChageDecimalPosX(bs.controlPoint2.X).ToString()
                + "," + CMath.ChageDecimalPosY(bs.controlPoint2.Y).ToString();
            name[3] = CMath.ChageDecimalPosX(bs.endPoint.X).ToString()
                + "," + CMath.ChageDecimalPosY(bs.endPoint.Y).ToString();

            return name;
        }
    }
 //カーブエディタで使う数学関数
    class CMath
    {
        /// <summary>
        /// 小数から元の座標に変換
        /// </summary>
        /// <param name="Value"></param>
        /// <returns></returns>
        public static int ChageNomalPosX(decimal Value)
        {
            decimal num = Value;

            decimal num2 = (num * (decimal)500);
            int num3 = Convert.ToInt32(num2);

            num3 += 10;//10からグラフが始まってるので右に+10
            return num3;
        }
        /// <summary>
        /// 元の座標から0~1の間に変換
        /// </summary>
        /// <param name="posX"></param>
        /// <returns></returns>
        public static decimal ChageDecimalPosX(int posX)
        {
            posX = Math.Max(0, posX - 10); //10からグラフが始まってるので右に-10
            return (decimal)posX / (decimal)500;
        }
        
        /// <summary>
        /// 小数から元の座標に変換
        /// </summary>
        /// <param name="Value"></param>
        /// <returns></returns>
        public static int ChageNomalPosY(decimal Value)
        {
            decimal num = Value;

            decimal num2 = num * (decimal)500;
            int num3 = Convert.ToInt32(num2);

            num3 = num3 - 500;
            num3 = num3 * -1;
            num3 += 10;//10からグラフが始まってるので右に+10

            return num3;
        }
        /// <summary>
        /// 元の座標から0~1の間に変換
        /// </summary>
        /// <param name="posY"></param>
        /// <returns></returns>
        public static decimal ChageDecimalPosY(int posY)
        {
            //  posY = Math.Min(500, posY - 10);
            //10からグラフが始まってるので右に-10
            posY = Math.Max(0, posY - 10);
            posY = posY - 500;
            posY = posY * -1;

            return Math.Max(0, (decimal)posY / (decimal)500);
        }
        /// <summary>
        /// 値を制限させる
        /// </summary>
        /// <param name="x"></param>
        /// <param name="minVal"></param>
        /// <param name="maxVal"></param>
        /// <returns></returns>
        public static int Clamp(int x, int minVal, int maxVal)
        {
            return Math.Min(Math.Max(minVal, x), maxVal);
        }
    }

終わりに

カーブエディタ作成もだんだん終わりが近づいてきました。 次は上書き保存を実装していきます。

次の記事

bravememo.hatenablog.com