OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes commits / 6118a06

mesh: take a MESH from the bridge, and subdivide FACES
Two halves of the same thing: proc-nodes can now be handed a mesh as both authoring systems define one, and the first node to receive one was reading it wrong. ProcMesh::from_mesh_data / to_mesh_data. Points, the corner array, faces as corner ranges, edges, and named attributes on their domains, in a Dictionary so blender-bridge can hand one over through ClassDB reflection. ProcGeometryNode::set_input_mesh_data wins over input_mesh when both are set, because an ArrayMesh is the DRAW form of the same thing: one domain, the vertex, plus an index buffer, so a face, edge or corner value has nowhere to live and a corner normal can only be expressed by duplicating the vertex. Everything the bridge sent arrived that way -- the mountain's 10088-point, 10086-QUAD cage as 60516 loose corners, from_array_mesh fanning 0,1,2,... over them because a non-indexed surface is still triangles. ⛔ An EDGE attribute cannot be copied by index. ProcMesh derives its edges in its own sorted order; the sender ships its edge array and every edge value is remapped through the vertex pair it belongs to. Without that the values land on the wrong edges with the counts still matching, which is the failure that does not announce itself. Subdivide Mesh read `indices` as a flat triangle list -- tri_count = indices.size() / 3 -- and ignored face_offsets. On a triangle mesh those coincide, and every mesh reaching it had been triangulated, so nothing could tell. Given Blender's own topology it read 10086 quads as 13448 triangles cut from quad corners three at a time: 1520981 vertices of plausible nonsense. Blender's Subdivide Mesh is simple (linear) subdivision with Catmull-Clark topology: every face becomes N QUADS, one per corner, spanning corner -> next edge midpoint -> face centre -> previous edge midpoint. A quad becomes 4 quads and A TRIANGLE BECOMES 3 QUADS, not the 4 triangles this produced before. Measured over the real transport, on mountain_voronoi1.blend: cage 10088 points / 10086 quads proc-nodes 2582018 verts Blender 2582018 verts Attributes are dropped where subdivision changed their domain, with a warning, rather than left as arrays that still read as data. demo/mesh_channel_gate.gd, 10/10: a quad stays one face, every edge value lands on its own edge, and the round trip keeps both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6118a063606bcc99c2e3df5abe527d8802a62e2b
by plankatron <fleeky@gmail.com> · 2026-09-08T19:38:44.000Z

Changed files (6) +461 -39

demo/mesh_channel_gate.gd +133 -0
……@@ -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.
15extends SceneTree
16
17var passed := 0
18var failed := 0
19
20func 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
25func _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.
120func _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
src/core/proc_mesh.cpp +149 -0
……@@ -48,6 +48,8 @@ void ProcMesh::_bind_methods() {
4848 ClassDB::bind_method(D_METHOD("get_domain_size", "domain"), &ProcMesh::get_domain_size);
4949 ClassDB::bind_method(D_METHOD("get_attribute_values", "domain", "name"), &ProcMesh::get_attribute_values);
5050 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);
5153}
5254
5355static 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) {
435437 return out;
436438}
437439
440
441// ── THE MESH CHANNEL ────────────────────────────────────────────────────────
442
443static 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
453static 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
459Ref<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
542Dictionary 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
438587} // namespace godot
src/core/proc_mesh.h +23 -0
……@@ -9,6 +9,7 @@
99#include <godot_cpp/variant/packed_float32_array.hpp>
1010#include <godot_cpp/variant/packed_int32_array.hpp>
1111#include <godot_cpp/variant/packed_vector2_array.hpp>
12#include <godot_cpp/variant/dictionary.hpp>
1213#include <godot_cpp/variant/packed_vector3_array.hpp>
1314
1415namespace godot {
……@@ -33,6 +34,28 @@ public:
3334 // to_array_mesh, has always existed; this direction had not, which is why
3435 // nothing could supply a graph's Group Input.
3536 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;
3659 PackedInt32Array face_offsets; // Face → corner range (OffsetIndices)
3760
3861 // Named attributes per domain
src/nodes/mesh_ops_nodes.cpp +112 -31
……@@ -178,49 +178,130 @@ void SubdivideMeshNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
178178 geo = geo->duplicate_geometry();
179179 Ref<ProcMesh> mesh = geo->get_mesh();
180180
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.
181195 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();
191197 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();
193201
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;
198215 }
199 int new_idx = new_positions.size();
216 const int n = new_positions.size();
200217 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;
205226 };
206227
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 }
218271 }
219272
220273 mesh->positions = new_positions;
221274 mesh->normals = new_normals;
222275 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 }
224305 }
225306
226307 p_ctx.write_geometry(p_node_idx, 0, geo);
src/scene/proc_geometry_node.cpp +30 -8
……@@ -10,18 +10,37 @@ namespace godot {
1010
1111Dictionary ProcGeometryNode::_build_overrides() const {
1212 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;
2128 }
2229 return d;
2330}
2431
32Dictionary 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
2544Dictionary ProcGeometryNode::get_last_exec_report() {
2645 return GraphExecutor::exec_report();
2746}
……@@ -29,6 +48,9 @@ Dictionary ProcGeometryNode::get_last_exec_report() {
2948void ProcGeometryNode::_bind_methods() {
3049 ClassDB::bind_method(D_METHOD("set_input_mesh", "mesh"), &ProcGeometryNode::set_input_mesh);
3150 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);
3254 ClassDB::bind_method(D_METHOD("set_graph", "graph"), &ProcGeometryNode::set_graph);
3355 ClassDB::bind_method(D_METHOD("get_graph"), &ProcGeometryNode::get_graph);
3456 ClassDB::bind_method(D_METHOD("set_auto_update", "auto_update"), &ProcGeometryNode::set_auto_update);
src/scene/proc_geometry_node.h +14 -0
……@@ -24,6 +24,13 @@ protected:
2424 // on the object's own mesh; without this a bridged graph has no geometry to
2525 // act on and bakes to nothing.
2626 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;
2734 bool auto_update = true;
2835 bool live_evaluation = true;
2936 bool use_gpu = true;
……@@ -80,6 +87,13 @@ public:
8087 void set_input_mesh(const Ref<Mesh> &p_mesh) { input_mesh = p_mesh; mark_dirty(); }
8188 Ref<Mesh> get_input_mesh() const { return input_mesh; }
8289
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
8397 void evaluate();
8498
8599 // Bake: evaluate once and save result