| … | … | @@ -41,6 +41,7 @@ void ProcMesh::_bind_methods() { |
| 41 | 41 | ClassDB::bind_method(D_METHOD("to_array_mesh"), &ProcMesh::to_array_mesh); |
| 42 | 42 | ClassDB::bind_method(D_METHOD("update_array_mesh", "mesh"), &ProcMesh::update_array_mesh); |
| 43 | 43 | ClassDB::bind_method(D_METHOD("recalculate_normals"), &ProcMesh::recalculate_normals); |
| 44 | ClassDB::bind_method(D_METHOD("reverse_faces"), &ProcMesh::reverse_faces); |
| 44 | 45 | ClassDB::bind_method(D_METHOD("get_indices"), &ProcMesh::get_indices); |
| 45 | 46 | ClassDB::bind_method(D_METHOD("get_face_offsets"), &ProcMesh::get_face_offsets); |
| 46 | 47 | ClassDB::bind_method(D_METHOD("get_corner_count"), &ProcMesh::get_corner_count); |
| … | … | @@ -217,35 +218,10 @@ Ref<ArrayMesh> ProcMesh::to_array_mesh() const { |
| 217 | 218 | arrays[Mesh::ARRAY_NORMAL] = normals; |
| 218 | 219 | } else if (indices.size() >= 3 && positions.size() > 0) { |
| 219 | 220 | PackedVector3Array derived; |
| 220 | | derived.resize(positions.size()); |
| 221 | | Vector3 *nw = derived.ptrw(); |
| 222 | | for (int i = 0; i < derived.size(); i++) { |
| 223 | | nw[i] = Vector3(); |
| 221 | compute_normals(derived); |
| 222 | if (derived.size() == positions.size()) { |
| 223 | arrays[Mesh::ARRAY_NORMAL] = derived; |
| 224 | 224 | } |
| 225 | | const Vector3 *pr = positions.ptr(); |
| 226 | | const int fc = face_count(); |
| 227 | | for (int f = 0; f < fc; f++) { |
| 228 | | const int b = face_corner_begin(f); |
| 229 | | const int n = face_size(f); |
| 230 | | for (int k = 1; k + 1 < n; k++) { |
| 231 | | const int32_t i0 = indices[b]; |
| 232 | | const int32_t i1 = indices[b + k]; |
| 233 | | const int32_t i2 = indices[b + k + 1]; |
| 234 | | if (i0 >= derived.size() || i1 >= derived.size() || i2 >= derived.size()) { |
| 235 | | continue; |
| 236 | | } |
| 237 | | // Area-weighted: the cross product's LENGTH is twice the triangle's |
| 238 | | // area, so there is no per-face normalisation. |
| 239 | | const Vector3 fn = (pr[i1] - pr[i0]).cross(pr[i2] - pr[i0]); |
| 240 | | nw[i0] += fn; |
| 241 | | nw[i1] += fn; |
| 242 | | nw[i2] += fn; |
| 243 | | } |
| 244 | | } |
| 245 | | for (int i = 0; i < derived.size(); i++) { |
| 246 | | nw[i] = nw[i].length_squared() > 0.0f ? nw[i].normalized() : Vector3(0, 1, 0); |
| 247 | | } |
| 248 | | arrays[Mesh::ARRAY_NORMAL] = derived; |
| 249 | 225 | } |
| 250 | 226 | |
| 251 | 227 | if (tangents.size() > 0 && tangents.size() == positions.size() * 4) { |
| … | … | @@ -327,42 +303,96 @@ bool ProcMesh::update_array_mesh(Ref<ArrayMesh> p_mesh) const { |
| 327 | 303 | return true; |
| 328 | 304 | } |
| 329 | 305 | |
| 330 | | void ProcMesh::recalculate_normals() { |
| 331 | | int vert_count = positions.size(); |
| 332 | | if (vert_count == 0 || indices.size() == 0) { |
| 306 | void ProcMesh::reverse_faces() { |
| 307 | const int fc = face_count(); |
| 308 | if (fc == 0 || indices.size() == 0) { |
| 333 | 309 | return; |
| 334 | 310 | } |
| 311 | PackedInt32Array perm; |
| 312 | perm.resize(indices.size()); |
| 313 | int32_t *cw = indices.ptrw(); |
| 314 | int32_t *pw = perm.ptrw(); |
| 315 | for (int f = 0; f < fc; f++) { |
| 316 | const int b = face_corner_begin(f); |
| 317 | const int n = face_size(f); |
| 318 | // The face keeps its first corner and reverses the rest, so it still |
| 319 | // starts at the same vertex and only its orientation changes. |
| 320 | pw[b] = b; |
| 321 | for (int k = 1; k < n; k++) { |
| 322 | pw[b + k] = b + (n - k); |
| 323 | } |
| 324 | for (int k = 1; k < (n + 1) / 2; k++) { |
| 325 | const int32_t tmp = cw[b + k]; |
| 326 | cw[b + k] = cw[b + n - k]; |
| 327 | cw[b + n - k] = tmp; |
| 328 | } |
| 329 | } |
| 330 | // Corner values are indexed by corner, so they move with them. Reversing |
| 331 | // one without the other shears them, silently, since the counts still match. |
| 332 | for (const KeyValue<StringName, Ref<AttributeArray>> &kv : corner_attrs.attributes) { |
| 333 | Ref<AttributeArray> a = kv.value; |
| 334 | if (a.is_null() || a->size() != indices.size()) { |
| 335 | continue; |
| 336 | } |
| 337 | const int esz = attr_type_size(a->type); |
| 338 | const PackedByteArray src = a->data; |
| 339 | uint8_t *dst = a->data.ptrw(); |
| 340 | const uint8_t *sp = src.ptr(); |
| 341 | for (int c = 0; c < indices.size(); c++) { |
| 342 | memcpy(dst + (int64_t)pw[c] * esz, sp + (int64_t)c * esz, esz); |
| 343 | } |
| 344 | } |
| 345 | } |
| 335 | 346 | |
| 336 | | normals.resize(vert_count); |
| 337 | | Vector3 *nw = normals.ptrw(); |
| 347 | void ProcMesh::recalculate_normals() { |
| 348 | compute_normals(normals); |
| 349 | } |
| 350 | |
| 351 | // ⛔ ONE PLACE. recalculate_normals() and to_array_mesh() each had their own |
| 352 | // copy of this and they disagreed about the SIGN, which is the whole of the |
| 353 | // question: proc-nodes stores meshes in GODOT's winding, where the front face |
| 354 | // is clockwise, so the outward normal is the NEGATED cross product. One of the |
| 355 | // two copies knew that. |
| 356 | // |
| 357 | // ⛔ AND IT READ `indices` AS A FLAT TRIANGLE LIST -- tri_count = size / 3 -- |
| 358 | // exactly as Subdivide Mesh did. `indices` is the CORNER array; face_offsets |
| 359 | // groups it. Once the bridge started carrying real topology and Subdivide Mesh |
| 360 | // started producing quads, this computed normals from corner triples that span |
| 361 | // three different faces: the bridged mountain rendered near-black with bright |
| 362 | // specks where a nonsense normal happened to face the light. |
| 363 | void ProcMesh::compute_normals(PackedVector3Array &r_out) const { |
| 364 | const int vert_count = positions.size(); |
| 365 | if (vert_count == 0 || indices.size() < 3) { |
| 366 | return; |
| 367 | } |
| 368 | r_out.resize(vert_count); |
| 369 | Vector3 *nw = r_out.ptrw(); |
| 338 | 370 | const Vector3 *pw = positions.ptr(); |
| 339 | 371 | const int32_t *iw = indices.ptr(); |
| 340 | | |
| 341 | | // Zero normals |
| 342 | 372 | for (int i = 0; i < vert_count; i++) { |
| 343 | 373 | nw[i] = Vector3(); |
| 344 | 374 | } |
| 345 | | |
| 346 | | // Accumulate face normals |
| 347 | | int tri_count = indices.size() / 3; |
| 348 | | for (int t = 0; t < tri_count; t++) { |
| 349 | | int i0 = iw[t * 3]; |
| 350 | | int i1 = iw[t * 3 + 1]; |
| 351 | | int i2 = iw[t * 3 + 2]; |
| 352 | | |
| 353 | | Vector3 e1 = pw[i1] - pw[i0]; |
| 354 | | Vector3 e2 = pw[i2] - pw[i0]; |
| 355 | | // Negate: CW winding (Godot front-face) produces inverted cross product |
| 356 | | Vector3 fn = -e1.cross(e2); |
| 357 | | |
| 358 | | nw[i0] += fn; |
| 359 | | nw[i1] += fn; |
| 360 | | nw[i2] += fn; |
| 375 | const int fc = face_count(); |
| 376 | for (int f = 0; f < fc; f++) { |
| 377 | const int b = face_corner_begin(f); |
| 378 | const int n = face_size(f); |
| 379 | for (int k = 1; k + 1 < n; k++) { |
| 380 | const int32_t i0 = iw[b]; |
| 381 | const int32_t i1 = iw[b + k]; |
| 382 | const int32_t i2 = iw[b + k + 1]; |
| 383 | if (i0 >= vert_count || i1 >= vert_count || i2 >= vert_count) { |
| 384 | continue; |
| 385 | } |
| 386 | // Area-weighted -- the cross product's length is twice the area -- |
| 387 | // and NEGATED for Godot's clockwise front face. |
| 388 | const Vector3 fn = -(pw[i1] - pw[i0]).cross(pw[i2] - pw[i0]); |
| 389 | nw[i0] += fn; |
| 390 | nw[i1] += fn; |
| 391 | nw[i2] += fn; |
| 392 | } |
| 361 | 393 | } |
| 362 | | |
| 363 | | // Normalize |
| 364 | 394 | for (int i = 0; i < vert_count; i++) { |
| 365 | | nw[i].normalize(); |
| 395 | nw[i] = nw[i].length_squared() > 0.0f ? nw[i].normalized() : Vector3(0, 1, 0); |
| 366 | 396 | } |
| 367 | 397 | } |
| 368 | 398 | |