ChatGPTが生成したトーンカーブの描画プログラムを修正,改善: https://chatgpt.com/share/6784f821-f064-8009-93bb-a07882318f27
Created
January 14, 2025 11:11
-
-
Save sthairno/81bf2ccacd6700372867c1c2c4274aa3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# include <Siv3D.hpp> // OpenSiv3D v0.6.15 | |
# include <Eigen/Dense> // Eigenライブラリのインクルード | |
// 与えられたデータ点をもとに多項式近似を行う関数 | |
Array<double> PolynomialApproximation(const Array<Vec2>& points, int degree) | |
{ | |
const int n = points.size(); | |
const int m = degree + 1; | |
// 行列を構築 | |
Eigen::MatrixXd A(n, m); | |
Eigen::VectorXd b(n); | |
for (int i = 0; i < n; ++i) | |
{ | |
double x = points[i].x; | |
double y = points[i].y; | |
for (int j = 0; j < m; ++j) | |
{ | |
A(i, j) = std::pow(x, j); | |
} | |
b(i) = y; | |
} | |
// 正規方程式を解く | |
Eigen::VectorXd coefficients = A.householderQr().solve(b); | |
// 結果をArrayに変換 | |
Array<double> result; | |
for (int i = 0; i < m; ++i) | |
{ | |
result << coefficients[i]; | |
} | |
return result; | |
} | |
// N次多項式の値を計算する関数 | |
// f(x) = clamp(c0 + c1 * x + c2 * x^2 + c3 * x^3 + ... + cN * x^N, 0.0, 1.0) | |
double EvaluatePolynomial(const Array<double>& coefficients, double x) | |
{ | |
double result = 0.0; | |
for (size_t i = 0; i < coefficients.size(); ++i) | |
{ | |
result += coefficients[i] * std::pow(x, i); | |
} | |
return Clamp(result, 0.0, 1.0); | |
} | |
void Main() | |
{ | |
// 背景を白に設定 | |
Scene::SetBackground(Palette::White); | |
// 初期状態のデータ点 | |
Array<Vec2> points{ {0, 0}, {1, 1} }; | |
// 係数 | |
Array<double> coefficients; | |
bool dirty = true; | |
// ドラッグ中のデータ点の番号 | |
Optional<size_t> draggingPointIdx; | |
// 描画済みグラフ | |
LineString graph01; | |
double graphMinDistance = 1.0 / 100; | |
// コントロールポイント | |
constexpr double ControlPointRadius = 5.0; | |
Font font{ 16 }; | |
// メインループ | |
while (System::Update()) | |
{ | |
dirty |= SimpleGUI::Slider(U"グラフ精度", graphMinDistance, 1, 0.01, { 10, 10 }, 120, 200); | |
String displayText; | |
{ | |
displayText += U"近似式: f(x) = "; | |
bool hasPrevTerm = false; | |
for (size_t i = 0; i < coefficients.size(); ++i) | |
{ | |
if (coefficients[i] == 0.0) | |
{ | |
continue; | |
} | |
if (hasPrevTerm) | |
{ | |
displayText += U" + "; | |
} | |
switch (i) | |
{ | |
case 0: | |
displayText += U"{:.2}"_fmt(coefficients[i]); | |
break; | |
case 1: | |
displayText += U"{:.2}x"_fmt(coefficients[i]); | |
break; | |
default: | |
displayText += U"{:.2}x^{}"_fmt(coefficients[i], i); | |
break; | |
} | |
hasPrevTerm = true; | |
} | |
if (not hasPrevTerm) | |
{ | |
displayText += U"0"; | |
} | |
} | |
displayText += U"\nコントロールポイント数: {}\n"_fmt(points.size()); | |
displayText += U"グラフ頂点数: {}\n"_fmt(graph01.size()); | |
font(displayText).draw(16, 46, Palette::Black); | |
if (dirty) | |
{ | |
// 係数を再計算 | |
coefficients = PolynomialApproximation(points, points.size()); | |
// グラフを再描画 | |
Vec2 zeroVertex{ 0, EvaluatePolynomial(coefficients, 0) }; // f(0) | |
Vec2 oneVertex{ 1, EvaluatePolynomial(coefficients, 1) }; // f(1) | |
Array<Vec2> sortedPoints = points.sorted_by([](const Vec2& a, const Vec2& b) { return a.x < b.x; }); | |
graph01.clear(); | |
for (auto i : Range(0, sortedPoints.size())) | |
{ | |
auto currentVertex = i != 0 ? sortedPoints[i - 1] : zeroVertex; | |
auto nextVertex = i < sortedPoints.size() ? sortedPoints[i] : oneVertex; | |
auto delta = nextVertex - currentVertex; | |
if (delta.x == 0) | |
{ | |
continue; | |
} | |
graph01 << currentVertex; | |
// 次の頂点との間隔からおおよその分割数を決定 | |
auto distance = delta.length(); | |
int splits = static_cast<int>(Floor(distance / graphMinDistance)); | |
for (auto j : Range(1, splits)) | |
{ | |
double x = currentVertex.x + (static_cast<double>(j) / (splits + 1)) * delta.x; | |
double y = EvaluatePolynomial(coefficients, x); | |
graph01 << Vec2{ x, y }; | |
} | |
} | |
// 最後の頂点を追加 | |
if (graph01.back().x < 1.0) | |
{ | |
graph01 << oneVertex; | |
} | |
dirty = false; | |
} | |
Rect canvasRect{ Arg::center = Scene::Center(), 400, 400 }; | |
// 更新処理 | |
{ | |
Mat3x2 graphToCanvas = Mat3x2{ | |
static_cast<float>(canvasRect.w), 0, | |
0, static_cast<float>(-canvasRect.h), | |
static_cast<float>(canvasRect.x), static_cast<float>(canvasRect.bottomY()), | |
}; | |
// ボタンを離したらドラッグ終了 | |
if (not MouseL.pressed()) | |
{ | |
draggingPointIdx.reset(); | |
} | |
if (not draggingPointIdx.has_value()) | |
{ | |
Optional<size_t> hoveredPointIdx; | |
for (size_t i : Range(points.size() - 1, 0, -1)) | |
{ | |
if (Circle{ graphToCanvas.transformPoint(points[i]), ControlPointRadius }.mouseOver()) | |
{ | |
hoveredPointIdx = i; | |
break; | |
} | |
} | |
// 左クリックされたらドラッグ開始 | |
if (MouseL.down()) | |
{ | |
if (hoveredPointIdx.has_value()) | |
{ | |
// マウスが重なっているデータ点をドラッグ | |
draggingPointIdx = *hoveredPointIdx; | |
} | |
else if (canvasRect.mouseOver()) | |
{ | |
// 新しいデータ点を追加してドラッグ | |
Vec2 cursorPos = Cursor::PosF(); | |
draggingPointIdx = points.size(); | |
points << Vec2{ | |
(cursorPos.x - canvasRect.x) / canvasRect.w, | |
1.0 - (cursorPos.y - canvasRect.y) / canvasRect.h | |
}; | |
dirty = true; | |
} | |
} | |
// 右クリックされたらデータ点を削除 (初期設定の2点を除く) | |
if (MouseR.down() && hoveredPointIdx >= 2) | |
{ | |
points.remove_at(*hoveredPointIdx); | |
dirty = true; | |
} | |
} | |
// ドラッグ処理 | |
if (draggingPointIdx.has_value()) | |
{ | |
Vec2& point = points[*draggingPointIdx]; | |
Vec2 delta = Cursor::DeltaF() / Vec2{ canvasRect.w, -canvasRect.h }; | |
if (not delta.isZero()) | |
{ | |
point = Vec2{ | |
Clamp(point.x + delta.x, 0.0, 1.0), | |
Clamp(point.y + delta.y, 0.0, 1.0) | |
}; | |
dirty = true; | |
} | |
} | |
} | |
// キャンバスを描画 | |
canvasRect.drawFrame(2, Palette::Black); | |
{ | |
Transformer2D t{ Mat3x2::Translate(canvasRect.pos) }; | |
Mat3x2 graphToCanvas = Mat3x2{ | |
static_cast<float>(canvasRect.w), 0, | |
0, static_cast<float>(-canvasRect.h), | |
0, static_cast<float>(canvasRect.h), | |
}; | |
// 基準線(?) | |
Line{ 0, canvasRect.h, canvasRect.w, 0 } | |
.draw(Palette::Gray); | |
// グラフ | |
LineString graph{ graph01.asArray().map([&](const Vec2& p) { return graphToCanvas.transformPoint(p); }) }; | |
graph.draw(2, Palette::Red); | |
// コントロールポイント | |
for (size_t i : Iota(0, points.size())) | |
{ | |
bool isRemovable = i >= 2; | |
Circle{ graphToCanvas.transformPoint(points[i]), ControlPointRadius } | |
.draw(draggingPointIdx == i ? Palette::Gray : Palette::White) | |
.drawFrame(1, isRemovable ? Palette::Gray : Palette::Red); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment