| … | … | @@ -19,7 +19,10 @@ using namespace godot; |
| 19 | 19 | // ── Data layout in blob region ──────────────────────────────────────────────── |
| 20 | 20 | // Per object: [name prefix] [4×4 transform (64 bytes)] [optional vertex data] |
| 21 | 21 | // Name prefix: [uint8 name_len] [name_bytes] [padding to 4-byte align] |
| 22 | | // Vertex data: [uint32 vertex_count] [positions: 3*N floats] [normals: 3*N floats] |
| 22 | // Vertex data: [uint32 vert_count] [float32×3 × vert_count positions] |
| 23 | // [uint32 has_normals] [float32×3 × vert_count normals] |
| 24 | // [uint32 idx_count] [int32 × idx_count indices] |
| 25 | // [uint32 smooth_count] [uint8 × smooth_count flags] (0=flat, 1=smooth) |
| 23 | 26 | |
| 24 | 27 | static constexpr uint32_t TRANSFORM_SIZE = 64; // 4×4 float matrix |
| 25 | 28 | |
| … | … | @@ -384,6 +387,41 @@ uint32_t BlenderBridge::_export_node(Node3D *node, uint32_t index, uint8_t *blob |
| 384 | 387 | memcpy(blob_base + blob_offset, idx_r, idx_count * 4); |
| 385 | 388 | blob_offset += idx_count * 4; |
| 386 | 389 | } |
| 390 | |
| 391 | // Write per-triangle smooth flags (0=flat, 1=smooth) |
| 392 | uint32_t tri_count = idx_count / 3; |
| 393 | memcpy(blob_base + blob_offset, &tri_count, 4); |
| 394 | blob_offset += 4; |
| 395 | |
| 396 | if (tri_count > 0) { |
| 397 | if (has_normals && has_indices) { |
| 398 | PackedVector3Array norm_arr = arrays[Mesh::ARRAY_NORMAL]; |
| 399 | const Vector3 *nr = norm_arr.ptr(); |
| 400 | PackedInt32Array idx_arr = arrays[Mesh::ARRAY_INDEX]; |
| 401 | const int32_t *ir = idx_arr.ptr(); |
| 402 | |
| 403 | for (uint32_t t = 0; t < tri_count; t++) { |
| 404 | int32_t i0 = ir[t * 3], i1 = ir[t * 3 + 1], i2 = ir[t * 3 + 2]; |
| 405 | Vector3 fn = (pos_r[i1] - pos_r[i0]).cross(pos_r[i2] - pos_r[i0]); |
| 406 | float len_sq = fn.length_squared(); |
| 407 | |
| 408 | uint8_t is_smooth = 1; |
| 409 | if (len_sq > 1e-12f) { |
| 410 | fn /= Math::sqrt(len_sq); |
| 411 | if (nr[i0].dot(fn) > 0.999f && |
| 412 | nr[i1].dot(fn) > 0.999f && |
| 413 | nr[i2].dot(fn) > 0.999f) { |
| 414 | is_smooth = 0; |
| 415 | } |
| 416 | } |
| 417 | blob_base[blob_offset++] = is_smooth; |
| 418 | } |
| 419 | } else { |
| 420 | // No normals or no indices — assume all smooth |
| 421 | memset(blob_base + blob_offset, 1, tri_count); |
| 422 | blob_offset += tri_count; |
| 423 | } |
| 424 | } |
| 387 | 425 | } |
| 388 | 426 | } |
| 389 | 427 | } |
| … | … | @@ -716,6 +754,7 @@ void BlenderBridge::import_changes(Node *root) { |
| 716 | 754 | if (idx_count > 0 && cursor + idx_count * 4 <= entry.data_offset + entry.data_size) { |
| 717 | 755 | indices.resize(idx_count); |
| 718 | 756 | memcpy(indices.ptrw(), base + cursor, idx_count * 4); |
| 757 | cursor += idx_count * 4; |
| 719 | 758 | // Reverse triangle winding order: the Y↔Z axis swap changes |
| 720 | 759 | // handedness, so Blender's CCW becomes CW in Godot without this fix. |
| 721 | 760 | int32_t *idx_w = indices.ptrw(); |
| … | … | @@ -726,6 +765,71 @@ void BlenderBridge::import_changes(Node *root) { |
| 726 | 765 | } |
| 727 | 766 | } |
| 728 | 767 | |
| 768 | // Read per-triangle smooth flags (after indices) |
| 769 | uint32_t smooth_count = 0; |
| 770 | if (cursor + 4 <= entry.data_offset + entry.data_size) { |
| 771 | memcpy(&smooth_count, base + cursor, 4); |
| 772 | cursor += 4; |
| 773 | } |
| 774 | |
| 775 | std::vector<uint8_t> smooth_flags; |
| 776 | if (smooth_count > 0 && cursor + smooth_count <= entry.data_offset + entry.data_size) { |
| 777 | smooth_flags.resize(smooth_count); |
| 778 | memcpy(smooth_flags.data(), base + cursor, smooth_count); |
| 779 | cursor += smooth_count; |
| 780 | } |
| 781 | |
| 782 | // Handle flat shading: duplicate vertices with face normals |
| 783 | if (!smooth_flags.empty() && normals.size() > 0 && indices.size() > 0) { |
| 784 | uint32_t tri_count = indices.size() / 3; |
| 785 | bool has_flat = false; |
| 786 | for (uint32_t t = 0; t < tri_count && t < smooth_count; t++) { |
| 787 | if (smooth_flags[t] == 0) { has_flat = true; break; } |
| 788 | } |
| 789 | |
| 790 | if (has_flat) { |
| 791 | PackedVector3Array new_positions = positions; |
| 792 | PackedVector3Array new_normals = normals; |
| 793 | PackedInt32Array new_indices; |
| 794 | const Vector3 *orig_pos = positions.ptr(); |
| 795 | |
| 796 | for (uint32_t t = 0; t < tri_count; t++) { |
| 797 | int32_t i0 = indices[t * 3]; |
| 798 | int32_t i1 = indices[t * 3 + 1]; |
| 799 | int32_t i2 = indices[t * 3 + 2]; |
| 800 | |
| 801 | if (t < smooth_count && smooth_flags[t] == 0) { |
| 802 | // Flat: duplicate vertices with face normal |
| 803 | Vector3 p0 = orig_pos[i0]; |
| 804 | Vector3 p1 = orig_pos[i1]; |
| 805 | Vector3 p2 = orig_pos[i2]; |
| 806 | Vector3 face_n = (p1 - p0).cross(p2 - p0).normalized(); |
| 807 | |
| 808 | int32_t base_idx = new_positions.size(); |
| 809 | new_positions.push_back(p0); |
| 810 | new_positions.push_back(p1); |
| 811 | new_positions.push_back(p2); |
| 812 | new_normals.push_back(face_n); |
| 813 | new_normals.push_back(face_n); |
| 814 | new_normals.push_back(face_n); |
| 815 | |
| 816 | new_indices.push_back(base_idx); |
| 817 | new_indices.push_back(base_idx + 1); |
| 818 | new_indices.push_back(base_idx + 2); |
| 819 | } else { |
| 820 | // Smooth: keep original indices |
| 821 | new_indices.push_back(i0); |
| 822 | new_indices.push_back(i1); |
| 823 | new_indices.push_back(i2); |
| 824 | } |
| 825 | } |
| 826 | |
| 827 | positions = new_positions; |
| 828 | normals = new_normals; |
| 829 | indices = new_indices; |
| 830 | } |
| 831 | } |
| 832 | |
| 729 | 833 | Ref<ArrayMesh> arr_mesh = Object::cast_to<ArrayMesh>(mesh_inst->get_mesh().ptr()); |
| 730 | 834 | if (!arr_mesh.is_valid()) { |
| 731 | 835 | arr_mesh.instantiate(); |
| … | … | @@ -1023,6 +1127,40 @@ void BlenderBridge::_detect_and_push_changes() { |
| 1023 | 1127 | memcpy(base + blob_offset, idx_r, idx_count * 4); |
| 1024 | 1128 | blob_offset += idx_count * 4; |
| 1025 | 1129 | } |
| 1130 | |
| 1131 | // Write per-triangle smooth flags (0=flat, 1=smooth) |
| 1132 | uint32_t tri_count = idx_count / 3; |
| 1133 | memcpy(base + blob_offset, &tri_count, 4); |
| 1134 | blob_offset += 4; |
| 1135 | |
| 1136 | if (tri_count > 0) { |
| 1137 | if (has_normals && has_indices) { |
| 1138 | PackedVector3Array norm_arr = arrays[Mesh::ARRAY_NORMAL]; |
| 1139 | const Vector3 *nr = norm_arr.ptr(); |
| 1140 | PackedInt32Array idx_arr = arrays[Mesh::ARRAY_INDEX]; |
| 1141 | const int32_t *ir = idx_arr.ptr(); |
| 1142 | |
| 1143 | for (uint32_t t = 0; t < tri_count; t++) { |
| 1144 | int32_t i0 = ir[t * 3], i1 = ir[t * 3 + 1], i2 = ir[t * 3 + 2]; |
| 1145 | Vector3 fn = (pos_r[i1] - pos_r[i0]).cross(pos_r[i2] - pos_r[i0]); |
| 1146 | float len_sq = fn.length_squared(); |
| 1147 | |
| 1148 | uint8_t is_smooth = 1; |
| 1149 | if (len_sq > 1e-12f) { |
| 1150 | fn /= Math::sqrt(len_sq); |
| 1151 | if (nr[i0].dot(fn) > 0.999f && |
| 1152 | nr[i1].dot(fn) > 0.999f && |
| 1153 | nr[i2].dot(fn) > 0.999f) { |
| 1154 | is_smooth = 0; |
| 1155 | } |
| 1156 | } |
| 1157 | base[blob_offset++] = is_smooth; |
| 1158 | } |
| 1159 | } else { |
| 1160 | memset(base + blob_offset, 1, tri_count); |
| 1161 | blob_offset += tri_count; |
| 1162 | } |
| 1163 | } |
| 1026 | 1164 | } |
| 1027 | 1165 | } |
| 1028 | 1166 | } |