#include "gfx/dag_catalog.h" #include #include namespace fn::gfx { static std::vector& mutable_catalog() { static std::vector catalog = []() { std::vector 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}; 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}; 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}; 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 = {}; 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}; 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}; 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}; 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 = {}; 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 = {}; 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)); } // ── Gen: checker ────────────────────────────────────────────── { DagNodeDef n; n.name = "checker"; n.label = "checker"; n.desc = "tablero animado"; n.kind = DagKind::Gen; n.num_inputs = 0; n.param_names = {"scale", "rotation", "hue", "speed"}; n.param_defaults = {8.0f, 0.0f, 0.55f, 0.2f}; n.controls = { { DagControl::Kind::Slider, "escala", {0, -1, -1}, 1.0f, 32.0f, 0.1f }, { DagControl::Kind::Slider, "rotacion", {1, -1, -1}, 0.0f, 6.2832f, 0.01f }, { DagControl::Kind::Slider, "tono", {2, -1, -1}, 0.0f, 1.0f, 0.01f }, { DagControl::Kind::Slider, "velocidad",{3, -1, -1}, 0.0f, 3.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 = p.y + u_time * p.w * 0.2;\n" " float ca = cos(ang), sa = sin(ang);\n" " vec2 q = uv - 0.5;\n" " q = vec2(ca*q.x - sa*q.y, sa*q.x + ca*q.y) * p.x + 0.5;\n" " vec2 cell = floor(q);\n" " float c = mod(cell.x + cell.y, 2.0);\n" " vec3 ca_col = 0.5 + 0.5 * cos(6.28318 * (p.z + vec3(0.0, 0.33, 0.67)));\n" " vec3 cb_col = 0.5 + 0.5 * cos(6.28318 * (p.z + 0.5 + vec3(0.0, 0.33, 0.67)));\n" " return vec4(mix(cb_col, ca_col, c), 1.0);"; }; v.push_back(std::move(n)); } // ── Gen: stripes ────────────────────────────────────────────── { DagNodeDef n; n.name = "stripes"; n.label = "stripes"; n.desc = "bandas direccionales"; n.kind = DagKind::Gen; n.num_inputs = 0; n.param_names = {"angle", "freq", "phase_speed", "hue"}; n.param_defaults = {0.785f, 12.0f, 1.0f, 0.6f}; n.controls = { { DagControl::Kind::Slider, "angulo", {0, -1, -1}, 0.0f, 6.2832f, 0.01f }, { DagControl::Kind::Slider, "frecuencia", {1, -1, -1}, 1.0f, 64.0f, 0.5f }, { DagControl::Kind::Slider, "velocidad", {2, -1, -1}, 0.0f, 5.0f, 0.01f }, { DagControl::Kind::Slider, "tono", {3, -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) * p.y + u_time * p.z;\n" " float s = 0.5 + 0.5 * sin(t);\n" " vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.w + s + vec3(0.0, 0.33, 0.67)));\n" " return vec4(col, 1.0);"; }; v.push_back(std::move(n)); } // ── Gen: dots ───────────────────────────────────────────────── { DagNodeDef n; n.name = "dots"; n.label = "dots"; n.desc = "rejilla de puntos"; n.kind = DagKind::Gen; n.num_inputs = 0; n.param_names = {"scale", "radius", "soft", "hue"}; n.param_defaults = {16.0f, 0.3f, 0.05f, 0.7f}; n.controls = { { DagControl::Kind::Slider, "escala", {0, -1, -1}, 1.0f, 64.0f, 0.5f }, { DagControl::Kind::Slider, "radio", {1, -1, -1}, 0.0f, 0.5f, 0.01f }, { DagControl::Kind::Slider, "suavidad", {2, -1, -1}, 0.001f, 0.2f, 0.001f }, { DagControl::Kind::Slider, "tono", {3, -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 aspect = u_resolution.x / u_resolution.y;\n" " vec2 q = vec2((uv.x - 0.5) * aspect, uv.y - 0.5) * p.x;\n" " vec2 cell = fract(q) - 0.5;\n" " float d = length(cell) - p.y;\n" " float fill = smoothstep(p.z, -p.z, d);\n" " vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.w + vec3(0.0, 0.33, 0.67)));\n" " return vec4(col * fill, 1.0);"; }; v.push_back(std::move(n)); } // ── Gen: rings ──────────────────────────────────────────────── { DagNodeDef n; n.name = "rings"; n.label = "rings"; n.desc = "anillos concentricos"; n.kind = DagKind::Gen; n.num_inputs = 0; n.param_names = {"cx", "cy", "freq", "speed"}; n.param_defaults = {0.0f, 0.0f, 30.0f, 2.0f}; n.controls = { { DagControl::Kind::XY, "centro", {0, 1, -1}, -0.5f, 0.5f, 0.01f }, { DagControl::Kind::Slider, "frecuencia",{2, -1, -1}, 1.0f, 100.0f, 0.5f }, { DagControl::Kind::Slider, "velocidad", {3, -1, -1}, 0.0f, 10.0f, 0.05f }, }; 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 q = vec2((uv.x - 0.5) * aspect - p.x, uv.y - 0.5 - p.y);\n" " float d = length(q);\n" " float r = 0.5 + 0.5 * sin(d * p.z - u_time * p.w);\n" " return vec4(vec3(r), 1.0);"; }; v.push_back(std::move(n)); } // ── Gen: polar_rays ─────────────────────────────────────────── { DagNodeDef n; n.name = "polar_rays"; n.label = "polar rays"; n.desc = "rayos radiales"; n.kind = DagKind::Gen; n.num_inputs = 0; n.param_names = {"cx", "cy", "count", "speed"}; n.param_defaults = {0.0f, 0.0f, 12.0f, 0.5f}; n.controls = { { DagControl::Kind::XY, "centro", {0, 1, -1}, -0.5f, 0.5f, 0.01f }, { DagControl::Kind::Slider, "rayos", {2, -1, -1}, 1.0f, 64.0f, 1.0f }, { DagControl::Kind::Slider, "velocidad", {3, -1, -1}, -3.0f, 3.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 aspect = u_resolution.x / u_resolution.y;\n" " vec2 q = vec2((uv.x - 0.5) * aspect - p.x, uv.y - 0.5 - p.y);\n" " float a = atan(q.y, q.x);\n" " float r = 0.5 + 0.5 * sin(a * p.z + u_time * p.w);\n" " return vec4(vec3(r), 1.0);"; }; v.push_back(std::move(n)); } // ── Gen: noise_value ────────────────────────────────────────── { DagNodeDef n; n.name = "noise_value"; n.label = "noise value"; n.desc = "value noise 2D"; n.kind = DagKind::Gen; n.num_inputs = 0; n.param_names = {"scale", "speed", "hue"}; n.param_defaults = {6.0f, 0.3f, 0.5f}; n.controls = { { DagControl::Kind::Slider, "escala", {0, -1, -1}, 0.5f, 32.0f, 0.1f }, { DagControl::Kind::Slider, "velocidad", {1, -1, -1}, 0.0f, 3.0f, 0.01f }, { DagControl::Kind::Slider, "tono", {2, -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 q = uv * p.x + u_time * p.y;\n" " vec2 fl = floor(q);\n" " vec2 fr = fract(q);\n" " fr = fr * fr * (3.0 - 2.0 * fr);\n" " float a = fract(sin(dot(fl + vec2(0.0, 0.0), vec2(12.9898, 78.233))) * 43758.5453);\n" " float b = fract(sin(dot(fl + vec2(1.0, 0.0), vec2(12.9898, 78.233))) * 43758.5453);\n" " float c = fract(sin(dot(fl + vec2(0.0, 1.0), vec2(12.9898, 78.233))) * 43758.5453);\n" " float d = fract(sin(dot(fl + vec2(1.0, 1.0), vec2(12.9898, 78.233))) * 43758.5453);\n" " float n = mix(mix(a, b, fr.x), mix(c, d, fr.x), fr.y);\n" " vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.z + n + vec3(0.0, 0.33, 0.67)));\n" " return vec4(col, 1.0);"; }; v.push_back(std::move(n)); } // ── Gen: voronoi ────────────────────────────────────────────── { DagNodeDef n; n.name = "voronoi"; n.label = "voronoi"; n.desc = "celdas voronoi"; n.kind = DagKind::Gen; n.num_inputs = 0; n.param_names = {"scale", "speed", "hue"}; n.param_defaults = {8.0f, 0.5f, 0.4f}; n.controls = { { DagControl::Kind::Slider, "escala", {0, -1, -1}, 1.0f, 32.0f, 0.1f }, { DagControl::Kind::Slider, "velocidad", {1, -1, -1}, 0.0f, 3.0f, 0.01f }, { DagControl::Kind::Slider, "tono", {2, -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 q = uv * p.x;\n" " vec2 fl = floor(q);\n" " vec2 fr = fract(q);\n" " float md = 1.0;\n" " for (int yy = -1; yy <= 1; yy++) {\n" " for (int xx = -1; xx <= 1; xx++) {\n" " vec2 nb = vec2(float(xx), float(yy));\n" " vec2 r = fract(sin(dot(fl + nb, vec2(127.1, 311.7))) * vec2(43758.5453, 22578.1459)) * vec2(1.0);\n" " vec2 pt = nb + 0.5 + 0.5 * sin(u_time * p.y + 6.2831 * r) - fr;\n" " md = min(md, dot(pt, pt));\n" " }\n" " }\n" " float d = sqrt(md);\n" " vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.z + d + vec3(0.0, 0.33, 0.67)));\n" " return vec4(col, 1.0);"; }; v.push_back(std::move(n)); } // ── Gen: truchet ────────────────────────────────────────────── { DagNodeDef n; n.name = "truchet"; n.label = "truchet"; n.desc = "patron truchet curvo"; n.kind = DagKind::Gen; n.num_inputs = 0; n.param_names = {"scale", "thickness", "hue"}; n.param_defaults = {10.0f, 0.15f, 0.3f}; n.controls = { { DagControl::Kind::Slider, "escala", {0, -1, -1}, 1.0f, 40.0f, 0.5f }, { DagControl::Kind::Slider, "grosor", {1, -1, -1}, 0.02f, 0.45f, 0.01f }, { DagControl::Kind::Slider, "tono", {2, -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 q = uv * p.x;\n" " vec2 fl = floor(q);\n" " vec2 fr = fract(q);\n" " float h = fract(sin(dot(fl, vec2(12.9898, 78.233))) * 43758.5453);\n" " if (h > 0.5) fr.x = 1.0 - fr.x;\n" " float d1 = abs(length(fr) - 0.5);\n" " float d2 = abs(length(fr - 1.0) - 0.5);\n" " float d = min(d1, d2);\n" " float fill = 1.0 - smoothstep(p.y - 0.02, p.y, d);\n" " vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.z + vec3(0.0, 0.33, 0.67)));\n" " return vec4(col * fill, 1.0);"; }; 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 = {}; n.controls = {}; n.body_glsl = [](int) -> std::string { return ""; }; v.push_back(std::move(n)); } for (auto& n : v) n.is_builtin = true; return v; }(); return catalog; } const std::vector& dag_catalog() { return mutable_catalog(); } const DagNodeDef* dag_find(const std::string& name) { for (const auto& n : dag_catalog()) { if (n.name == name) return &n; } return nullptr; } bool dag_register_node(DagNodeDef def) { auto& cat = mutable_catalog(); for (auto& n : cat) { if (n.name == def.name) { if (n.is_builtin) return false; n = std::move(def); n.is_builtin = false; return true; } } def.is_builtin = false; cat.push_back(std::move(def)); return true; } bool dag_unregister_node(const std::string& name) { auto& cat = mutable_catalog(); auto it = std::find_if(cat.begin(), cat.end(), [&](const DagNodeDef& n){ return n.name == name && !n.is_builtin; }); if (it == cat.end()) return false; cat.erase(it); return true; } } // namespace fn::gfx #ifdef DAG_CATALOG_TEST #include #include #include 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 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..param_count-1 of their node for (const auto& n : cat) { int pc = static_cast(n.param_defaults.size()); for (const auto& c : n.controls) { for (int idx : c.param_idx) { assert(idx >= -1 && idx < pc); } } assert(n.param_names.size() == n.param_defaults.size()); } std::printf("dag_catalog: 8/8 asserts passed (%zu nodes)\n", cat.size()); return 0; } #endif