e3cfab3dc7
The previous InvisibleButton captured mouse events, so you could drag from the Functions palette into the canvas, but node dragging and slider interaction inside the canvas stopped working. Fix: watch the global drag-drop payload without an explicit target. When the mouse releases LMB over the DAG window with a "DAG_NODE_TYPE" payload active, queue an add at that canvas position. No button, no capture. Tests (compiled standalone with preprocessor defines): - dag_compile: 6/6 asserts (empty, single gen, op chain, multi-source blend, Output-driven fragColor, unconnected-Output fallback). - dag_catalog: 8/8 asserts (uniqueness, per-kind input invariants, exactly one Output, body_glsl present & returns, control param indices valid). Build with: g++ -std=c++17 -Icpp/functions -DDAG_COMPILE_TEST cpp/functions/gfx/dag_compile.cpp cpp/functions/gfx/dag_catalog.cpp -o /tmp/dag_compile_test g++ -std=c++17 -Icpp/functions -DDAG_CATALOG_TEST cpp/functions/gfx/dag_catalog.cpp -o /tmp/dag_catalog_test Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
335 lines
14 KiB
C++
335 lines
14 KiB
C++
#include "gfx/dag_catalog.h"
|
|
#include <string>
|
|
|
|
namespace fn::gfx {
|
|
|
|
static const std::vector<DagNodeDef>& build_catalog() {
|
|
static std::vector<DagNodeDef> catalog = []() {
|
|
std::vector<DagNodeDef> v;
|
|
|
|
// ── Gen: solid ────────────────────────────────────────────────
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "solid";
|
|
n.label = "solid";
|
|
n.desc = "color constante";
|
|
n.kind = DagKind::Gen;
|
|
n.num_inputs = 0;
|
|
n.param_names = {"r", "g", "b", ""};
|
|
n.param_defaults = {0.35f, 0.25f, 0.55f, 0.0f};
|
|
n.controls = {
|
|
{ DagControl::Kind::Color, "color", {0, 1, 2}, 0.0f, 1.0f, 0.0f },
|
|
};
|
|
n.body_glsl = [](int idx) -> std::string {
|
|
std::string i = std::to_string(idx);
|
|
return " vec4 p = u_params[" + i + "];\n"
|
|
" return vec4(p.x, p.y, p.z, 1.0);";
|
|
};
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
// ── Gen: gradient ─────────────────────────────────────────────
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "gradient";
|
|
n.label = "gradient";
|
|
n.desc = "gradiente direccional";
|
|
n.kind = DagKind::Gen;
|
|
n.num_inputs = 0;
|
|
n.param_names = {"angle", "hue", "", ""};
|
|
n.param_defaults = {0.8f, 0.5f, 0.0f, 0.0f};
|
|
n.controls = {
|
|
{ DagControl::Kind::Slider, "angulo", {0, -1, -1}, 0.0f, 6.2832f, 0.01f },
|
|
{ DagControl::Kind::Slider, "tono", {1, -1, -1}, 0.0f, 1.0f, 0.01f },
|
|
};
|
|
n.body_glsl = [](int idx) -> std::string {
|
|
std::string i = std::to_string(idx);
|
|
return " vec4 p = u_params[" + i + "];\n"
|
|
" vec2 dir = vec2(cos(p.x), sin(p.x));\n"
|
|
" float t = dot(uv - 0.5, dir) + 0.5;\n"
|
|
" vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.y + vec3(0.0, 0.33, 0.67) + t));\n"
|
|
" return vec4(col, 1.0);";
|
|
};
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
// ── Gen: plasma ───────────────────────────────────────────────
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "plasma";
|
|
n.label = "plasma";
|
|
n.desc = "onda trigonometrica";
|
|
n.kind = DagKind::Gen;
|
|
n.num_inputs = 0;
|
|
n.param_names = {"speed", "scale", "", ""};
|
|
n.param_defaults = {1.0f, 2.0f, 0.0f, 0.0f};
|
|
n.controls = {
|
|
{ DagControl::Kind::Slider, "velocidad", {0, -1, -1}, 0.0f, 3.0f, 0.01f },
|
|
{ DagControl::Kind::Slider, "escala", {1, -1, -1}, 0.5f, 10.0f, 0.1f },
|
|
};
|
|
n.body_glsl = [](int idx) -> std::string {
|
|
std::string i = std::to_string(idx);
|
|
return " vec4 p = u_params[" + i + "];\n"
|
|
" vec3 col = 0.5 + 0.5 * cos(u_time * p.x + uv.xyx * p.y + vec3(0.0, 2.0, 4.0));\n"
|
|
" return vec4(col, 1.0);";
|
|
};
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
// ── Op: circle ────────────────────────────────────────────────
|
|
// Reclassified as Op (num_inputs=1): composites circle over input 'a'
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "circle";
|
|
n.label = "circle";
|
|
n.desc = "sdf de circulo (composita sobre input)";
|
|
n.kind = DagKind::Op;
|
|
n.num_inputs = 1;
|
|
n.param_names = {"cx", "cy", "radius", "soft"};
|
|
n.param_defaults = {0.0f, 0.0f, 0.35f, 0.01f};
|
|
n.controls = {
|
|
{ DagControl::Kind::XY, "centro", {0, 1, -1}, -0.8f, 0.8f, 0.01f },
|
|
{ DagControl::Kind::Slider, "radio", {2, -1, -1}, 0.0f, 1.0f, 0.01f },
|
|
{ DagControl::Kind::Slider, "suavidad", {3, -1, -1}, 0.001f, 0.1f, 0.001f },
|
|
};
|
|
n.body_glsl = [](int idx) -> std::string {
|
|
std::string i = std::to_string(idx);
|
|
return " vec4 p = u_params[" + i + "];\n"
|
|
" float aspect = u_resolution.x / u_resolution.y;\n"
|
|
" vec2 pos = vec2((uv.x - 0.5) * aspect - p.x, uv.y - 0.5 - p.y);\n"
|
|
" float d = length(pos) - p.z;\n"
|
|
" float fill = smoothstep(p.w, -p.w, d);\n"
|
|
" return mix(a, vec4(1.0), fill);";
|
|
};
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
// ── Op: invert ────────────────────────────────────────────────
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "invert";
|
|
n.label = "invert";
|
|
n.desc = "1 - rgb";
|
|
n.kind = DagKind::Op;
|
|
n.num_inputs = 1;
|
|
n.param_names = {"", "", "", ""};
|
|
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
n.controls = {};
|
|
n.body_glsl = [](int /*idx*/) -> std::string {
|
|
return " return vec4(1.0 - a.rgb, a.a);";
|
|
};
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
// ── Op: gamma ─────────────────────────────────────────────────
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "gamma";
|
|
n.label = "gamma";
|
|
n.desc = "pow(rgb, gamma)";
|
|
n.kind = DagKind::Op;
|
|
n.num_inputs = 1;
|
|
n.param_names = {"gamma", "", "", ""};
|
|
n.param_defaults = {1.0f, 0.0f, 0.0f, 0.0f};
|
|
n.controls = {
|
|
{ DagControl::Kind::Slider, "gamma", {0, -1, -1}, 0.1f, 4.0f, 0.01f },
|
|
};
|
|
n.body_glsl = [](int idx) -> std::string {
|
|
std::string i = std::to_string(idx);
|
|
return " vec4 p = u_params[" + i + "];\n"
|
|
" return vec4(pow(a.rgb, vec3(1.0 / max(p.x, 0.001))), a.a);";
|
|
};
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
// ── Op: hueShift ──────────────────────────────────────────────
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "hueShift";
|
|
n.label = "hue shift";
|
|
n.desc = "rotar matiz";
|
|
n.kind = DagKind::Op;
|
|
n.num_inputs = 1;
|
|
n.param_names = {"h", "", "", ""};
|
|
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
n.controls = {
|
|
{ DagControl::Kind::Slider, "h", {0, -1, -1}, 0.0f, 1.0f, 0.01f },
|
|
};
|
|
n.body_glsl = [](int idx) -> std::string {
|
|
std::string i = std::to_string(idx);
|
|
return " vec4 p = u_params[" + i + "];\n"
|
|
" float ang = 6.28318 * p.x;\n"
|
|
" float ca = cos(ang), sa = sin(ang);\n"
|
|
" mat3 hueMat = mat3(\n"
|
|
" vec3(0.299 + 0.701 * ca + 0.168 * sa, 0.587 - 0.587 * ca + 0.330 * sa, 0.114 - 0.114 * ca - 0.497 * sa),\n"
|
|
" vec3(0.299 - 0.299 * ca - 0.328 * sa, 0.587 + 0.413 * ca + 0.035 * sa, 0.114 - 0.114 * ca + 0.292 * sa),\n"
|
|
" vec3(0.299 - 0.300 * ca + 1.250 * sa, 0.587 - 0.588 * ca - 1.050 * sa, 0.114 + 0.886 * ca - 0.203 * sa)\n"
|
|
" );\n"
|
|
" return vec4(clamp(hueMat * a.rgb, 0.0, 1.0), a.a);";
|
|
};
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
// ── Blend: mix ────────────────────────────────────────────────
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "blend_mix";
|
|
n.label = "mix";
|
|
n.desc = "interpolacion mix(a, b, t)";
|
|
n.kind = DagKind::Blend;
|
|
n.num_inputs = 2;
|
|
n.param_names = {"t", "", "", ""};
|
|
n.param_defaults = {0.5f, 0.0f, 0.0f, 0.0f};
|
|
n.controls = {
|
|
{ DagControl::Kind::Slider, "t", {0, -1, -1}, 0.0f, 1.0f, 0.01f },
|
|
};
|
|
n.body_glsl = [](int idx) -> std::string {
|
|
std::string i = std::to_string(idx);
|
|
return " vec4 p = u_params[" + i + "];\n"
|
|
" return mix(a, b, p.x);";
|
|
};
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
// ── Blend: multiply ───────────────────────────────────────────
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "blend_multiply";
|
|
n.label = "multiply";
|
|
n.desc = "a * b";
|
|
n.kind = DagKind::Blend;
|
|
n.num_inputs = 2;
|
|
n.param_names = {"", "", "", ""};
|
|
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
n.controls = {};
|
|
n.body_glsl = [](int /*idx*/) -> std::string {
|
|
return " return vec4(a.rgb * b.rgb, a.a);";
|
|
};
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
// ── Blend: screen ─────────────────────────────────────────────
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "blend_screen";
|
|
n.label = "screen";
|
|
n.desc = "1 - (1-a)(1-b)";
|
|
n.kind = DagKind::Blend;
|
|
n.num_inputs = 2;
|
|
n.param_names = {"", "", "", ""};
|
|
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
n.controls = {};
|
|
n.body_glsl = [](int /*idx*/) -> std::string {
|
|
return " return vec4(1.0 - (1.0 - a.rgb) * (1.0 - b.rgb), a.a);";
|
|
};
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
// ── Output (sink — drives fragColor) ─────────────────────────
|
|
{
|
|
DagNodeDef n;
|
|
n.name = "output";
|
|
n.label = "Output";
|
|
n.desc = "canvas DAG output";
|
|
n.kind = DagKind::Output;
|
|
n.num_inputs = 1;
|
|
n.param_names = {"", "", "", ""};
|
|
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
n.controls = {};
|
|
n.body_glsl = [](int) -> std::string { return ""; };
|
|
v.push_back(std::move(n));
|
|
}
|
|
|
|
return v;
|
|
}();
|
|
return catalog;
|
|
}
|
|
|
|
const std::vector<DagNodeDef>& dag_catalog() {
|
|
return build_catalog();
|
|
}
|
|
|
|
const DagNodeDef* dag_find(const std::string& name) {
|
|
for (const auto& n : dag_catalog()) {
|
|
if (n.name == name) return &n;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace fn::gfx
|
|
|
|
#ifdef DAG_CATALOG_TEST
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <set>
|
|
|
|
int main() {
|
|
using namespace fn::gfx;
|
|
const auto& cat = dag_catalog();
|
|
|
|
// 1. Catalog not empty
|
|
assert(!cat.empty());
|
|
|
|
// 2. All node names are unique
|
|
std::set<std::string> names;
|
|
for (const auto& n : cat) {
|
|
assert(names.insert(n.name).second && "duplicate node name");
|
|
}
|
|
|
|
// 3. Invariants by kind
|
|
int gen_count = 0, op_count = 0, blend_count = 0, output_count = 0;
|
|
for (const auto& n : cat) {
|
|
switch (n.kind) {
|
|
case DagKind::Gen:
|
|
assert(n.num_inputs == 0 && "Gen must have 0 inputs");
|
|
++gen_count; break;
|
|
case DagKind::Op:
|
|
assert(n.num_inputs >= 1 && "Op must have >= 1 input");
|
|
++op_count; break;
|
|
case DagKind::Blend:
|
|
assert(n.num_inputs >= 2 && "Blend must have >= 2 inputs");
|
|
++blend_count; break;
|
|
case DagKind::Output:
|
|
assert(n.num_inputs == 1 && "Output must have exactly 1 input");
|
|
++output_count; break;
|
|
}
|
|
assert(n.num_inputs >= 0 && n.num_inputs <= 4);
|
|
assert(n.body_glsl && "body_glsl must be set");
|
|
}
|
|
|
|
// 4. Exactly one Output in the catalog
|
|
assert(output_count == 1 && "catalog must declare exactly one Output node");
|
|
|
|
// 5. At least one Gen and one Op
|
|
assert(gen_count >= 1);
|
|
assert(op_count >= 1);
|
|
assert(blend_count >= 1);
|
|
|
|
// 6. dag_find works and returns nullptr for unknown names
|
|
assert(dag_find("output") != nullptr);
|
|
assert(dag_find("plasma") != nullptr);
|
|
assert(dag_find("__nonexistent__") == nullptr);
|
|
|
|
// 7. Every non-Output body emits non-empty GLSL
|
|
for (const auto& n : cat) {
|
|
if (n.kind == DagKind::Output) continue;
|
|
auto body = n.body_glsl(0);
|
|
assert(!body.empty() && "non-Output nodes must emit body");
|
|
// sanity: should contain a return statement
|
|
assert(body.find("return") != std::string::npos);
|
|
}
|
|
|
|
// 8. Control param indices stay within 0..3
|
|
for (const auto& n : cat) {
|
|
for (const auto& c : n.controls) {
|
|
for (int idx : c.param_idx) {
|
|
assert(idx >= -1 && idx < 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::printf("dag_catalog: 8/8 asserts passed (%zu nodes)\n", cat.size());
|
|
return 0;
|
|
}
|
|
#endif
|