| … | … | @@ -414,6 +414,53 @@ String ProcNodeGraph::compile_to_glsl_function(const String &p_function_name, |
| 414 | 414 | } |
| 415 | 415 | } |
| 416 | 416 | |
| 417 | // ONE GRAPH, TWO PRODUCTS. | |
| 418 | // | |
| 419 | // A tree bridged from Blender is a MESH tree that also declares a density: | |
| 420 | // the mountain subdivides a cage, displaces it and solidifies it through an | |
| 421 | // SDF grid, and separately says "this float is my density" on its group | |
| 422 | // interface. Emitting every node then refuses on SubdivideMesh -- a node | |
| 423 | // that cannot affect the density and has no business being in the density | |
| 424 | // shader at all. | |
| 425 | // | |
| 426 | // So when the graph DECLARES a density, only the cone that feeds it is | |
| 427 | // emitted. This is not the silent drop the refusal below exists to catch: | |
| 428 | // a node INSIDE the cone that cannot emit still refuses loudly, and the | |
| 429 | // nodes excluded are excluded because the density provably does not read | |
| 430 | // them. With no declared density the whole graph is emitted exactly as | |
| 431 | // before, so every existing heightfield graph is untouched. | |
| 432 | bool cone_only = false; | |
| 433 | Vector<bool> in_cone; | |
| 434 | { | |
| 435 | int dn = -1, ds = -1; | |
| 436 | if (find_density_output(dn, ds)) { | |
| 437 | const int src = get_input_source_node(dn, ds); | |
| 438 | if (src >= 0) { | |
| 439 | cone_only = true; | |
| 440 | in_cone.resize(nodes.size()); | |
| 441 | for (int i = 0; i < in_cone.size(); i++) { | |
| 442 | in_cone.write[i] = false; | |
| 443 | } | |
| 444 | // Backward reachability over connections, iterated to a fixed | |
| 445 | // point. The graph is small and this runs once per compile. | |
| 446 | in_cone.write[src] = true; | |
| 447 | bool grew = true; | |
| 448 | while (grew) { | |
| 449 | grew = false; | |
| 450 | for (int c = 0; c < connections.size(); c++) { | |
| 451 | const ProcConnection &conn = connections[c]; | |
| 452 | if (conn.to_node < 0 || conn.to_node >= in_cone.size()) continue; | |
| 453 | if (conn.from_node < 0 || conn.from_node >= in_cone.size()) continue; | |
| 454 | if (in_cone[conn.to_node] && !in_cone[conn.from_node]) { | |
| 455 | in_cone.write[conn.from_node] = true; | |
| 456 | grew = true; | |
| 457 | } | |
| 458 | } | |
| 459 | } | |
| 460 | } | |
| 461 | } | |
| 462 | } | |
| 463 | ||
| 417 | 464 | Vector<Ref<ProcNode>> gpu_nodes; |
| 418 | 465 | Vector<int> gpu_indices; |
| 419 | 466 | |
| … | … | @@ -427,6 +474,10 @@ String ProcNodeGraph::compile_to_glsl_function(const String &p_function_name, |
| 427 | 474 | if (p_skip_types.has(String(nodes[i]->get_node_type()))) { |
| 428 | 475 | continue; |
| 429 | 476 | } |
| 477 | // Outside the declared density's cone: it cannot change the answer. | |
| 478 | if (cone_only && i < in_cone.size() && !in_cone[i]) { | |
| 479 | continue; | |
| 480 | } | |
| 430 | 481 | // ANYTHING ELSE THAT CANNOT BE EMITTED IS A REFUSAL, NOT A SKIP. This |
| 431 | 482 | // used to drop the node and emit the rest: adding a Math node to |
| 432 | 483 | // map_dc_erosion's graph moved the emitted text by one comment line and |
| … | … | @@ -9,6 +9,7 @@ |
| 9 | 9 | // is worse than a missing one. |
| 10 | 10 | |
| 11 | 11 | #include "rotation_nodes.h" |
| 12 | #include "../gpu/glsl_emit_context.h" | |
| 12 | 13 | |
| 13 | 14 | #include "../graph/graph_executor.h" |
| 14 | 15 | |
| … | … | @@ -512,4 +513,55 @@ void TransposeMatrixNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 512 | 513 | }); |
| 513 | 514 | } |
| 514 | 515 | |
| 516 | // --- GLSL for the two rotation nodes the density cone needs ------------------ | |
| 517 | // | |
| 518 | // PN_EULER_ORDER is ZYX, so the basis is Rz * Ry * Rx and the quaternion is | |
| 519 | // qz * qy * qx -- matrix product order, which is what "ZYX" names here. The | |
| 520 | // composition is written out rather than fused so the order is readable at the | |
| 521 | // point it matters. | |
| 522 | static const char *PN_QUAT_GLSL = R"( | |
| 523 | #ifndef PN_QUAT_ROT_HELPERS | |
| 524 | #define PN_QUAT_ROT_HELPERS | |
| 525 | vec4 pn_quat_axis(vec3 axis, float ang) { | |
| 526 | float h = ang * 0.5; | |
| 527 | return vec4(axis * sin(h), cos(h)); | |
| 528 | } | |
| 529 | vec4 pn_quat_mul(vec4 a, vec4 b) { | |
| 530 | return vec4(a.w * b.xyz + b.w * a.xyz + cross(a.xyz, b.xyz), | |
| 531 | a.w * b.w - dot(a.xyz, b.xyz)); | |
| 532 | } | |
| 533 | vec4 pn_quat_from_euler_zyx(vec3 e) { | |
| 534 | vec4 qz = pn_quat_axis(vec3(0.0, 0.0, 1.0), e.z); | |
| 535 | vec4 qy = pn_quat_axis(vec3(0.0, 1.0, 0.0), e.y); | |
| 536 | vec4 qx = pn_quat_axis(vec3(1.0, 0.0, 0.0), e.x); | |
| 537 | return pn_quat_mul(pn_quat_mul(qz, qy), qx); | |
| 538 | } | |
| 539 | vec3 pn_quat_xform(vec4 q, vec3 v) { | |
| 540 | q = normalize(q); | |
| 541 | vec3 t = 2.0 * cross(q.xyz, v); | |
| 542 | return v + q.w * t + cross(q.xyz, t); | |
| 543 | } | |
| 544 | #endif | |
| 545 | )"; | |
| 546 | ||
| 547 | String EulerToRotationNode::compile_glsl_prelude(int) const { | |
| 548 | return String(PN_QUAT_GLSL); | |
| 549 | } | |
| 550 | ||
| 551 | String EulerToRotationNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const { | |
| 552 | return p_ctx.declare(p_node_idx, 0) + String(" = pn_quat_from_euler_zyx(") + | |
| 553 | p_ctx.in(p_node_idx, 0) + String(");"); | |
| 554 | } | |
| 555 | ||
| 556 | String RotateVectorNode::compile_glsl_prelude(int) const { | |
| 557 | return String(PN_QUAT_GLSL); | |
| 558 | } | |
| 559 | ||
| 560 | String RotateVectorNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const { | |
| 561 | // execute() normalizes the quaternion before xform; pn_quat_xform does the | |
| 562 | // same, so an unnormalized Rotation arriving from either side behaves alike. | |
| 563 | return p_ctx.declare(p_node_idx, 0) + String(" = pn_quat_xform(") + | |
| 564 | p_ctx.in(p_node_idx, 1) + String(", ") + p_ctx.in(p_node_idx, 0) + String(");"); | |
| 565 | } | |
| 566 | ||
| 515 | 567 | } // namespace godot |
| … | … | @@ -38,6 +38,16 @@ public: |
| 38 | 38 | void execute(ProcEvalContext &p_ctx, int p_node_idx) override; |
| 39 | 39 | |
| 40 | 40 | EulerToRotationNode(); |
| 41 | // GLSL, because MTN_CellSpace's lattice rotation sits inside the bridged | |
| 42 | // mountain's DENSITY cone -- the emitter refused the whole graph on it. | |
| 43 | // ⚠ The euler order is PN_EULER_ORDER (ZYX): Blender's XYZ euler is Godot's | |
| 44 | // ZYX, one naming the order rotations are APPLIED and the other the order | |
| 45 | // matrices are MULTIPLIED. This is held to the CPU by | |
| 46 | // tests/test_emitted_density_parity.cpp, which compares NUMBERS -- a check | |
| 47 | // that asserted the spelling of an order certified the wrong one twice. | |
| 48 | bool supports_gpu() const override { return true; } | |
| 49 | String compile_glsl_prelude(int p_node_idx) const override; | |
| 50 | String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override; | |
| 41 | 51 | }; |
| 42 | 52 | |
| 43 | 53 | class QuaternionToRotationNode : public ProcNode { |
| … | … | @@ -150,6 +160,16 @@ public: |
| 150 | 160 | void execute(ProcEvalContext &p_ctx, int p_node_idx) override; |
| 151 | 161 | |
| 152 | 162 | RotateVectorNode(); |
| 163 | // GLSL, because MTN_CellSpace's lattice rotation sits inside the bridged | |
| 164 | // mountain's DENSITY cone -- the emitter refused the whole graph on it. | |
| 165 | // ⚠ The euler order is PN_EULER_ORDER (ZYX): Blender's XYZ euler is Godot's | |
| 166 | // ZYX, one naming the order rotations are APPLIED and the other the order | |
| 167 | // matrices are MULTIPLIED. This is held to the CPU by | |
| 168 | // tests/test_emitted_density_parity.cpp, which compares NUMBERS -- a check | |
| 169 | // that asserted the spelling of an order certified the wrong one twice. | |
| 170 | bool supports_gpu() const override { return true; } | |
| 171 | String compile_glsl_prelude(int p_node_idx) const override; | |
| 172 | String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override; | |
| 153 | 173 | }; |
| 154 | 174 | |
| 155 | 175 | class InputRotationNode : public ProcNode { |