| … | … | @@ -0,0 +1,133 @@ |
| 1 | ## The mesh channel, inside proc-nodes: does a MESH survive the Dictionary? | |
| 2 | ## | |
| 3 | ## The bridge hands ProcMesh points, a corner array, faces as corner ranges, | |
| 4 | ## edges and attributes on their domains. This asserts each of those arrives as | |
| 5 | ## itself -- especially the two that cannot be checked by counting: | |
| 6 | ## | |
| 7 | ## FACES a quad must stay one face, not become two triangles. Everything | |
| 8 | ## that crossed as an ArrayMesh became triangles, and a per-face | |
| 9 | ## value then had nowhere to agree. | |
| 10 | ## EDGES ProcMesh DERIVES its edges in its own sorted order, so an edge | |
| 11 | ## value copied by index lands on the wrong edge with the counts | |
| 12 | ## still matching. The sender's edge array is shipped so it can be | |
| 13 | ## remapped; this gate gives every edge a value that encodes its own | |
| 14 | ## vertex pair, so a mis-landed value is arithmetic, not luck. | |
| 15 | extends SceneTree | |
| 16 | ||
| 17 | var passed := 0 | |
| 18 | var failed := 0 | |
| 19 | ||
| 20 | func ck(name: String, ok: bool, detail: String = "") -> void: | |
| 21 | print(" %-46s %s %s" % [name, "PASS" if ok else "FAIL", detail]) | |
| 22 | if ok: passed += 1 | |
| 23 | else: failed += 1 | |
| 24 | ||
| 25 | func _init() -> void: | |
| 26 | # A 2x2 grid of QUADS: 9 points, 4 faces, 16 corners, 12 edges. | |
| 27 | var pts := PackedVector3Array() | |
| 28 | for y in range(3): | |
| 29 | for x in range(3): | |
| 30 | pts.append(Vector3(float(x), 0.0, float(y))) | |
| 31 | var corners := PackedInt32Array() | |
| 32 | var faces := PackedInt32Array() | |
| 33 | for fy in range(2): | |
| 34 | for fx in range(2): | |
| 35 | faces.append(corners.size()) | |
| 36 | var v0 := fy * 3 + fx | |
| 37 | corners.append_array(PackedInt32Array([v0, v0 + 1, v0 + 4, v0 + 3])) | |
| 38 | faces.append(corners.size()) | |
| 39 | ||
| 40 | # Edges in a deliberately UNSORTED order, some stored high-to-low, so the | |
| 41 | # sender's order cannot accidentally equal ProcMesh's. | |
| 42 | var edge_pairs := [[4,3],[0,1],[1,2],[3,0],[4,1],[5,2],[3,4],[8,5],[6,7],[7,8],[6,3],[7,4],[8,7],[5,4],[2,1]] | |
| 43 | var seen := {} | |
| 44 | var edges := PackedInt32Array() | |
| 45 | var edge_vals := PackedFloat32Array() | |
| 46 | for e in edge_pairs: | |
| 47 | var lo: int = min(e[0], e[1]) | |
| 48 | var hi: int = max(e[0], e[1]) | |
| 49 | var key := lo * 100 + hi | |
| 50 | if seen.has(key): continue | |
| 51 | seen[key] = true | |
| 52 | edges.append_array(PackedInt32Array([e[0], e[1]])) | |
| 53 | # The value IS the pair: any mis-landing is visible as a wrong pair. | |
| 54 | edge_vals.append(float(key)) | |
| 55 | ||
| 56 | var d := {} | |
| 57 | d["points"] = pts | |
| 58 | d["corners"] = corners | |
| 59 | d["faces"] = faces | |
| 60 | d["edges"] = edges | |
| 61 | d["attributes"] = [ | |
| 62 | {"name": "pt_h", "domain": 0, "type": 0, | |
| 63 | "data": PackedFloat32Array([0,1,2,3,4,5,6,7,8]).to_byte_array()}, | |
| 64 | {"name": "edge_pair", "domain": 1, "type": 0, "data": edge_vals.to_byte_array()}, | |
| 65 | {"name": "face_id", "domain": 2, "type": 4, | |
| 66 | "data": PackedInt32Array([10,11,12,13]).to_byte_array()}, | |
| 67 | {"name": "uvish", "domain": 3, "type": 1, | |
| 68 | "data": PackedVector2Array([Vector2(0,0),Vector2(1,0),Vector2(1,1),Vector2(0,1), | |
| 69 | Vector2(0,0),Vector2(1,0),Vector2(1,1),Vector2(0,1), | |
| 70 | Vector2(0,0),Vector2(1,0),Vector2(1,1),Vector2(0,1), | |
| 71 | Vector2(0,0),Vector2(1,0),Vector2(1,1),Vector2(0,1)]).to_byte_array()}, | |
| 72 | ] | |
| 73 | ||
| 74 | var m: ProcMesh = ProcMesh.from_mesh_data(d) | |
| 75 | ck("points survive", m.get_vertex_count() == 9, "%d verts" % m.get_vertex_count()) | |
| 76 | ck("corners survive", m.get_corner_count() == 16, "%d corners" % m.get_corner_count()) | |
| 77 | # ⛔ THE ONE THE ARRAYMESH PATH CANNOT PASS: 4 quads, not 8 triangles. | |
| 78 | ck("a quad stays one face", m.get_face_count() == 4, "%d faces" % m.get_face_count()) | |
| 79 | ck("edges are derived", m.get_domain_size(1) == 12, "%d edges" % m.get_domain_size(1)) | |
| 80 | ||
| 81 | ck("point attribute", m.get_attribute_values(0, "pt_h").size() == 9, | |
| 82 | "%d values" % m.get_attribute_values(0, "pt_h").size()) | |
| 83 | ck("face attribute", str(m.get_attribute_values(2, "face_id")) == "[10, 11, 12, 13]", | |
| 84 | str(m.get_attribute_values(2, "face_id"))) | |
| 85 | ck("corner attribute", m.get_attribute_values(3, "uvish").size() == 16, | |
| 86 | "%d values" % m.get_attribute_values(3, "uvish").size()) | |
| 87 | ||
| 88 | # Every edge value must equal the key of the pair it now sits on, under | |
| 89 | # ProcMesh's OWN edge order. | |
| 90 | var vals: Array = m.get_attribute_values(1, "edge_pair") | |
| 91 | var ours: PackedInt32Array = m.get_indices() # placeholder, replaced below | |
| 92 | var bad := 0 | |
| 93 | var our_edges := _our_edges(m) | |
| 94 | for i in range(our_edges.size()): | |
| 95 | var pair: Array = our_edges[i] | |
| 96 | var want := float(pair[0] * 100 + pair[1]) | |
| 97 | if i >= vals.size() or abs(float(vals[i]) - want) > 1e-6: | |
| 98 | bad += 1 | |
| 99 | ck("every edge value landed on ITS OWN edge", bad == 0 and vals.size() == 12, | |
| 100 | "%d of %d wrong" % [bad, vals.size()]) | |
| 101 | ||
| 102 | # And out again. | |
| 103 | var back: Dictionary = m.to_mesh_data() | |
| 104 | var f2: PackedInt32Array = back["faces"] | |
| 105 | ck("round trip keeps the faces", f2.size() == 5 and f2[4] == 16, | |
| 106 | "%d offsets, last %d" % [f2.size(), f2[f2.size() - 1] if f2.size() > 0 else -1]) | |
| 107 | var names := [] | |
| 108 | for a in back["attributes"]: | |
| 109 | names.append(a["name"]) | |
| 110 | names.sort() | |
| 111 | ck("round trip keeps every attribute", names == ["edge_pair", "face_id", "pt_h", "uvish"], | |
| 112 | str(names)) | |
| 113 | ||
| 114 | print("%d/%d passed" % [passed, passed + failed]) | |
| 115 | quit(0 if failed == 0 else 1) | |
| 116 | ||
| 117 | ||
| 118 | ## ProcMesh's derived edge order, rebuilt here the way the class documents it: | |
| 119 | ## each pair low-to-high, the set sorted. | |
| 120 | func _our_edges(m: ProcMesh) -> Array: | |
| 121 | var set := {} | |
| 122 | var corners: PackedInt32Array = m.get_indices() | |
| 123 | var fo: PackedInt32Array = m.get_face_offsets() | |
| 124 | for f in range(m.get_face_count()): | |
| 125 | var b: int = fo[f] if fo.size() > 1 else f * 3 | |
| 126 | var e: int = fo[f + 1] if fo.size() > 1 else f * 3 + 3 | |
| 127 | for c in range(b, e): | |
| 128 | var a: int = corners[c] | |
| 129 | var bb: int = corners[b + ((c - b + 1) % (e - b))] | |
| 130 | set[[min(a, bb), max(a, bb)]] = true | |
| 131 | var out := set.keys() | |
| 132 | out.sort() | |
| 133 | return out |
| … | … | @@ -48,6 +48,8 @@ void ProcMesh::_bind_methods() { |
| 48 | 48 | ClassDB::bind_method(D_METHOD("get_domain_size", "domain"), &ProcMesh::get_domain_size); |
| 49 | 49 | ClassDB::bind_method(D_METHOD("get_attribute_values", "domain", "name"), &ProcMesh::get_attribute_values); |
| 50 | 50 | ClassDB::bind_method(D_METHOD("get_attribute_names", "domain"), &ProcMesh::get_attribute_names); |
| 51 | ClassDB::bind_static_method("ProcMesh", D_METHOD("from_mesh_data", "data"), &ProcMesh::from_mesh_data); | |
| 52 | ClassDB::bind_method(D_METHOD("to_mesh_data"), &ProcMesh::to_mesh_data); | |
| 51 | 53 | } |
| 52 | 54 | |
| 53 | 55 | static const AttributeStorage *_storage_for(const ProcMesh *p_mesh, int p_domain) { |
| … | … | @@ -435,4 +437,151 @@ Ref<ProcMesh> ProcMesh::from_array_mesh(const Ref<Mesh> &p_mesh) { |
| 435 | 437 | return out; |
| 436 | 438 | } |
| 437 | 439 | |
| 440 | ||
| 441 | // ── THE MESH CHANNEL ──────────────────────────────────────────────────────── | |
| 442 | ||
| 443 | static AttributeStorage *_storage_for_write(ProcMesh *p_mesh, AttrDomain p_domain) { | |
| 444 | switch (p_domain) { | |
| 445 | case AttrDomain::Point: return &p_mesh->point_attrs; | |
| 446 | case AttrDomain::Edge: return &p_mesh->edge_attrs; | |
| 447 | case AttrDomain::Face: return &p_mesh->face_attrs; | |
| 448 | case AttrDomain::Corner: return &p_mesh->corner_attrs; | |
| 449 | default: return nullptr; | |
| 450 | } | |
| 451 | } | |
| 452 | ||
| 453 | static inline uint64_t _edge_key(int32_t a, int32_t b) { | |
| 454 | const uint32_t lo = (uint32_t)(a < b ? a : b); | |
| 455 | const uint32_t hi = (uint32_t)(a < b ? b : a); | |
| 456 | return ((uint64_t)lo << 32) | (uint64_t)hi; | |
| 457 | } | |
| 458 | ||
| 459 | Ref<ProcMesh> ProcMesh::from_mesh_data(const Dictionary &p_data) { | |
| 460 | Ref<ProcMesh> out; | |
| 461 | out.instantiate(); | |
| 462 | out->positions = p_data.get("points", PackedVector3Array()); | |
| 463 | out->indices = p_data.get("corners", PackedInt32Array()); | |
| 464 | PackedInt32Array faces = p_data.get("faces", PackedInt32Array()); | |
| 465 | // A single trailing offset means "no faces declared" -- every face is a | |
| 466 | // triangle, which is what an empty face_offsets already means here. | |
| 467 | if (faces.size() > 1) { | |
| 468 | out->face_offsets = faces; | |
| 469 | } | |
| 470 | ||
| 471 | // ⛔ AN EDGE ATTRIBUTE CANNOT BE COPIED BY INDEX. ProcMesh does not store | |
| 472 | // edges; it derives them, each pair normalised low-to-high and the set | |
| 473 | // sorted, which is OUR order and not the sender's. Blender builds an | |
| 474 | // icosphere's edges in its own sequence: the counts agree and edge i is a | |
| 475 | // different edge. So the sender ships its edge array and every edge-domain | |
| 476 | // value is remapped through the vertex pair it belongs to. Without this the | |
| 477 | // values land on the wrong edges silently, because the counts match. | |
| 478 | PackedInt32Array sender_edges = p_data.get("edges", PackedInt32Array()); | |
| 479 | HashMap<uint64_t, int32_t> our_edge_index; | |
| 480 | bool need_edge_map = false; | |
| 481 | Array attrs = p_data.get("attributes", Array()); | |
| 482 | for (int i = 0; i < attrs.size(); i++) { | |
| 483 | Dictionary a = attrs[i]; | |
| 484 | if ((int)a.get("domain", 0) == (int)AttrDomain::Edge) { | |
| 485 | need_edge_map = true; | |
| 486 | break; | |
| 487 | } | |
| 488 | } | |
| 489 | if (need_edge_map && sender_edges.size() >= 2) { | |
| 490 | PackedInt32Array ours = out->compute_edges(); | |
| 491 | const int32_t *o = ours.ptr(); | |
| 492 | for (int e = 0; e < ours.size() / 2; e++) { | |
| 493 | our_edge_index[_edge_key(o[e * 2], o[e * 2 + 1])] = e; | |
| 494 | } | |
| 495 | } | |
| 496 | ||
| 497 | for (int i = 0; i < attrs.size(); i++) { | |
| 498 | Dictionary a = attrs[i]; | |
| 499 | const StringName name = StringName(String(a.get("name", ""))); | |
| 500 | const AttrDomain domain = static_cast<AttrDomain>((int)a.get("domain", 0)); | |
| 501 | const AttrType type = static_cast<AttrType>((int)a.get("type", 0)); | |
| 502 | PackedByteArray data = a.get("data", PackedByteArray()); | |
| 503 | AttributeStorage *st = _storage_for_write(out.ptr(), domain); | |
| 504 | if (st == nullptr || String(name).is_empty()) { | |
| 505 | continue; | |
| 506 | } | |
| 507 | const int esz = attr_type_size(type); | |
| 508 | const int incoming = esz > 0 ? data.size() / esz : 0; | |
| 509 | const int want = out->domain_size(domain); | |
| 510 | Ref<AttributeArray> arr; | |
| 511 | arr.instantiate(); | |
| 512 | arr->name = name; | |
| 513 | arr->type = type; | |
| 514 | arr->domain = domain; | |
| 515 | arr->resize(want); | |
| 516 | if (incoming <= 0 || want <= 0) { | |
| 517 | st->set(name, arr); | |
| 518 | continue; | |
| 519 | } | |
| 520 | uint8_t *dst = arr->data.ptrw(); | |
| 521 | const uint8_t *src = data.ptr(); | |
| 522 | if (domain == AttrDomain::Edge && our_edge_index.size() > 0 && | |
| 523 | sender_edges.size() / 2 == incoming) { | |
| 524 | const int32_t *se = sender_edges.ptr(); | |
| 525 | for (int e = 0; e < incoming; e++) { | |
| 526 | HashMap<uint64_t, int32_t>::ConstIterator it = | |
| 527 | our_edge_index.find(_edge_key(se[e * 2], se[e * 2 + 1])); | |
| 528 | if (it == our_edge_index.end() || it->value >= want) { | |
| 529 | continue; | |
| 530 | } | |
| 531 | memcpy(dst + (int64_t)it->value * esz, src + (int64_t)e * esz, esz); | |
| 532 | } | |
| 533 | } else { | |
| 534 | const int n = incoming < want ? incoming : want; | |
| 535 | memcpy(dst, src, (int64_t)n * esz); | |
| 536 | } | |
| 537 | st->set(name, arr); | |
| 538 | } | |
| 539 | return out; | |
| 540 | } | |
| 541 | ||
| 542 | Dictionary ProcMesh::to_mesh_data() const { | |
| 543 | Dictionary d; | |
| 544 | d["points"] = positions; | |
| 545 | d["corners"] = indices; | |
| 546 | PackedInt32Array faces = face_offsets; | |
| 547 | if (faces.size() <= 1) { | |
| 548 | // Triangles: say so explicitly rather than making the receiver guess, | |
| 549 | // which is the habit this channel exists to end. | |
| 550 | const int fc = triangle_count(); | |
| 551 | faces.resize(fc + 1); | |
| 552 | int32_t *fw = faces.ptrw(); | |
| 553 | for (int f = 0; f <= fc; f++) { | |
| 554 | fw[f] = f * 3; | |
| 555 | } | |
| 556 | } | |
| 557 | d["faces"] = faces; | |
| 558 | // Our derived order, shipped WITH the edge values so the receiver can put | |
| 559 | // them back on the right edges. | |
| 560 | PackedInt32Array edges = compute_edges(); | |
| 561 | d["edges"] = edges; | |
| 562 | ||
| 563 | Array attrs; | |
| 564 | const AttrDomain domains[4] = { AttrDomain::Point, AttrDomain::Edge, | |
| 565 | AttrDomain::Face, AttrDomain::Corner }; | |
| 566 | for (int di = 0; di < 4; di++) { | |
| 567 | const AttributeStorage *st = _storage_for(this, (int)domains[di]); | |
| 568 | if (st == nullptr) { | |
| 569 | continue; | |
| 570 | } | |
| 571 | for (const KeyValue<StringName, Ref<AttributeArray>> &kv : st->attributes) { | |
| 572 | if (kv.value.is_null()) { | |
| 573 | continue; | |
| 574 | } | |
| 575 | Dictionary a; | |
| 576 | a["name"] = String(kv.key); | |
| 577 | a["domain"] = (int)domains[di]; | |
| 578 | a["type"] = (int)kv.value->type; | |
| 579 | a["data"] = kv.value->data; | |
| 580 | attrs.push_back(a); | |
| 581 | } | |
| 582 | } | |
| 583 | d["attributes"] = attrs; | |
| 584 | return d; | |
| 585 | } | |
| 586 | ||
| 438 | 587 | } // namespace godot |
| … | … | @@ -9,6 +9,7 @@ |
| 9 | 9 | #include <godot_cpp/variant/packed_float32_array.hpp> |
| 10 | 10 | #include <godot_cpp/variant/packed_int32_array.hpp> |
| 11 | 11 | #include <godot_cpp/variant/packed_vector2_array.hpp> |
| 12 | #include <godot_cpp/variant/dictionary.hpp> | |
| 12 | 13 | #include <godot_cpp/variant/packed_vector3_array.hpp> |
| 13 | 14 | |
| 14 | 15 | namespace godot { |
| … | … | @@ -33,6 +34,28 @@ public: |
| 33 | 34 | // to_array_mesh, has always existed; this direction had not, which is why |
| 34 | 35 | // nothing could supply a graph's Group Input. |
| 35 | 36 | static Ref<ProcMesh> from_array_mesh(const Ref<Mesh> &p_mesh); |
| 37 | ||
| 38 | // THE MESH CHANNEL. A mesh as both authoring systems define one -- points, | |
| 39 | // a corner array, faces as corner ranges, edges, and named attributes on | |
| 40 | // their domains -- carried in a Dictionary so blender-bridge can hand one | |
| 41 | // over through ClassDB reflection without a compile-time dependency. | |
| 42 | // | |
| 43 | // ⛔ NOT an ArrayMesh. An ArrayMesh has ONE domain, the vertex, plus an | |
| 44 | // index buffer: a face, corner or edge value has nowhere to live in it, and | |
| 45 | // a corner normal can only be expressed by duplicating the vertex. Every | |
| 46 | // mesh that crossed the bridge as an ArrayMesh arrived triangulated and | |
| 47 | // de-indexed -- the mountain's 10088-vertex quad cage as 60516 loose | |
| 48 | // corners -- and no graph could ask it a topological question. | |
| 49 | // | |
| 50 | // points PackedVector3Array positions, sender's frame | |
| 51 | // corners PackedInt32Array vertex index per corner | |
| 52 | // faces PackedInt32Array face_count + 1 offsets into corners | |
| 53 | // edges PackedInt32Array 2 per edge (the SENDER's order) | |
| 54 | // attributes Array[Dictionary] {name, domain, type, data} | |
| 55 | // | |
| 56 | // See blender-bridge/docs/mesh-channel-design.md. | |
| 57 | static Ref<ProcMesh> from_mesh_data(const Dictionary &p_data); | |
| 58 | Dictionary to_mesh_data() const; | |
| 36 | 59 | PackedInt32Array face_offsets; // Face → corner range (OffsetIndices) |
| 37 | 60 | |
| 38 | 61 | // Named attributes per domain |
| … | … | @@ -178,49 +178,130 @@ void SubdivideMeshNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 178 | 178 | geo = geo->duplicate_geometry(); |
| 179 | 179 | Ref<ProcMesh> mesh = geo->get_mesh(); |
| 180 | 180 | |
| 181 | // ⛔ THIS NODE USED TO READ `indices` AS A FLAT TRIANGLE LIST. | |
| 182 | // | |
| 183 | // `indices` is the CORNER array; `face_offsets` groups it into faces. On a | |
| 184 | // triangle mesh those coincide, and for as long as every mesh arrived here | |
| 185 | // triangulated nobody could tell. The moment the bridge started carrying | |
| 186 | // Blender's own topology, the mountain's 10086-QUAD cage was read as 13448 | |
| 187 | // triangles cut out of quad corners three at a time -- 1520981 vertices of | |
| 188 | // plausible nonsense against Blender's 2582018. | |
| 189 | // | |
| 190 | // Blender's Subdivide Mesh is SIMPLE (linear) subdivision, and its topology | |
| 191 | // step is Catmull-Clark's: every face becomes N QUADS, one per corner, each | |
| 192 | // spanning corner -> next edge midpoint -> face centre -> previous edge | |
| 193 | // midpoint. A quad becomes 4 quads; a TRIANGLE BECOMES 3 QUADS, not 4 | |
| 194 | // triangles, which is what this produced before and is a different mesh. | |
| 181 | 195 | for (int level = 0; level < levels; level++) { |
| 182 | HashMap<uint64_t, int> edge_midpoints; | |
| 183 | PackedVector3Array new_positions = mesh->positions; | |
| 184 | PackedVector3Array new_normals = mesh->normals; | |
| 185 | PackedVector2Array new_uvs = mesh->uvs; | |
| 186 | PackedInt32Array new_indices; | |
| 187 | ||
| 188 | const Vector3 *pos = mesh->positions.ptr(); | |
| 189 | const Vector3 *nor = mesh->normals.size() > 0 ? mesh->normals.ptr() : nullptr; | |
| 190 | const Vector2 *uv_r = mesh->uvs.size() > 0 ? mesh->uvs.ptr() : nullptr; | |
| 196 | const int face_count = mesh->face_count(); | |
| 191 | 197 | const int32_t *idx = mesh->indices.ptr(); |
| 192 | int tri_count = mesh->indices.size() / 3; | |
| 198 | const Vector3 *pos = mesh->positions.ptr(); | |
| 199 | const bool has_nor = mesh->normals.size() == mesh->positions.size(); | |
| 200 | const bool has_uv = mesh->uvs.size() == mesh->positions.size(); | |
| 193 | 201 | |
| 194 | auto get_midpoint = [&](int a, int b) -> int { | |
| 195 | uint64_t key = (a < b) ? (static_cast<uint64_t>(a) << 32 | b) : (static_cast<uint64_t>(b) << 32 | a); | |
| 196 | if (edge_midpoints.has(key)) { | |
| 197 | return edge_midpoints[key]; | |
| 202 | PackedVector3Array new_positions = mesh->positions; | |
| 203 | PackedVector3Array new_normals = has_nor ? mesh->normals : PackedVector3Array(); | |
| 204 | PackedVector2Array new_uvs = has_uv ? mesh->uvs : PackedVector2Array(); | |
| 205 | PackedInt32Array new_corners; | |
| 206 | PackedInt32Array new_faces; | |
| 207 | ||
| 208 | HashMap<uint64_t, int> edge_midpoints; | |
| 209 | auto midpoint = [&](int a, int b) -> int { | |
| 210 | const uint64_t key = (a < b) ? ((uint64_t)a << 32 | (uint32_t)b) | |
| 211 | : ((uint64_t)b << 32 | (uint32_t)a); | |
| 212 | HashMap<uint64_t, int>::ConstIterator it = edge_midpoints.find(key); | |
| 213 | if (it != edge_midpoints.end()) { | |
| 214 | return it->value; | |
| 198 | 215 | } |
| 199 | int new_idx = new_positions.size(); | |
| 216 | const int n = new_positions.size(); | |
| 200 | 217 | new_positions.push_back((pos[a] + pos[b]) * 0.5f); |
| 201 | if (nor) new_normals.push_back(((nor[a] + nor[b]) * 0.5f).normalized()); | |
| 202 | if (uv_r) new_uvs.push_back((uv_r[a] + uv_r[b]) * 0.5f); | |
| 203 | edge_midpoints[key] = new_idx; | |
| 204 | return new_idx; | |
| 218 | if (has_nor) { | |
| 219 | new_normals.push_back(((mesh->normals[a] + mesh->normals[b]) * 0.5f).normalized()); | |
| 220 | } | |
| 221 | if (has_uv) { | |
| 222 | new_uvs.push_back((mesh->uvs[a] + mesh->uvs[b]) * 0.5f); | |
| 223 | } | |
| 224 | edge_midpoints[key] = n; | |
| 225 | return n; | |
| 205 | 226 | }; |
| 206 | 227 | |
| 207 | for (int t = 0; t < tri_count; t++) { | |
| 208 | int v0 = idx[t * 3], v1 = idx[t * 3 + 1], v2 = idx[t * 3 + 2]; | |
| 209 | int m01 = get_midpoint(v0, v1); | |
| 210 | int m12 = get_midpoint(v1, v2); | |
| 211 | int m20 = get_midpoint(v2, v0); | |
| 212 | ||
| 213 | // 4 sub-triangles | |
| 214 | new_indices.push_back(v0); new_indices.push_back(m01); new_indices.push_back(m20); | |
| 215 | new_indices.push_back(m01); new_indices.push_back(v1); new_indices.push_back(m12); | |
| 216 | new_indices.push_back(m20); new_indices.push_back(m12); new_indices.push_back(v2); | |
| 217 | new_indices.push_back(m01); new_indices.push_back(m12); new_indices.push_back(m20); | |
| 228 | new_faces.push_back(0); | |
| 229 | for (int f = 0; f < face_count; f++) { | |
| 230 | const int begin = mesh->face_corner_begin(f); | |
| 231 | const int n = mesh->face_size(f); | |
| 232 | if (n < 3) { | |
| 233 | continue; | |
| 234 | } | |
| 235 | // The face centre, once per face. | |
| 236 | Vector3 c_pos; | |
| 237 | Vector3 c_nor; | |
| 238 | Vector2 c_uv; | |
| 239 | for (int k = 0; k < n; k++) { | |
| 240 | const int v = idx[begin + k]; | |
| 241 | c_pos += pos[v]; | |
| 242 | if (has_nor) { | |
| 243 | c_nor += mesh->normals[v]; | |
| 244 | } | |
| 245 | if (has_uv) { | |
| 246 | c_uv += mesh->uvs[v]; | |
| 247 | } | |
| 248 | } | |
| 249 | const float inv = 1.0f / (float)n; | |
| 250 | const int centre = new_positions.size(); | |
| 251 | new_positions.push_back(c_pos * inv); | |
| 252 | if (has_nor) { | |
| 253 | new_normals.push_back((c_nor * inv).normalized()); | |
| 254 | } | |
| 255 | if (has_uv) { | |
| 256 | new_uvs.push_back(c_uv * inv); | |
| 257 | } | |
| 258 | ||
| 259 | for (int k = 0; k < n; k++) { | |
| 260 | const int v = idx[begin + k]; | |
| 261 | const int v_next = idx[begin + (k + 1) % n]; | |
| 262 | const int v_prev = idx[begin + (k + n - 1) % n]; | |
| 263 | const int m_next = midpoint(v, v_next); | |
| 264 | const int m_prev = midpoint(v_prev, v); | |
| 265 | new_corners.push_back(v); | |
| 266 | new_corners.push_back(m_next); | |
| 267 | new_corners.push_back(centre); | |
| 268 | new_corners.push_back(m_prev); | |
| 269 | new_faces.push_back(new_corners.size()); | |
| 270 | } | |
| 218 | 271 | } |
| 219 | 272 | |
| 220 | 273 | mesh->positions = new_positions; |
| 221 | 274 | mesh->normals = new_normals; |
| 222 | 275 | mesh->uvs = new_uvs; |
| 223 | mesh->indices = new_indices; | |
| 276 | mesh->indices = new_corners; | |
| 277 | mesh->face_offsets = new_faces; | |
| 278 | } | |
| 279 | ||
| 280 | // Attributes cannot ride through this untouched: every domain changed size. | |
| 281 | // Dropping the ones that no longer fit is not a loss of information -- they | |
| 282 | // were already meaningless at the new size -- and it is said out loud rather | |
| 283 | // than left as an array that still reads as data. | |
| 284 | { | |
| 285 | const AttrDomain doms[4] = { AttrDomain::Point, AttrDomain::Edge, | |
| 286 | AttrDomain::Face, AttrDomain::Corner }; | |
| 287 | AttributeStorage *st[4] = { &mesh->point_attrs, &mesh->edge_attrs, | |
| 288 | &mesh->face_attrs, &mesh->corner_attrs }; | |
| 289 | for (int d = 0; d < 4; d++) { | |
| 290 | Vector<StringName> stale; | |
| 291 | for (const KeyValue<StringName, Ref<AttributeArray>> &kv : st[d]->attributes) { | |
| 292 | if (kv.value.is_valid() && kv.value->size() != mesh->domain_size(doms[d])) { | |
| 293 | stale.push_back(kv.key); | |
| 294 | } | |
| 295 | } | |
| 296 | for (int i = 0; i < stale.size(); i++) { | |
| 297 | UtilityFunctions::push_warning( | |
| 298 | "proc-nodes: Subdivide Mesh dropped attribute '", String(stale[i]), | |
| 299 | "' on the ", attr_domain_name(doms[d]), | |
| 300 | " domain -- subdivision changes that domain and this node does " | |
| 301 | "not interpolate yet."); | |
| 302 | st[d]->remove(stale[i]); | |
| 303 | } | |
| 304 | } | |
| 224 | 305 | } |
| 225 | 306 | |
| 226 | 307 | p_ctx.write_geometry(p_node_idx, 0, geo); |
| … | … | @@ -10,18 +10,37 @@ namespace godot { |
| 10 | 10 | |
| 11 | 11 | Dictionary ProcGeometryNode::_build_overrides() const { |
| 12 | 12 | Dictionary d; |
| 13 | if (input_mesh.is_valid()) { | |
| 14 | Ref<ProcMesh> pm = ProcMesh::from_array_mesh(input_mesh); | |
| 15 | if (pm.is_valid()) { | |
| 16 | Ref<ProcGeometry> g; | |
| 17 | g.instantiate(); | |
| 18 | g->set_mesh(pm); | |
| 19 | d["geometry"] = g; | |
| 20 | } | |
| 13 | // The mesh channel first. An ArrayMesh reaching this function has already | |
| 14 | // lost its faces and, when it came over the bridge, its shared vertices -- | |
| 15 | // so if the real mesh is here, the ArrayMesh is not a second opinion, it is | |
| 16 | // a worse copy of the same thing. | |
| 17 | Ref<ProcMesh> pm; | |
| 18 | if (input_mesh_data.has("points")) { | |
| 19 | pm = ProcMesh::from_mesh_data(input_mesh_data); | |
| 20 | } else if (input_mesh.is_valid()) { | |
| 21 | pm = ProcMesh::from_array_mesh(input_mesh); | |
| 22 | } | |
| 23 | if (pm.is_valid()) { | |
| 24 | Ref<ProcGeometry> g; | |
| 25 | g.instantiate(); | |
| 26 | g->set_mesh(pm); | |
| 27 | d["geometry"] = g; | |
| 21 | 28 | } |
| 22 | 29 | return d; |
| 23 | 30 | } |
| 24 | 31 | |
| 32 | Dictionary ProcGeometryNode::get_output_mesh_data() { | |
| 33 | Ref<ProcGeometry> g = bake_geometry(); | |
| 34 | if (g.is_null()) { | |
| 35 | return Dictionary(); | |
| 36 | } | |
| 37 | Ref<ProcMesh> m = g->get_mesh(); | |
| 38 | if (m.is_null()) { | |
| 39 | return Dictionary(); | |
| 40 | } | |
| 41 | return m->to_mesh_data(); | |
| 42 | } | |
| 43 | ||
| 25 | 44 | Dictionary ProcGeometryNode::get_last_exec_report() { |
| 26 | 45 | return GraphExecutor::exec_report(); |
| 27 | 46 | } |
| … | … | @@ -29,6 +48,9 @@ Dictionary ProcGeometryNode::get_last_exec_report() { |
| 29 | 48 | void ProcGeometryNode::_bind_methods() { |
| 30 | 49 | ClassDB::bind_method(D_METHOD("set_input_mesh", "mesh"), &ProcGeometryNode::set_input_mesh); |
| 31 | 50 | ClassDB::bind_method(D_METHOD("get_input_mesh"), &ProcGeometryNode::get_input_mesh); |
| 51 | ClassDB::bind_method(D_METHOD("set_input_mesh_data", "data"), &ProcGeometryNode::set_input_mesh_data); | |
| 52 | ClassDB::bind_method(D_METHOD("get_input_mesh_data"), &ProcGeometryNode::get_input_mesh_data); | |
| 53 | ClassDB::bind_method(D_METHOD("get_output_mesh_data"), &ProcGeometryNode::get_output_mesh_data); | |
| 32 | 54 | ClassDB::bind_method(D_METHOD("set_graph", "graph"), &ProcGeometryNode::set_graph); |
| 33 | 55 | ClassDB::bind_method(D_METHOD("get_graph"), &ProcGeometryNode::get_graph); |
| 34 | 56 | ClassDB::bind_method(D_METHOD("set_auto_update", "auto_update"), &ProcGeometryNode::set_auto_update); |
| … | … | @@ -24,6 +24,13 @@ protected: |
| 24 | 24 | // on the object's own mesh; without this a bridged graph has no geometry to |
| 25 | 25 | // act on and bakes to nothing. |
| 26 | 26 | Ref<Mesh> input_mesh; |
| 27 | ||
| 28 | // The same input, as a MESH rather than as a draw buffer: points, the | |
| 29 | // corner array, faces as corner ranges, edges and attributes on their | |
| 30 | // domains. blender-bridge fills this; when it is present it WINS over | |
| 31 | // input_mesh, because input_mesh can only ever be the triangulated, | |
| 32 | // corner-expanded shadow of it. See ProcMesh::from_mesh_data. | |
| 33 | Dictionary input_mesh_data; | |
| 27 | 34 | bool auto_update = true; |
| 28 | 35 | bool live_evaluation = true; |
| 29 | 36 | bool use_gpu = true; |
| … | … | @@ -80,6 +87,13 @@ public: |
| 80 | 87 | void set_input_mesh(const Ref<Mesh> &p_mesh) { input_mesh = p_mesh; mark_dirty(); } |
| 81 | 88 | Ref<Mesh> get_input_mesh() const { return input_mesh; } |
| 82 | 89 | |
| 90 | void set_input_mesh_data(const Dictionary &p_data) { input_mesh_data = p_data; mark_dirty(); } | |
| 91 | Dictionary get_input_mesh_data() const { return input_mesh_data; } | |
| 92 | ||
| 93 | // The evaluated result as a mesh, for the Godot -> Blender direction. Every | |
| 94 | // face, edge and corner value survives, which bake_mesh() cannot carry. | |
| 95 | Dictionary get_output_mesh_data(); | |
| 96 | ||
| 83 | 97 | void evaluate(); |
| 84 | 98 | |
| 85 | 99 | // Bake: evaluate once and save result |