using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace _3dDraw
{
/// <summary>
/// 複数の数式と媒介変数の範囲を保持するためのパラメータクラス
/// </summary>
public class EquationParameter
{
public string ExprX { get; set; }
public string ExprY { get; set; }
public string ExprZ { get; set; }
public int TStart { get; set; }
public int TEnd { get; set; }
// コンストラクタで簡単に初期化できるようにする
public EquationParameter(string exprX, string exprY, string exprZ, int tStart, int tEnd)
{
ExprX = exprX;
ExprY = exprY;
ExprZ = exprZ;
TStart = tStart;
TEnd = tEnd;
}
}
/// <summary>
/// 数式テキストを解析して3次元座標を自動生成し、描画を支援するクラス。
/// CoordinateConverter を継承しています。
/// </summary>
public class ParametricEquationGenerator : CoordinateConverter
{
/// <summary>
/// X, Y, Z軸の数式と媒介変数 t の範囲を指定して、ポイントデータを生成し Points3D にセットします。
/// </summary>
/// <param name="exprX">X軸の数式 (例: "const[20]+cos{param[] * const[3]}")</param>
/// <param name="exprY">Y軸の数式</param>
/// <param name="exprZ">Z軸の数式</param>
/// <param name="tStart">媒介変数 t の開始値</param>
/// <param name="tEnd">媒介変数 t の終了値</param>
public void GenerateAndSetPoints(string exprX, string exprY, string exprZ, int tStart, int tEnd)
{
// 単一のデータを生成して配列にセット
this.Points3D = new double[][,] { GeneratePointsSingle(exprX, exprY, exprZ, tStart, tEnd) };
}
/// <summary>
/// 複数の数式セットを同時に渡して、複数のポイントデータを生成し Points3D にセットします。
/// </summary>
/// <param name="parameters">生成する式のパラメータの可変長配列</param>
public void GenerateAndSetPoints(params EquationParameter[] parameters)
{
if (parameters == null || parameters.Length == 0) return;
List<double[,]> pointsList = new List<double[,]>();
foreach (var param in parameters)
{
// 各パラメータごとに座標配列を生成してリストに追加
pointsList.Add(GeneratePointsSingle(param.ExprX, param.ExprY, param.ExprZ, param.TStart, param.TEnd));
}
// 親クラスの配列プロパティに一括セット
this.Points3D = pointsList.ToArray();
}
/// <summary>
/// 単一の数式セットから座標配列を生成する内部メソッド
/// </summary>
private double[,] GeneratePointsSingle(string exprX, string exprY, string exprZ, int tStart, int tEnd)
{
int count = tEnd - tStart + 1;
if (count <= 0)
{
throw new ArgumentException("tEnd は tStart 以上である必要があります。");
}
double[,] generatedPoints = new double[count, 3];
for (int i = 0; i < count; i++)
{
double t = tStart + i; // 媒介変数を1ずつ変化させる
// それぞれの軸の数式を評価して座標を計算
generatedPoints[i, 0] = EvaluateExpression(exprX, t);
generatedPoints[i, 1] = EvaluateExpression(exprY, t);
generatedPoints[i, 2] = EvaluateExpression(exprZ, t);
}
return generatedPoints;
}
/// <summary>
/// 独自の数式ルールを解析して計算結果を返します。
/// </summary>
private double EvaluateExpression(string expression, double t)
{
// 空白を削除して処理しやすくする
string expr = expression.Replace(" ", "");
// 1. 媒介変数 param[] を t の値に置換
expr = expr.Replace("param[]", t.ToString("F6"));
// 2. 定数 const[値] を実際の数値に置換
// 例: const[20] -> 20, const[-3.5] -> -3.5
expr = Regex.Replace(expr, @"const\[(-?[0-9.]+)\]", "$1");
// 3. 三角関数 sin{}, cos{}, tan{} の処理 (ネストにも対応するため内側から評価)
var regexTrig = new Regex(@"(sin|cos|tan)\{([^\{\}]+)\}");
while (regexTrig.IsMatch(expr))
{
expr = regexTrig.Replace(expr, match =>
{
string func = match.Groups[1].Value.ToLower();
string innerExpr = match.Groups[2].Value; // { } の中身の数式
// 中身の数式を四則演算で計算
double innerValue = CalculateBasicMath(innerExpr);
// Degree(度)からRadian(ラジアン)に変換して三角関数を適用
double rad = innerValue * Math.PI / 180.0;
double result = 0;
if (func == "sin") result = Math.Sin(rad);
else if (func == "cos") result = Math.Cos(rad);
else if (func == "tan") result = Math.Tan(rad);
return result.ToString("F6"); // 計算結果の数値文字列で置き換え
});
}
// 4. 三角関数などの処理が終わった後、残った四則演算を計算して返す
return CalculateBasicMath(expr);
}
/// <summary>
/// 文字列の四則演算 (+, -, *, /) を評価して計算します。
/// C#標準の DataTable.Compute を利用して解析の手間を省いています。
/// </summary>
private double CalculateBasicMath(string expr)
{
// 計算機の構文エラーを防ぐため、符号の連続を整理 (例: 20+-5 -> 20-5)
expr = expr.Replace("+-", "-").Replace("-+", "-").Replace("--", "+").Replace("++", "+");
try
{
using (var dt = new DataTable())
{
// DataTable.Compute は文字列の数式(例: "1+2*3")を自動で計算してくれます
var result = dt.Compute(expr, "");
// 名前衝突を避けるため System.Convert を明示的に指定
return System.Convert.ToDouble(result);
}
}
catch (Exception ex)
{
throw new Exception($"数式の四則演算に失敗しました。数式: '{expr}'", ex);
}
}
}
}