| … | … | @@ -126,6 +126,8 @@ void ProcNodeGraph::_bind_methods() { |
| 126 | 126 | ClassDB::bind_method(D_METHOD("connect_sockets", "from_node", "from_socket", "to_node", "to_socket"), &ProcNodeGraph::connect_sockets); |
| 127 | 127 | ClassDB::bind_method(D_METHOD("disconnect_sockets", "from_node", "from_socket", "to_node", "to_socket"), &ProcNodeGraph::disconnect_sockets); |
| 128 | 128 | ClassDB::bind_method(D_METHOD("evaluate_socket", "node_idx", "socket_idx"), &ProcNodeGraph::evaluate_socket); |
| 129 | ClassDB::bind_method(D_METHOD("evaluate_density_batch", "positions"), &ProcNodeGraph::evaluate_density_batch); |
| 130 | ClassDB::bind_method(D_METHOD("evaluate_density_at", "pos"), &ProcNodeGraph::evaluate_density_at); |
| 129 | 131 | ClassDB::bind_static_method("ProcNodeGraph", |
| 130 | 132 | D_METHOD("debug_phacelle_noise", "p", "dir", "freq", "offset", "normalization"), |
| 131 | 133 | &ProcNodeGraph::debug_phacelle_noise); |
| … | … | @@ -156,11 +158,13 @@ void ProcNodeGraph::_bind_methods() { |
| 156 | 158 | } |
| 157 | 159 | |
| 158 | 160 | int ProcNodeGraph::add_node(const Ref<ProcNode> &p_node) { |
| 161 | _density_plan_dirty = true; |
| 159 | 162 | nodes.push_back(p_node); |
| 160 | 163 | return nodes.size() - 1; |
| 161 | 164 | } |
| 162 | 165 | |
| 163 | 166 | void ProcNodeGraph::remove_node(int p_idx) { |
| 167 | _density_plan_dirty = true; |
| 164 | 168 | if (p_idx < 0 || p_idx >= nodes.size()) { |
| 165 | 169 | return; |
| 166 | 170 | } |
| … | … | @@ -186,6 +190,7 @@ Ref<ProcNode> ProcNodeGraph::get_node(int p_idx) const { |
| 186 | 190 | } |
| 187 | 191 | |
| 188 | 192 | void ProcNodeGraph::connect_sockets(int p_from_node, int p_from_socket, int p_to_node, int p_to_socket) { |
| 193 | _density_plan_dirty = true; |
| 189 | 194 | // A CONNECTION TO A SOCKET THAT DOES NOT EXIST IS NOT MERELY DROPPED -- it |
| 190 | 195 | // used to be ACCEPTED, and `_read_input_raw` then computed |
| 191 | 196 | // `output_values[src_node * max_output_sockets + src_socket]` with no bound |
| … | … | @@ -261,6 +266,7 @@ void ProcNodeGraph::connect_sockets(int p_from_node, int p_from_socket, int p_to |
| 261 | 266 | } |
| 262 | 267 | |
| 263 | 268 | void ProcNodeGraph::disconnect_sockets(int p_from_node, int p_from_socket, int p_to_node, int p_to_socket) { |
| 269 | _density_plan_dirty = true; |
| 264 | 270 | for (int i = connections.size() - 1; i >= 0; i--) { |
| 265 | 271 | const ProcConnection &c = connections[i]; |
| 266 | 272 | if (c.from_node == p_from_node && c.from_socket == p_from_socket && |
| … | … | @@ -436,6 +442,165 @@ String ProcNodeGraph::compile_to_glsl_function(const String &p_function_name, |
| 436 | 442 | return GpuCompiler::compile_to_function(gpu_nodes, gpu_indices, p_function_name, p_clamp_octaves, this); |
| 437 | 443 | } |
| 438 | 444 | |
| 445 | // --- The CPU side of proc_density ------------------------------------------ |
| 446 | |
| 447 | // Which value the graph's density IS, by the same rules gpu_compiler.cpp uses |
| 448 | // for the emitted function. Returns the node and socket of an explicit Group |
| 449 | // Output float, or false for "the displacement of pos.y". |
| 450 | static bool _density_output_socket(const ProcNodeGraph &p_graph, int &r_node, int &r_socket) { |
| 451 | for (int i = 0; i < p_graph.get_node_count(); i++) { |
| 452 | Ref<ProcNode> n = p_graph.get_node(i); |
| 453 | if (n.is_null() || n->get_node_type() != StringName("GroupOutput")) { |
| 454 | continue; |
| 455 | } |
| 456 | for (int s = 0; s < n->get_input_count(); s++) { |
| 457 | if (n->get_input_def(s).type != SocketType::Float) { |
| 458 | continue; |
| 459 | } |
| 460 | if (p_graph.get_input_source_node(i, s) < 0) { |
| 461 | continue; |
| 462 | } |
| 463 | r_node = i; |
| 464 | r_socket = s; |
| 465 | return true; |
| 466 | } |
| 467 | } |
| 468 | return false; |
| 469 | } |
| 470 | |
| 471 | ProcNodeGraph::~ProcNodeGraph() { |
| 472 | delete _density_plan; |
| 473 | _density_plan = nullptr; |
| 474 | } |
| 475 | |
| 476 | PackedFloat32Array ProcNodeGraph::evaluate_density_batch(const PackedVector3Array &p_positions) { |
| 477 | PackedFloat32Array out; |
| 478 | const int n = p_positions.size(); |
| 479 | out.resize(n); |
| 480 | if (n == 0) { |
| 481 | return out; |
| 482 | } |
| 483 | float *w = out.ptrw(); |
| 484 | for (int i = 0; i < n; i++) { |
| 485 | w[i] = 0.0f; |
| 486 | } |
| 487 | |
| 488 | // One point of geometry per position. The graph displaces them exactly as |
| 489 | // it displaces a mesh's vertices, which is what the shader's `pos` is. |
| 490 | Ref<ProcGeometry> in_geo; |
| 491 | in_geo.instantiate(); |
| 492 | Ref<ProcMesh> in_mesh = in_geo->ensure_mesh(); |
| 493 | in_mesh->positions = p_positions; |
| 494 | in_mesh->normals.resize(n); |
| 495 | for (int i = 0; i < n; i++) { |
| 496 | in_mesh->normals.ptrw()[i] = Vector3(0, 1, 0); |
| 497 | } |
| 498 | |
| 499 | Dictionary overrides; |
| 500 | overrides["geometry"] = in_geo; |
| 501 | |
| 502 | if (_density_plan == nullptr) { |
| 503 | _density_plan = new ExecutionPlan(); |
| 504 | _density_plan_dirty = true; |
| 505 | } |
| 506 | if (_density_plan_dirty) { |
| 507 | *_density_plan = GraphExecutor::compile(*this); |
| 508 | _density_plan_dirty = false; |
| 509 | } |
| 510 | // use_gpu FALSE: this IS the CPU answer, and the point of it is to agree |
| 511 | // with the shader rather than to be the shader. |
| 512 | Ref<ProcGeometry> result = GraphExecutor::execute(*this, *_density_plan, overrides, false, |
| 513 | nullptr, 0.016f, nullptr); |
| 514 | if (result.is_null()) { |
| 515 | return out; |
| 516 | } |
| 517 | |
| 518 | int out_node = -1, out_socket = -1; |
| 519 | if (_density_output_socket(*this, out_node, out_socket)) { |
| 520 | const Variant v = GraphExecutor::evaluate_socket_value(*this, out_node, out_socket); |
| 521 | // A scalar output is the same value at every point; a field is per point. |
| 522 | if (v.get_type() == Variant::PACKED_FLOAT32_ARRAY) { |
| 523 | PackedFloat32Array f = v; |
| 524 | for (int i = 0; i < n && i < f.size(); i++) { |
| 525 | w[i] = f[i]; |
| 526 | } |
| 527 | } else { |
| 528 | const float c = (float)(double)v; |
| 529 | for (int i = 0; i < n; i++) { |
| 530 | w[i] = c; |
| 531 | } |
| 532 | } |
| 533 | return out; |
| 534 | } |
| 535 | |
| 536 | Ref<ProcMesh> out_mesh = result->get_mesh(); |
| 537 | if (out_mesh.is_null() || out_mesh->positions.size() != n) { |
| 538 | // A graph that changed the point COUNT has no per-input answer, and |
| 539 | // pairing them by index would invent one. |
| 540 | UtilityFunctions::push_warning( |
| 541 | "proc-nodes: evaluate_density_batch got ", |
| 542 | out_mesh.is_null() ? 0 : out_mesh->positions.size(), |
| 543 | " points back for ", n, " in. A graph that changes the point count " |
| 544 | "cannot answer a density per input point."); |
| 545 | return out; |
| 546 | } |
| 547 | const Vector3 *src = p_positions.ptr(); |
| 548 | const Vector3 *dst = out_mesh->positions.ptr(); |
| 549 | for (int i = 0; i < n; i++) { |
| 550 | w[i] = dst[i].y - src[i].y; |
| 551 | } |
| 552 | return out; |
| 553 | } |
| 554 | |
| 555 | float ProcNodeGraph::evaluate_density_at(const Vector3 &p_pos) { |
| 556 | // The scratch geometry is built ONCE and its single position rewritten per |
| 557 | // query. Going through evaluate_density_batch instead allocates a fresh |
| 558 | // ProcGeometry and ProcMesh every call, and a collision query cannot pay |
| 559 | // for that. |
| 560 | if (_density_scratch.is_null()) { |
| 561 | _density_scratch.instantiate(); |
| 562 | Ref<ProcMesh> m = _density_scratch->ensure_mesh(); |
| 563 | m->positions.resize(1); |
| 564 | m->normals.resize(1); |
| 565 | m->normals.ptrw()[0] = Vector3(0, 1, 0); |
| 566 | } |
| 567 | Ref<ProcMesh> mesh = _density_scratch->get_mesh(); |
| 568 | mesh->positions.ptrw()[0] = p_pos; |
| 569 | |
| 570 | if (_density_plan == nullptr) { |
| 571 | _density_plan = new ExecutionPlan(); |
| 572 | _density_plan_dirty = true; |
| 573 | } |
| 574 | if (_density_plan_dirty) { |
| 575 | *_density_plan = GraphExecutor::compile(*this); |
| 576 | _density_plan_dirty = false; |
| 577 | } |
| 578 | |
| 579 | Dictionary overrides; |
| 580 | overrides["geometry"] = _density_scratch; |
| 581 | Ref<ProcGeometry> result = GraphExecutor::execute(*this, *_density_plan, overrides, false, |
| 582 | nullptr, 0.016f, nullptr); |
| 583 | if (result.is_null()) { |
| 584 | return 0.0f; |
| 585 | } |
| 586 | |
| 587 | int out_node = -1, out_socket = -1; |
| 588 | if (_density_output_socket(*this, out_node, out_socket)) { |
| 589 | const Variant v = GraphExecutor::evaluate_socket_value(*this, out_node, out_socket); |
| 590 | if (v.get_type() == Variant::PACKED_FLOAT32_ARRAY) { |
| 591 | PackedFloat32Array f = v; |
| 592 | return f.size() > 0 ? f[0] : 0.0f; |
| 593 | } |
| 594 | return (float)(double)v; |
| 595 | } |
| 596 | |
| 597 | Ref<ProcMesh> out_mesh = result->get_mesh(); |
| 598 | if (out_mesh.is_null() || out_mesh->positions.size() != 1) { |
| 599 | return 0.0f; |
| 600 | } |
| 601 | return out_mesh->positions[0].y - p_pos.y; |
| 602 | } |
| 603 | |
| 439 | 604 | PackedInt32Array ProcNodeGraph::get_connections_flat() const { |
| 440 | 605 | PackedInt32Array out; |
| 441 | 606 | out.resize(connections.size() * 4); |