diff options
21 files changed, 1172 insertions, 174 deletions
diff --git a/core/math/static_raycaster.cpp b/core/math/static_raycaster.cpp new file mode 100644 index 0000000000..da05d49428 --- /dev/null +++ b/core/math/static_raycaster.cpp @@ -0,0 +1,40 @@ +/*************************************************************************/ +/* static_raycaster.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "static_raycaster.h" + +StaticRaycaster *(*StaticRaycaster::create_function)() = nullptr; + +Ref<StaticRaycaster> StaticRaycaster::create() { + if (create_function) { + return Ref<StaticRaycaster>(create_function()); + } + return Ref<StaticRaycaster>(); +} diff --git a/core/math/static_raycaster.h b/core/math/static_raycaster.h new file mode 100644 index 0000000000..3759c788a7 --- /dev/null +++ b/core/math/static_raycaster.h @@ -0,0 +1,111 @@ +/*************************************************************************/ +/* static_raycaster.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef STATIC_RAYCASTER_H +#define STATIC_RAYCASTER_H + +#include "core/object/ref_counted.h" + +#if !defined(__aligned) + +#if defined(_WIN32) && defined(_MSC_VER) +#define __aligned(...) __declspec(align(__VA_ARGS__)) +#else +#define __aligned(...) __attribute__((aligned(__VA_ARGS__))) +#endif + +#endif + +class StaticRaycaster : public RefCounted { + GDCLASS(StaticRaycaster, RefCounted) +protected: + static StaticRaycaster *(*create_function)(); + +public: + // compatible with embree3 rays + struct __aligned(16) Ray { + const static unsigned int INVALID_GEOMETRY_ID = ((unsigned int)-1); // from rtcore_common.h + + /*! Default construction does nothing. */ + _FORCE_INLINE_ Ray() : + geomID(INVALID_GEOMETRY_ID) {} + + /*! Constructs a ray from origin, direction, and ray segment. Near + * has to be smaller than far. */ + _FORCE_INLINE_ Ray(const Vector3 &org, + const Vector3 &dir, + float tnear = 0.0f, + float tfar = INFINITY) : + org(org), + tnear(tnear), + dir(dir), + time(0.0f), + tfar(tfar), + mask(-1), + u(0.0), + v(0.0), + primID(INVALID_GEOMETRY_ID), + geomID(INVALID_GEOMETRY_ID), + instID(INVALID_GEOMETRY_ID) {} + + /*! Tests if we hit something. */ + _FORCE_INLINE_ explicit operator bool() const { return geomID != INVALID_GEOMETRY_ID; } + + public: + Vector3 org; //!< Ray origin + tnear + float tnear; //!< Start of ray segment + Vector3 dir; //!< Ray direction + tfar + float time; //!< Time of this ray for motion blur. + float tfar; //!< End of ray segment + unsigned int mask; //!< used to mask out objects during traversal + unsigned int id; //!< ray ID + unsigned int flags; //!< ray flags + + Vector3 normal; //!< Not normalized geometry normal + float u; //!< Barycentric u coordinate of hit + float v; //!< Barycentric v coordinate of hit + unsigned int primID; //!< primitive ID + unsigned int geomID; //!< geometry ID + unsigned int instID; //!< instance ID + }; + + virtual bool intersect(Ray &p_ray) = 0; + virtual void intersect(Vector<Ray> &r_rays) = 0; + + virtual void add_mesh(const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices, unsigned int p_id) = 0; + virtual void commit() = 0; + + virtual void set_mesh_filter(const Set<int> &p_mesh_ids) = 0; + virtual void clear_mesh_filter() = 0; + + static Ref<StaticRaycaster> create(); +}; + +#endif // STATIC_RAYCASTER_H diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml index 2eb75d48a3..adc1eab393 100644 --- a/doc/classes/Dictionary.xml +++ b/doc/classes/Dictionary.xml @@ -40,7 +40,7 @@ }; [/csharp] [/codeblocks] - You can access a dictionary's values by referencing the appropriate key. In the above example, [code]points_dir["White"][/code] will return [code]50[/code]. You can also write [code]points_dir.White[/code], which is equivalent. However, you'll have to use the bracket syntax if the key you're accessing the dictionary with isn't a fixed string (such as a number or variable). + You can access a dictionary's values by referencing the appropriate key. In the above example, [code]points_dict["White"][/code] will return [code]50[/code]. You can also write [code]points_dict.White[/code], which is equivalent. However, you'll have to use the bracket syntax if the key you're accessing the dictionary with isn't a fixed string (such as a number or variable). [codeblocks] [gdscript] export(string, "White", "Yellow", "Orange") var my_color @@ -72,7 +72,7 @@ my_dict = {"First Array": [1, 2, 3, 4]} # Assigns an Array to a String key. [/gdscript] [csharp] - var myDir = new Godot.Collections.Dictionary + var myDict = new Godot.Collections.Dictionary { {"First Array", new Godot.Collections.Array{1, 2, 3, 4}} }; @@ -85,7 +85,7 @@ points_dict["Blue"] = 150 # Add "Blue" as a key and assign 150 as its value. [/gdscript] [csharp] - var pointsDir = new Godot.Collections.Dictionary + var pointsDict = new Godot.Collections.Dictionary { {"White", 50}, {"Yellow", 75}, @@ -98,7 +98,7 @@ [codeblocks] [gdscript] # This is a valid dictionary. - # To access the string "Nested value" below, use `my_dir.sub_dir.sub_key` or `my_dir["sub_dir"]["sub_key"]`. + # To access the string "Nested value" below, use `my_dict.sub_dict.sub_key` or `my_dict["sub_dict"]["sub_key"]`. # Indexing styles can be mixed and matched depending on your needs. var my_dict = { "String Key": 5, @@ -109,8 +109,7 @@ [/gdscript] [csharp] // This is a valid dictionary. - // To access the string "Nested value" below, use `my_dir.sub_dir.sub_key` or `my_dir["sub_dir"]["sub_key"]`. - // Indexing styles can be mixed and matched depending on your needs. + // To access the string "Nested value" below, use `((Godot.Collections.Dictionary)myDict["sub_dict"])["sub_key"]`. var myDict = new Godot.Collections.Dictionary { {"String Key", 5}, {4, new Godot.Collections.Array{1,2,3}}, diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index c48d9bb117..2c9bc7dadf 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -980,6 +980,8 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/shadow_meshes", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lightmap_uv", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lods", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_split_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 25.0f)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_merge_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 60.0f)); } break; case INTERNAL_IMPORT_CATEGORY_MATERIAL: { r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "use_external/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); @@ -1259,6 +1261,8 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m //do mesh processing bool generate_lods = p_generate_lods; + float split_angle = 25.0f; + float merge_angle = 60.0f; bool create_shadow_meshes = p_create_shadow_meshes; bool bake_lightmaps = p_light_bake_mode == LIGHT_BAKE_STATIC_LIGHTMAPS; String save_to_file; @@ -1301,6 +1305,14 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m } } + if (mesh_settings.has("lods/normal_split_angle")) { + split_angle = mesh_settings["lods/normal_split_angle"]; + } + + if (mesh_settings.has("lods/normal_merge_angle")) { + merge_angle = mesh_settings["lods/normal_merge_angle"]; + } + if (mesh_settings.has("save_to_file/enabled") && bool(mesh_settings["save_to_file/enabled"]) && mesh_settings.has("save_to_file/path")) { save_to_file = mesh_settings["save_to_file/path"]; if (!save_to_file.is_resource_file()) { @@ -1310,8 +1322,9 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m } if (generate_lods) { - src_mesh_node->get_mesh()->generate_lods(); + src_mesh_node->get_mesh()->generate_lods(merge_angle, split_angle); } + if (create_shadow_meshes) { src_mesh_node->get_mesh()->create_shadow_mesh(); } diff --git a/editor/import/scene_importer_mesh.cpp b/editor/import/scene_importer_mesh.cpp index 5e6dd08e79..f83a7d046a 100644 --- a/editor/import/scene_importer_mesh.cpp +++ b/editor/import/scene_importer_mesh.cpp @@ -30,11 +30,99 @@ #include "scene_importer_mesh.h" -#include "core/math/math_defs.h" +#include "core/math/random_pcg.h" +#include "core/math/static_raycaster.h" #include "scene/resources/surface_tool.h" #include <cstdint> +void EditorSceneImporterMesh::Surface::split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals) { + ERR_FAIL_COND(arrays.size() != RS::ARRAY_MAX); + + const PackedVector3Array &vertices = arrays[RS::ARRAY_VERTEX]; + int current_vertex_count = vertices.size(); + int new_vertex_count = p_indices.size(); + int final_vertex_count = current_vertex_count + new_vertex_count; + const int *indices_ptr = p_indices.ptr(); + + for (int i = 0; i < arrays.size(); i++) { + if (i == RS::ARRAY_INDEX) { + continue; + } + + if (arrays[i].get_type() == Variant::NIL) { + continue; + } + + switch (arrays[i].get_type()) { + case Variant::PACKED_VECTOR3_ARRAY: { + PackedVector3Array data = arrays[i]; + data.resize(final_vertex_count); + Vector3 *data_ptr = data.ptrw(); + if (i == RS::ARRAY_NORMAL) { + const Vector3 *normals_ptr = p_normals.ptr(); + memcpy(&data_ptr[current_vertex_count], normals_ptr, sizeof(Vector3) * new_vertex_count); + } else { + for (int j = 0; j < new_vertex_count; j++) { + data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; + } + } + arrays[i] = data; + } break; + case Variant::PACKED_VECTOR2_ARRAY: { + PackedVector2Array data = arrays[i]; + data.resize(final_vertex_count); + Vector2 *data_ptr = data.ptrw(); + for (int j = 0; j < new_vertex_count; j++) { + data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; + } + arrays[i] = data; + } break; + case Variant::PACKED_FLOAT32_ARRAY: { + PackedFloat32Array data = arrays[i]; + int elements = data.size() / current_vertex_count; + data.resize(final_vertex_count * elements); + float *data_ptr = data.ptrw(); + for (int j = 0; j < new_vertex_count; j++) { + memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(float) * elements); + } + arrays[i] = data; + } break; + case Variant::PACKED_INT32_ARRAY: { + PackedInt32Array data = arrays[i]; + int elements = data.size() / current_vertex_count; + data.resize(final_vertex_count * elements); + int32_t *data_ptr = data.ptrw(); + for (int j = 0; j < new_vertex_count; j++) { + memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(int32_t) * elements); + } + arrays[i] = data; + } break; + case Variant::PACKED_BYTE_ARRAY: { + PackedByteArray data = arrays[i]; + int elements = data.size() / current_vertex_count; + data.resize(final_vertex_count * elements); + uint8_t *data_ptr = data.ptrw(); + for (int j = 0; j < new_vertex_count; j++) { + memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(uint8_t) * elements); + } + arrays[i] = data; + } break; + case Variant::PACKED_COLOR_ARRAY: { + PackedColorArray data = arrays[i]; + data.resize(final_vertex_count); + Color *data_ptr = data.ptrw(); + for (int j = 0; j < new_vertex_count; j++) { + data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; + } + } break; + default: { + ERR_FAIL_MSG("Uhandled array type."); + } break; + } + } +} + void EditorSceneImporterMesh::add_blend_shape(const String &p_name) { ERR_FAIL_COND(surfaces.size() > 0); blend_shapes.push_back(p_name); @@ -157,29 +245,14 @@ void EditorSceneImporterMesh::set_surface_material(int p_surface, const Ref<Mate mesh.unref(); } -Basis EditorSceneImporterMesh::compute_rotation_matrix_from_ortho_6d(Vector3 p_x_raw, Vector3 p_y_raw) { - Vector3 x = p_x_raw.normalized(); - Vector3 z = x.cross(p_y_raw); - z = z.normalized(); - Vector3 y = z.cross(x); - Basis basis; - basis.set_axis(Vector3::AXIS_X, x); - basis.set_axis(Vector3::AXIS_Y, y); - basis.set_axis(Vector3::AXIS_Z, z); - return basis; -} - -void EditorSceneImporterMesh::generate_lods() { - if (!SurfaceTool::simplify_func) { - return; - } +void EditorSceneImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle) { if (!SurfaceTool::simplify_scale_func) { return; } - if (!SurfaceTool::simplify_sloppy_func) { + if (!SurfaceTool::simplify_with_attrib_func) { return; } - if (!SurfaceTool::simplify_with_attrib_func) { + if (!SurfaceTool::optimize_vertex_cache_func) { return; } @@ -190,67 +263,343 @@ void EditorSceneImporterMesh::generate_lods() { surfaces.write[i].lods.clear(); Vector<Vector3> vertices = surfaces[i].arrays[RS::ARRAY_VERTEX]; - Vector<int> indices = surfaces[i].arrays[RS::ARRAY_INDEX]; - if (indices.size() == 0) { + PackedInt32Array indices = surfaces[i].arrays[RS::ARRAY_INDEX]; + Vector<Vector3> normals = surfaces[i].arrays[RS::ARRAY_NORMAL]; + Vector<Vector2> uvs = surfaces[i].arrays[RS::ARRAY_TEX_UV]; + + unsigned int index_count = indices.size(); + unsigned int vertex_count = vertices.size(); + + if (index_count == 0) { continue; //no lods if no indices } - Vector<Vector3> normals = surfaces[i].arrays[RS::ARRAY_NORMAL]; - uint32_t vertex_count = vertices.size(); + const Vector3 *vertices_ptr = vertices.ptr(); - Vector<float> attributes; - Vector<float> normal_weights; - int32_t attribute_count = 6; - if (normals.size()) { - attributes.resize(normals.size() * attribute_count); - for (int32_t normal_i = 0; normal_i < normals.size(); normal_i++) { - Basis basis; - basis.set_euler(normals[normal_i]); - Vector3 basis_x = basis.get_axis(0); - Vector3 basis_y = basis.get_axis(1); - basis = compute_rotation_matrix_from_ortho_6d(basis_x, basis_y); - basis_x = basis.get_axis(0); - basis_y = basis.get_axis(1); - attributes.write[normal_i * attribute_count + 0] = basis_x.x; - attributes.write[normal_i * attribute_count + 1] = basis_x.y; - attributes.write[normal_i * attribute_count + 2] = basis_x.z; - attributes.write[normal_i * attribute_count + 3] = basis_y.x; - attributes.write[normal_i * attribute_count + 4] = basis_y.y; - attributes.write[normal_i * attribute_count + 5] = basis_y.z; - } - normal_weights.resize(vertex_count); - for (int32_t weight_i = 0; weight_i < normal_weights.size(); weight_i++) { - normal_weights.write[weight_i] = 1.0; + const int *indices_ptr = indices.ptr(); + + if (normals.is_empty()) { + normals.resize(vertices.size()); + Vector3 *n_ptr = normals.ptrw(); + for (unsigned int j = 0; j < index_count; j += 3) { + const Vector3 &v0 = vertices_ptr[indices_ptr[j + 0]]; + const Vector3 &v1 = vertices_ptr[indices_ptr[j + 1]]; + const Vector3 &v2 = vertices_ptr[indices_ptr[j + 2]]; + Vector3 n = vec3_cross(v0 - v2, v0 - v1).normalized(); + n_ptr[j + 0] = n; + n_ptr[j + 1] = n; + n_ptr[j + 2] = n; } - } else { - attribute_count = 0; } - const int min_indices = 10; - const float error_tolerance = 1.44224'95703; // Cube root of 3 - const float threshold = 1.0 / error_tolerance; - int index_target = indices.size() * threshold; - float max_mesh_error_percentage = 1e0f; + + float normal_merge_threshold = Math::cos(Math::deg2rad(p_normal_merge_angle)); + float normal_pre_split_threshold = Math::cos(Math::deg2rad(MIN(180.0f, p_normal_split_angle * 2.0f))); + float normal_split_threshold = Math::cos(Math::deg2rad(p_normal_split_angle)); + const Vector3 *normals_ptr = normals.ptr(); + + Map<Vector3, LocalVector<Pair<int, int>>> unique_vertices; + + LocalVector<int> vertex_remap; + LocalVector<int> vertex_inverse_remap; + LocalVector<Vector3> merged_vertices; + LocalVector<Vector3> merged_normals; + LocalVector<int> merged_normals_counts; + const Vector2 *uvs_ptr = uvs.ptr(); + + for (unsigned int j = 0; j < vertex_count; j++) { + const Vector3 &v = vertices_ptr[j]; + const Vector3 &n = normals_ptr[j]; + + Map<Vector3, LocalVector<Pair<int, int>>>::Element *E = unique_vertices.find(v); + + if (E) { + const LocalVector<Pair<int, int>> &close_verts = E->get(); + + bool found = false; + for (unsigned int k = 0; k < close_verts.size(); k++) { + const Pair<int, int> &idx = close_verts[k]; + + // TODO check more attributes? + if ((!uvs_ptr || uvs_ptr[j].distance_squared_to(uvs_ptr[idx.second]) < CMP_EPSILON2) && normals[idx.second].dot(n) > normal_merge_threshold) { + vertex_remap.push_back(idx.first); + merged_normals[idx.first] += normals[idx.second]; + merged_normals_counts[idx.first]++; + found = true; + break; + } + } + + if (!found) { + int vcount = merged_vertices.size(); + unique_vertices[v].push_back(Pair<int, int>(vcount, j)); + vertex_inverse_remap.push_back(j); + merged_vertices.push_back(v); + vertex_remap.push_back(vcount); + merged_normals.push_back(normals_ptr[j]); + merged_normals_counts.push_back(1); + } + } else { + int vcount = merged_vertices.size(); + unique_vertices[v] = LocalVector<Pair<int, int>>(); + unique_vertices[v].push_back(Pair<int, int>(vcount, j)); + vertex_inverse_remap.push_back(j); + merged_vertices.push_back(v); + vertex_remap.push_back(vcount); + merged_normals.push_back(normals_ptr[j]); + merged_normals_counts.push_back(1); + } + } + + LocalVector<int> merged_indices; + merged_indices.resize(index_count); + for (unsigned int j = 0; j < index_count; j++) { + merged_indices[j] = vertex_remap[indices[j]]; + } + + unsigned int merged_vertex_count = merged_vertices.size(); + const Vector3 *merged_vertices_ptr = merged_vertices.ptr(); + const int32_t *merged_indices_ptr = merged_indices.ptr(); + + { + const int *counts_ptr = merged_normals_counts.ptr(); + Vector3 *merged_normals_ptrw = merged_normals.ptr(); + for (unsigned int j = 0; j < merged_vertex_count; j++) { + merged_normals_ptrw[j] /= counts_ptr[j]; + } + } + + LocalVector<float> normal_weights; + normal_weights.resize(merged_vertex_count); + for (unsigned int j = 0; j < merged_vertex_count; j++) { + normal_weights[j] = 2.0; // Give some weight to normal preservation, may be worth exposing as an import setting + } + + const float max_mesh_error = FLT_MAX; // We don't want to limit by error, just by index target + float scale = SurfaceTool::simplify_scale_func((const float *)merged_vertices_ptr, merged_vertex_count, sizeof(Vector3)); float mesh_error = 0.0f; - float scale = SurfaceTool::simplify_scale_func((const float *)vertices_ptr, vertex_count, sizeof(Vector3)); - while (index_target > min_indices) { - Vector<int> new_indices; - new_indices.resize(indices.size()); - size_t new_len = SurfaceTool::simplify_with_attrib_func((unsigned int *)new_indices.ptrw(), (const unsigned int *)indices.ptr(), indices.size(), (const float *)vertices_ptr, vertex_count, sizeof(Vector3), index_target, max_mesh_error_percentage, &mesh_error, (float *)attributes.ptrw(), normal_weights.ptrw(), attribute_count); - if ((int)new_len > (index_target * error_tolerance)) { - break; + + unsigned int index_target = 12; // Start with the smallest target, 4 triangles + unsigned int last_index_count = 0; + + int split_vertex_count = vertex_count; + LocalVector<Vector3> split_vertex_normals; + LocalVector<int> split_vertex_indices; + split_vertex_normals.reserve(index_count / 3); + split_vertex_indices.reserve(index_count / 3); + + RandomPCG pcg; + pcg.seed(123456789); // Keep seed constant across imports + + Ref<StaticRaycaster> raycaster = StaticRaycaster::create(); + if (raycaster.is_valid()) { + raycaster->add_mesh(vertices, indices, 0); + raycaster->commit(); + } + + while (index_target < index_count) { + PackedInt32Array new_indices; + new_indices.resize(index_count); + + size_t new_index_count = SurfaceTool::simplify_with_attrib_func((unsigned int *)new_indices.ptrw(), (const uint32_t *)merged_indices_ptr, index_count, (const float *)merged_vertices_ptr, merged_vertex_count, sizeof(Vector3), index_target, max_mesh_error, &mesh_error, (float *)merged_normals.ptr(), normal_weights.ptr(), 3); + + if (new_index_count < last_index_count * 1.5f) { + index_target = index_target * 1.5f; + continue; } - Surface::LOD lod; - lod.distance = mesh_error * scale; - if (Math::is_zero_approx(mesh_error)) { + + if (new_index_count <= 0 || (new_index_count >= (index_count * 0.75f))) { break; } - if (new_len <= 0) { - break; + + new_indices.resize(new_index_count); + + LocalVector<LocalVector<int>> vertex_corners; + vertex_corners.resize(vertex_count); + { + int *ptrw = new_indices.ptrw(); + for (unsigned int j = 0; j < new_index_count; j++) { + const int &remapped = vertex_inverse_remap[ptrw[j]]; + vertex_corners[remapped].push_back(j); + ptrw[j] = remapped; + } } - new_indices.resize(new_len); + + if (raycaster.is_valid()) { + float error_factor = 1.0f / (scale * MAX(mesh_error, 0.15)); + const float ray_bias = 0.05; + float ray_length = ray_bias + mesh_error * scale * 3.0f; + + Vector<StaticRaycaster::Ray> rays; + LocalVector<Vector2> ray_uvs; + + int32_t *new_indices_ptr = new_indices.ptrw(); + + int current_ray_count = 0; + for (unsigned int j = 0; j < new_index_count; j += 3) { + const Vector3 &v0 = vertices_ptr[new_indices_ptr[j + 0]]; + const Vector3 &v1 = vertices_ptr[new_indices_ptr[j + 1]]; + const Vector3 &v2 = vertices_ptr[new_indices_ptr[j + 2]]; + Vector3 face_normal = vec3_cross(v0 - v2, v0 - v1); + float face_area = face_normal.length(); // Actually twice the face area, since it's the same error_factor on all faces, we don't care + + Vector3 dir = face_normal / face_area; + int ray_count = CLAMP(5.0 * face_area * error_factor, 16, 64); + + rays.resize(current_ray_count + ray_count); + StaticRaycaster::Ray *rays_ptr = rays.ptrw(); + + ray_uvs.resize(current_ray_count + ray_count); + Vector2 *ray_uvs_ptr = ray_uvs.ptr(); + + for (int k = 0; k < ray_count; k++) { + float u = pcg.randf(); + float v = pcg.randf(); + + if (u + v >= 1.0f) { + u = 1.0f - u; + v = 1.0f - v; + } + + u = 0.9f * u + 0.05f / 3.0f; // Give barycentric coordinates some padding, we don't want to sample right on the edge + v = 0.9f * v + 0.05f / 3.0f; // v = (v - one_third) * 0.95f + one_third; + float w = 1.0f - u - v; + + Vector3 org = v0 * w + v1 * u + v2 * v; + org -= dir * ray_bias; + rays_ptr[current_ray_count + k] = StaticRaycaster::Ray(org, dir, 0.0f, ray_length); + rays_ptr[current_ray_count + k].id = j / 3; + ray_uvs_ptr[current_ray_count + k] = Vector2(u, v); + } + + current_ray_count += ray_count; + } + + raycaster->intersect(rays); + + LocalVector<Vector3> ray_normals; + LocalVector<float> ray_normal_weights; + + ray_normals.resize(new_index_count); + ray_normal_weights.resize(new_index_count); + + for (unsigned int j = 0; j < new_index_count; j++) { + ray_normal_weights[j] = 0.0f; + } + + const StaticRaycaster::Ray *rp = rays.ptr(); + for (int j = 0; j < rays.size(); j++) { + if (rp[j].geomID != 0) { // Ray missed + continue; + } + + if (rp[j].normal.normalized().dot(rp[j].dir) > 0.0f) { // Hit a back face. + continue; + } + + const float &u = rp[j].u; + const float &v = rp[j].v; + const float w = 1.0f - u - v; + + const unsigned int &hit_tri_id = rp[j].primID; + const unsigned int &orig_tri_id = rp[j].id; + + const Vector3 &n0 = normals_ptr[indices_ptr[hit_tri_id * 3 + 0]]; + const Vector3 &n1 = normals_ptr[indices_ptr[hit_tri_id * 3 + 1]]; + const Vector3 &n2 = normals_ptr[indices_ptr[hit_tri_id * 3 + 2]]; + Vector3 normal = n0 * w + n1 * u + n2 * v; + + Vector2 orig_uv = ray_uvs[j]; + float orig_bary[3] = { 1.0f - orig_uv.x - orig_uv.y, orig_uv.x, orig_uv.y }; + for (int k = 0; k < 3; k++) { + int idx = orig_tri_id * 3 + k; + float weight = orig_bary[k]; + ray_normals[idx] += normal * weight; + ray_normal_weights[idx] += weight; + } + } + + for (unsigned int j = 0; j < new_index_count; j++) { + if (ray_normal_weights[j] < 1.0f) { // Not enough data, the new normal would be just a bad guess + ray_normals[j] = Vector3(); + } else { + ray_normals[j] /= ray_normal_weights[j]; + } + } + + LocalVector<LocalVector<int>> normal_group_indices; + LocalVector<Vector3> normal_group_averages; + normal_group_indices.reserve(24); + normal_group_averages.reserve(24); + + for (unsigned int j = 0; j < vertex_count; j++) { + const LocalVector<int> &corners = vertex_corners[j]; + const Vector3 &vertex_normal = normals_ptr[j]; + + for (unsigned int k = 0; k < corners.size(); k++) { + const int &corner_idx = corners[k]; + const Vector3 &ray_normal = ray_normals[corner_idx]; + + if (ray_normal.length_squared() < CMP_EPSILON2) { + continue; + } + + bool found = false; + for (unsigned int l = 0; l < normal_group_indices.size(); l++) { + LocalVector<int> &group_indices = normal_group_indices[l]; + Vector3 n = normal_group_averages[l] / group_indices.size(); + if (n.dot(ray_normal) > normal_pre_split_threshold) { + found = true; + group_indices.push_back(corner_idx); + normal_group_averages[l] += ray_normal; + break; + } + } + + if (!found) { + LocalVector<int> new_group; + new_group.push_back(corner_idx); + normal_group_indices.push_back(new_group); + normal_group_averages.push_back(ray_normal); + } + } + + for (unsigned int k = 0; k < normal_group_indices.size(); k++) { + LocalVector<int> &group_indices = normal_group_indices[k]; + Vector3 n = normal_group_averages[k] / group_indices.size(); + + if (vertex_normal.dot(n) < normal_split_threshold) { + split_vertex_indices.push_back(j); + split_vertex_normals.push_back(n); + int new_idx = split_vertex_count++; + for (unsigned int l = 0; l < group_indices.size(); l++) { + new_indices_ptr[group_indices[l]] = new_idx; + } + } + } + + normal_group_indices.clear(); + normal_group_averages.clear(); + } + } + + Surface::LOD lod; + lod.distance = MAX(mesh_error * scale, CMP_EPSILON2); lod.indices = new_indices; - print_line("Lod " + itos(surfaces.write[i].lods.size()) + " begin with " + itos(indices.size() / 3) + " triangles and shoot for " + itos(index_target / 3) + " triangles. Got " + itos(new_len / 3) + " triangles. Lod screen ratio " + rtos(lod.distance)); surfaces.write[i].lods.push_back(lod); - index_target *= threshold; + index_target = MAX(new_index_count, index_target) * 2; + last_index_count = new_index_count; + + if (mesh_error == 0.0f) { + break; + } + } + + surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals); + surfaces.write[i].lods.sort_custom<Surface::LODComparator>(); + + for (int j = 0; j < surfaces.write[i].lods.size(); j++) { + Surface::LOD &lod = surfaces.write[i].lods.write[j]; + unsigned int *lod_indices_ptr = (unsigned int *)lod.indices.ptrw(); + SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), split_vertex_count); } } } @@ -347,7 +696,7 @@ void EditorSceneImporterMesh::create_shadow_mesh() { Map<Vector3, int> unique_vertices; const Vector3 *vptr = vertices.ptr(); for (int j = 0; j < vertex_count; j++) { - Vector3 v = vptr[j]; + const Vector3 &v = vptr[j]; Map<Vector3, int>::Element *E = unique_vertices.find(v); @@ -397,9 +746,9 @@ void EditorSceneImporterMesh::create_shadow_mesh() { index_wptr = new_indices.ptrw(); for (int k = 0; k < index_count; k++) { - int index = index_rptr[j]; + int index = index_rptr[k]; ERR_FAIL_INDEX(index, vertex_count); - index_wptr[j] = vertex_remap[index]; + index_wptr[k] = vertex_remap[index]; } lods[surfaces[i].lods[j].distance] = new_indices; @@ -436,9 +785,9 @@ void EditorSceneImporterMesh::_set_data(const Dictionary &p_data) { if (s.has("lods")) { lods = s["lods"]; } - Array blend_shapes; - if (s.has("blend_shapes")) { - blend_shapes = s["blend_shapes"]; + Array b_shapes; + if (s.has("b_shapes")) { + b_shapes = s["b_shapes"]; } Ref<Material> material; if (s.has("material")) { @@ -448,7 +797,7 @@ void EditorSceneImporterMesh::_set_data(const Dictionary &p_data) { if (s.has("flags")) { flags = s["flags"]; } - add_surface(prim, arr, blend_shapes, lods, material, name, flags); + add_surface(prim, arr, b_shapes, lods, material, name, flags); } } } diff --git a/editor/import/scene_importer_mesh.h b/editor/import/scene_importer_mesh.h index d32b1fdf74..111b191cae 100644 --- a/editor/import/scene_importer_mesh.h +++ b/editor/import/scene_importer_mesh.h @@ -32,6 +32,7 @@ #define EDITOR_SCENE_IMPORTER_MESH_H #include "core/io/resource.h" +#include "core/templates/local_vector.h" #include "scene/resources/concave_polygon_shape_3d.h" #include "scene/resources/convex_polygon_shape_3d.h" #include "scene/resources/mesh.h" @@ -55,12 +56,20 @@ class EditorSceneImporterMesh : public Resource { Vector<BlendShape> blend_shape_data; struct LOD { Vector<int> indices; - float distance; + float distance = 0.0f; }; Vector<LOD> lods; Ref<Material> material; String name; uint32_t flags = 0; + + struct LODComparator { + _FORCE_INLINE_ bool operator()(const LOD &l, const LOD &r) const { + return l.distance < r.distance; + } + }; + + void split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals); }; Vector<Surface> surfaces; Vector<String> blend_shapes; @@ -71,7 +80,6 @@ class EditorSceneImporterMesh : public Resource { Ref<EditorSceneImporterMesh> shadow_mesh; Size2i lightmap_size_hint; - Basis compute_rotation_matrix_from_ortho_6d(Vector3 p_x_raw, Vector3 y_raw); protected: void _set_data(const Dictionary &p_data); @@ -103,7 +111,7 @@ public: void set_surface_material(int p_surface, const Ref<Material> &p_material); - void generate_lods(); + void generate_lods(float p_normal_merge_angle, float p_normal_split_angle); void create_shadow_mesh(); Ref<EditorSceneImporterMesh> get_shadow_mesh() const; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index e5e3be8a4d..857190bc92 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -2601,6 +2601,9 @@ void Node3DEditorViewport::_project_settings_changed() { const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"); viewport->set_use_occlusion_culling(use_occlusion_culling); + + const float lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels"); + viewport->set_lod_threshold(lod_threshold); } void Node3DEditorViewport::_notification(int p_what) { diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 2485e9f432..032e08f5a0 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1867,19 +1867,11 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType left_type; if (p_binary_op->left_operand) { - if (p_binary_op->left_operand->is_constant) { - left_type = type_from_variant(p_binary_op->left_operand->reduced_value, p_binary_op->left_operand); - } else { - left_type = p_binary_op->left_operand->get_datatype(); - } + left_type = p_binary_op->left_operand->get_datatype(); } GDScriptParser::DataType right_type; if (p_binary_op->right_operand) { - if (p_binary_op->right_operand->is_constant) { - right_type = type_from_variant(p_binary_op->right_operand->reduced_value, p_binary_op->right_operand); - } else { - right_type = p_binary_op->right_operand->get_datatype(); - } + right_type = p_binary_op->right_operand->get_datatype(); } if (!left_type.is_set() || !right_type.is_set()) { diff --git a/modules/raycast/SCsub b/modules/raycast/SCsub index 4820cf7608..ef4c598194 100644 --- a/modules/raycast/SCsub +++ b/modules/raycast/SCsub @@ -87,6 +87,20 @@ if env["builtin_embree"]: # Embree needs those, it will automatically use SSE2NEON in ARM env_thirdparty.Append(CPPDEFINES=["__SSE2__", "__SSE__"]) + if not env.msvc: + env_thirdparty.Append( + CPPFLAGS=[ + "-fno-strict-overflow", + "-fno-delete-null-pointer-checks", + "-fwrapv", + "-fsigned-char", + "-fno-strict-aliasing", + "-fno-tree-vectorize", + "-fvisibility=hidden", + "-fvisibility-inlines-hidden", + ] + ) + env.modules_sources += thirdparty_obj diff --git a/modules/raycast/lightmap_raycaster.cpp b/modules/raycast/lightmap_raycaster.cpp index 0583acc119..fdcf509da8 100644 --- a/modules/raycast/lightmap_raycaster.cpp +++ b/modules/raycast/lightmap_raycaster.cpp @@ -168,7 +168,7 @@ void LightmapRaycasterEmbree::clear_mesh_filter() { filter_meshes.clear(); } -void embree_error_handler(void *p_user_data, RTCError p_code, const char *p_str) { +void embree_lm_error_handler(void *p_user_data, RTCError p_code, const char *p_str) { print_error("Embree error: " + String(p_str)); } @@ -179,16 +179,11 @@ LightmapRaycasterEmbree::LightmapRaycasterEmbree() { #endif embree_device = rtcNewDevice(nullptr); - rtcSetDeviceErrorFunction(embree_device, &embree_error_handler, nullptr); + rtcSetDeviceErrorFunction(embree_device, &embree_lm_error_handler, nullptr); embree_scene = rtcNewScene(embree_device); } LightmapRaycasterEmbree::~LightmapRaycasterEmbree() { -#ifdef __SSE2__ - _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF); - _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF); -#endif - if (embree_scene != nullptr) { rtcReleaseScene(embree_scene); } diff --git a/modules/raycast/lightmap_raycaster.h b/modules/raycast/lightmap_raycaster.h index 4c3de27837..290b0a1cf3 100644 --- a/modules/raycast/lightmap_raycaster.h +++ b/modules/raycast/lightmap_raycaster.h @@ -30,9 +30,9 @@ #ifdef TOOLS_ENABLED +#include "core/io/image.h" #include "core/object/object.h" #include "scene/3d/lightmapper.h" -#include "scene/resources/mesh.h" #include <embree3/rtcore.h> diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp index 3975e83797..75491c98e5 100644 --- a/modules/raycast/raycast_occlusion_cull.cpp +++ b/modules/raycast/raycast_occlusion_cull.cpp @@ -41,9 +41,14 @@ RaycastOcclusionCull *RaycastOcclusionCull::raycast_singleton = nullptr; void RaycastOcclusionCull::RaycastHZBuffer::clear() { HZBuffer::clear(); - camera_rays.clear(); + if (camera_rays_unaligned_buffer) { + memfree(camera_rays_unaligned_buffer); + camera_rays_unaligned_buffer = nullptr; + camera_rays = nullptr; + } camera_ray_masks.clear(); - packs_size = Size2i(); + camera_rays_tile_count = 0; + tile_grid_size = Size2i(); } void RaycastOcclusionCull::RaycastHZBuffer::resize(const Size2i &p_size) { @@ -58,10 +63,19 @@ void RaycastOcclusionCull::RaycastHZBuffer::resize(const Size2i &p_size) { HZBuffer::resize(p_size); - packs_size = Size2i(Math::ceil(p_size.x / (float)TILE_SIZE), Math::ceil(p_size.y / (float)TILE_SIZE)); - int ray_packets_count = packs_size.x * packs_size.y; - camera_rays.resize(ray_packets_count); - camera_ray_masks.resize(ray_packets_count * TILE_SIZE * TILE_SIZE); + tile_grid_size = Size2i(Math::ceil(p_size.x / (float)TILE_SIZE), Math::ceil(p_size.y / (float)TILE_SIZE)); + camera_rays_tile_count = tile_grid_size.x * tile_grid_size.y; + + if (camera_rays_unaligned_buffer) { + memfree(camera_rays_unaligned_buffer); + } + + const int alignment = 64; // Embree requires ray packets to be 64-aligned + camera_rays_unaligned_buffer = (uint8_t *)memalloc(camera_rays_tile_count * sizeof(CameraRayTile) + alignment); + camera_rays = (CameraRayTile *)(camera_rays_unaligned_buffer + alignment - (((uint64_t)camera_rays_unaligned_buffer) % alignment)); + + camera_ray_masks.resize(camera_rays_tile_count * TILE_RAYS); + memset(camera_ray_masks.ptr(), ~0, camera_rays_tile_count * TILE_RAYS * sizeof(uint32_t)); } void RaycastOcclusionCull::RaycastHZBuffer::update_camera_rays(const Transform3D &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, ThreadWorkPool &p_thread_work_pool) { @@ -96,65 +110,55 @@ void RaycastOcclusionCull::RaycastHZBuffer::update_camera_rays(const Transform3D } void RaycastOcclusionCull::RaycastHZBuffer::_camera_rays_threaded(uint32_t p_thread, const CameraRayThreadData *p_data) { - uint32_t packs_total = camera_rays.size(); + uint32_t total_tiles = camera_rays_tile_count; uint32_t total_threads = p_data->thread_count; - uint32_t from = p_thread * packs_total / total_threads; - uint32_t to = (p_thread + 1 == total_threads) ? packs_total : ((p_thread + 1) * packs_total / total_threads); + uint32_t from = p_thread * total_tiles / total_threads; + uint32_t to = (p_thread + 1 == total_threads) ? total_tiles : ((p_thread + 1) * total_tiles / total_threads); _generate_camera_rays(p_data, from, to); } void RaycastOcclusionCull::RaycastHZBuffer::_generate_camera_rays(const CameraRayThreadData *p_data, int p_from, int p_to) { const Size2i &buffer_size = sizes[0]; - RayPacket *ray_packets = camera_rays.ptr(); - uint32_t *ray_masks = camera_ray_masks.ptr(); - for (int i = p_from; i < p_to; i++) { - RayPacket &packet = ray_packets[i]; - int tile_x = (i % packs_size.x) * TILE_SIZE; - int tile_y = (i / packs_size.x) * TILE_SIZE; + CameraRayTile &tile = camera_rays[i]; + int tile_x = (i % tile_grid_size.x) * TILE_SIZE; + int tile_y = (i / tile_grid_size.x) * TILE_SIZE; for (int j = 0; j < TILE_RAYS; j++) { int x = tile_x + j % TILE_SIZE; int y = tile_y + j / TILE_SIZE; - if (x >= buffer_size.x || y >= buffer_size.y) { - ray_masks[i * TILE_RAYS + j] = 0U; - continue; - } - - ray_masks[i * TILE_RAYS + j] = ~0U; - float u = (float(x) + 0.5f) / buffer_size.x; float v = (float(y) + 0.5f) / buffer_size.y; Vector3 pixel_pos = p_data->pixel_corner + u * p_data->pixel_u_interp + v * p_data->pixel_v_interp; - packet.ray.tnear[j] = p_data->z_near; + tile.ray.tnear[j] = p_data->z_near; Vector3 dir; if (p_data->camera_orthogonal) { dir = -p_data->camera_dir; - packet.ray.org_x[j] = pixel_pos.x - dir.x * p_data->z_near; - packet.ray.org_y[j] = pixel_pos.y - dir.y * p_data->z_near; - packet.ray.org_z[j] = pixel_pos.z - dir.z * p_data->z_near; + tile.ray.org_x[j] = pixel_pos.x - dir.x * p_data->z_near; + tile.ray.org_y[j] = pixel_pos.y - dir.y * p_data->z_near; + tile.ray.org_z[j] = pixel_pos.z - dir.z * p_data->z_near; } else { dir = (pixel_pos - p_data->camera_pos).normalized(); - packet.ray.org_x[j] = p_data->camera_pos.x; - packet.ray.org_y[j] = p_data->camera_pos.y; - packet.ray.org_z[j] = p_data->camera_pos.z; - packet.ray.tnear[j] /= dir.dot(p_data->camera_dir); + tile.ray.org_x[j] = p_data->camera_pos.x; + tile.ray.org_y[j] = p_data->camera_pos.y; + tile.ray.org_z[j] = p_data->camera_pos.z; + tile.ray.tnear[j] /= dir.dot(p_data->camera_dir); } - packet.ray.dir_x[j] = dir.x; - packet.ray.dir_y[j] = dir.y; - packet.ray.dir_z[j] = dir.z; + tile.ray.dir_x[j] = dir.x; + tile.ray.dir_y[j] = dir.y; + tile.ray.dir_z[j] = dir.z; - packet.ray.tfar[j] = p_data->z_far; - packet.ray.time[j] = 0.0f; + tile.ray.tfar[j] = p_data->z_far; + tile.ray.time[j] = 0.0f; - packet.ray.flags[j] = 0; - packet.ray.mask[j] = -1; - packet.hit.geomID[j] = RTC_INVALID_GEOMETRY_ID; + tile.ray.flags[j] = 0; + tile.ray.mask[j] = ~0U; + tile.hit.geomID[j] = RTC_INVALID_GEOMETRY_ID; } } } @@ -163,8 +167,8 @@ void RaycastOcclusionCull::RaycastHZBuffer::sort_rays(const Vector3 &p_camera_di ERR_FAIL_COND(is_empty()); Size2i buffer_size = sizes[0]; - for (int i = 0; i < packs_size.y; i++) { - for (int j = 0; j < packs_size.x; j++) { + for (int i = 0; i < tile_grid_size.y; i++) { + for (int j = 0; j < tile_grid_size.x; j++) { for (int tile_i = 0; tile_i < TILE_SIZE; tile_i++) { for (int tile_j = 0; tile_j < TILE_SIZE; tile_j++) { int x = j * TILE_SIZE + tile_j; @@ -173,13 +177,13 @@ void RaycastOcclusionCull::RaycastHZBuffer::sort_rays(const Vector3 &p_camera_di continue; } int k = tile_i * TILE_SIZE + tile_j; - int packet_index = i * packs_size.x + j; - float d = camera_rays[packet_index].ray.tfar[k]; + int tile_index = i * tile_grid_size.x + j; + float d = camera_rays[tile_index].ray.tfar[k]; if (!p_orthogonal) { - const float &dir_x = camera_rays[packet_index].ray.dir_x[k]; - const float &dir_y = camera_rays[packet_index].ray.dir_y[k]; - const float &dir_z = camera_rays[packet_index].ray.dir_z[k]; + const float &dir_x = camera_rays[tile_index].ray.dir_x[k]; + const float &dir_y = camera_rays[tile_index].ray.dir_y[k]; + const float &dir_z = camera_rays[tile_index].ray.dir_z[k]; float cos_theta = p_camera_dir.x * dir_x + p_camera_dir.y * dir_y + p_camera_dir.z * dir_z; d *= cos_theta; } @@ -191,6 +195,12 @@ void RaycastOcclusionCull::RaycastHZBuffer::sort_rays(const Vector3 &p_camera_di } } +RaycastOcclusionCull::RaycastHZBuffer::~RaycastHZBuffer() { + if (camera_rays_unaligned_buffer) { + memfree(camera_rays_unaligned_buffer); + } +} + //////////////////////////////////////////////////////// bool RaycastOcclusionCull::is_occluder(RID p_rid) { @@ -474,7 +484,7 @@ void RaycastOcclusionCull::Scenario::_raycast(uint32_t p_idx, const RaycastThrea rtcIntersect16((const int *)&p_raycast_data->masks[p_idx * TILE_RAYS], ebr_scene[current_scene_idx], &ctx, &p_raycast_data->rays[p_idx]); } -void RaycastOcclusionCull::Scenario::raycast(LocalVector<RayPacket> &r_rays, const LocalVector<uint32_t> p_valid_masks, ThreadWorkPool &p_thread_pool) const { +void RaycastOcclusionCull::Scenario::raycast(CameraRayTile *r_rays, const uint32_t *p_valid_masks, uint32_t p_tile_count, ThreadWorkPool &p_thread_pool) const { ERR_FAIL_COND(singleton == nullptr); if (raycast_singleton->ebr_device == nullptr) { return; // Embree is initialized on demand when there is some scenario with occluders in it. @@ -485,10 +495,10 @@ void RaycastOcclusionCull::Scenario::raycast(LocalVector<RayPacket> &r_rays, con } RaycastThreadData td; - td.rays = r_rays.ptr(); - td.masks = p_valid_masks.ptr(); + td.rays = r_rays; + td.masks = p_valid_masks; - p_thread_pool.do_work(r_rays.size(), this, &Scenario::_raycast, &td); + p_thread_pool.do_work(p_tile_count, this, &Scenario::_raycast, &td); } //////////////////////////////////////////////////////// @@ -536,7 +546,7 @@ void RaycastOcclusionCull::buffer_update(RID p_buffer, const Transform3D &p_cam_ buffer.update_camera_rays(p_cam_transform, p_cam_projection, p_cam_orthogonal, p_thread_pool); - scenario.raycast(buffer.camera_rays, buffer.camera_ray_masks, p_thread_pool); + scenario.raycast(buffer.camera_rays, buffer.camera_ray_masks.ptr(), buffer.camera_rays_tile_count, p_thread_pool); buffer.sort_rays(-p_cam_transform.basis.get_axis(2), p_cam_orthogonal); buffer.update_mips(); } @@ -592,13 +602,15 @@ RaycastOcclusionCull::~RaycastOcclusionCull() { scenario.commit_thread->wait_to_finish(); memdelete(scenario.commit_thread); } + + for (int i = 0; i < 2; i++) { + if (scenario.ebr_scene[i]) { + rtcReleaseScene(scenario.ebr_scene[i]); + } + } } if (ebr_device != nullptr) { -#ifdef __SSE2__ - _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF); - _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF); -#endif rtcReleaseDevice(ebr_device); } diff --git a/modules/raycast/raycast_occlusion_cull.h b/modules/raycast/raycast_occlusion_cull.h index cc87a6342c..ea96df5ff6 100644 --- a/modules/raycast/raycast_occlusion_cull.h +++ b/modules/raycast/raycast_occlusion_cull.h @@ -43,12 +43,12 @@ #include <embree3/rtcore.h> class RaycastOcclusionCull : public RendererSceneOcclusionCull { - typedef RTCRayHit16 RayPacket; + typedef RTCRayHit16 CameraRayTile; public: class RaycastHZBuffer : public HZBuffer { private: - Size2i packs_size; + Size2i tile_grid_size; struct CameraRayThreadData { int thread_count; @@ -67,7 +67,9 @@ public: void _generate_camera_rays(const CameraRayThreadData *p_data, int p_from, int p_to); public: - LocalVector<RayPacket> camera_rays; + unsigned int camera_rays_tile_count = 0; + uint8_t *camera_rays_unaligned_buffer = nullptr; + CameraRayTile *camera_rays = nullptr; LocalVector<uint32_t> camera_ray_masks; RID scenario_rid; @@ -75,6 +77,8 @@ public: virtual void resize(const Size2i &p_size) override; void sort_rays(const Vector3 &p_camera_dir, bool p_orthogonal); void update_camera_rays(const Transform3D &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, ThreadWorkPool &p_thread_work_pool); + + ~RaycastHZBuffer(); }; private: @@ -111,7 +115,7 @@ private: struct Scenario { struct RaycastThreadData { - RayPacket *rays; + CameraRayTile *rays; const uint32_t *masks; }; @@ -144,7 +148,7 @@ private: bool update(ThreadWorkPool &p_thread_pool); void _raycast(uint32_t p_thread, const RaycastThreadData *p_raycast_data) const; - void raycast(LocalVector<RayPacket> &r_rays, const LocalVector<uint32_t> p_valid_masks, ThreadWorkPool &p_thread_pool) const; + void raycast(CameraRayTile *r_rays, const uint32_t *p_valid_masks, uint32_t p_tile_count, ThreadWorkPool &p_thread_pool) const; }; static RaycastOcclusionCull *raycast_singleton; diff --git a/modules/raycast/register_types.cpp b/modules/raycast/register_types.cpp index 78ca91309f..ed99e635e1 100644 --- a/modules/raycast/register_types.cpp +++ b/modules/raycast/register_types.cpp @@ -32,12 +32,14 @@ #include "lightmap_raycaster.h" #include "raycast_occlusion_cull.h" +#include "static_raycaster.h" RaycastOcclusionCull *raycast_occlusion_cull = nullptr; void register_raycast_types() { #ifdef TOOLS_ENABLED LightmapRaycasterEmbree::make_default_raycaster(); + StaticRaycasterEmbree::make_default_raycaster(); #endif raycast_occlusion_cull = memnew(RaycastOcclusionCull); } @@ -46,4 +48,7 @@ void unregister_raycast_types() { if (raycast_occlusion_cull) { memdelete(raycast_occlusion_cull); } +#ifdef TOOLS_ENABLED + StaticRaycasterEmbree::free(); +#endif } diff --git a/modules/raycast/static_raycaster.cpp b/modules/raycast/static_raycaster.cpp new file mode 100644 index 0000000000..2ba65eebf8 --- /dev/null +++ b/modules/raycast/static_raycaster.cpp @@ -0,0 +1,137 @@ +/*************************************************************************/ +/* static_raycaster.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef TOOLS_ENABLED + +#include "static_raycaster.h" + +#ifdef __SSE2__ +#include <pmmintrin.h> +#endif + +RTCDevice StaticRaycasterEmbree::embree_device; + +StaticRaycaster *StaticRaycasterEmbree::create_embree_raycaster() { + return memnew(StaticRaycasterEmbree); +} + +void StaticRaycasterEmbree::make_default_raycaster() { + create_function = create_embree_raycaster; +} + +void StaticRaycasterEmbree::free() { + if (embree_device) { + rtcReleaseDevice(embree_device); + } +} + +bool StaticRaycasterEmbree::intersect(Ray &r_ray) { + RTCIntersectContext context; + rtcInitIntersectContext(&context); + rtcIntersect1(embree_scene, &context, (RTCRayHit *)&r_ray); + return r_ray.geomID != RTC_INVALID_GEOMETRY_ID; +} + +void StaticRaycasterEmbree::intersect(Vector<Ray> &r_rays) { + Ray *rays = r_rays.ptrw(); + for (int i = 0; i < r_rays.size(); ++i) { + intersect(rays[i]); + } +} + +void StaticRaycasterEmbree::add_mesh(const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices, unsigned int p_id) { + RTCGeometry embree_mesh = rtcNewGeometry(embree_device, RTC_GEOMETRY_TYPE_TRIANGLE); + + int vertex_count = p_vertices.size(); + + Vector3 *embree_vertices = (Vector3 *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, sizeof(Vector3), vertex_count); + memcpy(embree_vertices, p_vertices.ptr(), sizeof(Vector3) * vertex_count); + + if (p_indices.is_empty()) { + ERR_FAIL_COND(vertex_count % 3 != 0); + uint32_t *embree_triangles = (uint32_t *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, sizeof(uint32_t) * 3, vertex_count / 3); + for (int i = 0; i < vertex_count; i++) { + embree_triangles[i] = i; + } + } else { + uint32_t *embree_triangles = (uint32_t *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, sizeof(uint32_t) * 3, p_indices.size() / 3); + memcpy(embree_triangles, p_indices.ptr(), sizeof(uint32_t) * p_indices.size()); + } + + rtcCommitGeometry(embree_mesh); + rtcAttachGeometryByID(embree_scene, embree_mesh, p_id); + rtcReleaseGeometry(embree_mesh); +} + +void StaticRaycasterEmbree::commit() { + rtcCommitScene(embree_scene); +} + +void StaticRaycasterEmbree::set_mesh_filter(const Set<int> &p_mesh_ids) { + for (Set<int>::Element *E = p_mesh_ids.front(); E; E = E->next()) { + rtcDisableGeometry(rtcGetGeometry(embree_scene, E->get())); + } + rtcCommitScene(embree_scene); + filter_meshes = p_mesh_ids; +} + +void StaticRaycasterEmbree::clear_mesh_filter() { + for (Set<int>::Element *E = filter_meshes.front(); E; E = E->next()) { + rtcEnableGeometry(rtcGetGeometry(embree_scene, E->get())); + } + rtcCommitScene(embree_scene); + filter_meshes.clear(); +} + +void embree_error_handler(void *p_user_data, RTCError p_code, const char *p_str) { + print_error("Embree error: " + String(p_str)); +} + +StaticRaycasterEmbree::StaticRaycasterEmbree() { +#ifdef __SSE2__ + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); +#endif + + if (!embree_device) { + embree_device = rtcNewDevice(nullptr); + rtcSetDeviceErrorFunction(embree_device, &embree_error_handler, nullptr); + } + + embree_scene = rtcNewScene(embree_device); +} + +StaticRaycasterEmbree::~StaticRaycasterEmbree() { + if (embree_scene != nullptr) { + rtcReleaseScene(embree_scene); + } +} + +#endif diff --git a/modules/raycast/static_raycaster.h b/modules/raycast/static_raycaster.h new file mode 100644 index 0000000000..6b13ecf690 --- /dev/null +++ b/modules/raycast/static_raycaster.h @@ -0,0 +1,64 @@ +/*************************************************************************/ +/* static_raycaster.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef TOOLS_ENABLED + +#include "core/math/static_raycaster.h" + +#include <embree3/rtcore.h> + +class StaticRaycasterEmbree : public StaticRaycaster { + GDCLASS(StaticRaycasterEmbree, StaticRaycaster); + +private: + static RTCDevice embree_device; + RTCScene embree_scene; + + Set<int> filter_meshes; + +public: + virtual bool intersect(Ray &p_ray) override; + virtual void intersect(Vector<Ray> &r_rays) override; + + virtual void add_mesh(const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices, unsigned int p_id) override; + virtual void commit() override; + + virtual void set_mesh_filter(const Set<int> &p_mesh_ids) override; + virtual void clear_mesh_filter() override; + + static StaticRaycaster *create_embree_raycaster(); + static void make_default_raycaster(); + static void free(); + + StaticRaycasterEmbree(); + ~StaticRaycasterEmbree(); +}; + +#endif diff --git a/modules/upnp/doc_classes/UPNP.xml b/modules/upnp/doc_classes/UPNP.xml index 5b1d9dbfd1..2cd0b8843a 100644 --- a/modules/upnp/doc_classes/UPNP.xml +++ b/modules/upnp/doc_classes/UPNP.xml @@ -16,6 +16,38 @@ [codeblock] upnp.delete_port_mapping(port) [/codeblock] + [b]Note:[/b] UPnP discovery blocks the current thread. To perform discovery without blocking the main thread, use [Thread]s like this: + [codeblock] + # Emitted when UPnP port mapping setup is completed (regardless of success or failure). + signal upnp_completed(error) + + # Replace this with your own server port number between 1025 and 65535. + const SERVER_PORT = 3928 + var thread = null + + func _upnp_setup(server_port): + # UPNP queries take some time. + var upnp = UPNP.new() + var err = upnp.discover() + + if err != OK: + push_error(str(err)) + emit_signal("upnp_completed", err) + return + + if upnp.get_gateway() and upnp.get_gateway().is_valid_gateway(): + upnp.add_port_mapping(server_port, server_port, ProjectSettings.get_setting("application/config/name"), "UDP") + upnp.add_port_mapping(server_port, server_port, ProjectSettings.get_setting("application/config/name"), "TCP") + emit_signal("upnp_completed", OK) + + func _ready(): + thread = Thread.new() + thread.start(self, "_upnp_setup", SERVER_PORT) + + func _exit_tree(): + # Wait for thread finish here to handle game exit while the thread is running. + thread.wait_to_finish() + [/codeblock] </description> <tutorials> </tutorials> diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 1b04a6e5e2..579f8abbe6 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -295,7 +295,7 @@ public: AABB aabb; struct LOD { - float edge_length; + float edge_length = 0.0f; Vector<uint8_t> index_data; }; Vector<LOD> lods; diff --git a/thirdparty/README.md b/thirdparty/README.md index 3403a0be31..3d32609088 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -373,7 +373,9 @@ Files extracted from upstream repository: - `LICENSE.md`. An [experimental upstream feature](https://github.com/zeux/meshoptimizer/tree/simplify-attr), -has been backported, see patch in `patches` directory. +has been backported. On top of that, it was modified to report only distance error metrics +instead of a combination of distance and attribute errors. Patches for both changes can be +found in the `patches` directory. ## miniupnpc diff --git a/thirdparty/meshoptimizer/patches/attribute-aware-simplify-distance-only-metric.patch b/thirdparty/meshoptimizer/patches/attribute-aware-simplify-distance-only-metric.patch new file mode 100644 index 0000000000..54132a6c86 --- /dev/null +++ b/thirdparty/meshoptimizer/patches/attribute-aware-simplify-distance-only-metric.patch @@ -0,0 +1,176 @@ +diff --git a/thirdparty/meshoptimizer/simplifier.cpp b/thirdparty/meshoptimizer/simplifier.cpp +index 0f10ebef4b..cf5db4e119 100644 +--- a/thirdparty/meshoptimizer/simplifier.cpp ++++ b/thirdparty/meshoptimizer/simplifier.cpp +@@ -20,7 +20,7 @@ + #define TRACESTATS(i) (void)0 + #endif + +-#define ATTRIBUTES 8 ++#define ATTRIBUTES 3 + + // This work is based on: + // Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997 +@@ -445,6 +445,7 @@ struct Collapse + float error; + unsigned int errorui; + }; ++ float distance_error; + }; + + static float normalize(Vector3& v) +@@ -525,6 +526,34 @@ static float quadricError(const Quadric& Q, const Vector3& v) + return fabsf(r) * s; + } + ++static float quadricErrorNoAttributes(const Quadric& Q, const Vector3& v) ++{ ++ float rx = Q.b0; ++ float ry = Q.b1; ++ float rz = Q.b2; ++ ++ rx += Q.a10 * v.y; ++ ry += Q.a21 * v.z; ++ rz += Q.a20 * v.x; ++ ++ rx *= 2; ++ ry *= 2; ++ rz *= 2; ++ ++ rx += Q.a00 * v.x; ++ ry += Q.a11 * v.y; ++ rz += Q.a22 * v.z; ++ ++ float r = Q.c; ++ r += rx * v.x; ++ r += ry * v.y; ++ r += rz * v.z; ++ ++ float s = Q.w == 0.f ? 0.f : 1.f / Q.w; ++ ++ return fabsf(r) * s; ++} ++ + static void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, float w) + { + float aw = a * w; +@@ -680,7 +709,7 @@ static void quadricUpdateAttributes(Quadric& Q, const Vector3& p0, const Vector3 + } + #endif + +-static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap) ++static void fillFaceQuadrics(Quadric* vertex_quadrics, Quadric* vertex_no_attrib_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap) + { + for (size_t i = 0; i < index_count; i += 3) + { +@@ -690,6 +719,9 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indic + + Quadric Q; + quadricFromTriangle(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], 1.f); ++ quadricAdd(vertex_no_attrib_quadrics[remap[i0]], Q); ++ quadricAdd(vertex_no_attrib_quadrics[remap[i1]], Q); ++ quadricAdd(vertex_no_attrib_quadrics[remap[i2]], Q); + + #if ATTRIBUTES + quadricUpdateAttributes(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], Q.w); +@@ -700,7 +732,7 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indic + } + } + +-static void fillEdgeQuadrics(Quadric* vertex_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap, const unsigned char* vertex_kind, const unsigned int* loop, const unsigned int* loopback) ++static void fillEdgeQuadrics(Quadric* vertex_quadrics, Quadric* vertex_no_attrib_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap, const unsigned char* vertex_kind, const unsigned int* loop, const unsigned int* loopback) + { + for (size_t i = 0; i < index_count; i += 3) + { +@@ -744,6 +776,9 @@ static void fillEdgeQuadrics(Quadric* vertex_quadrics, const unsigned int* indic + + quadricAdd(vertex_quadrics[remap[i0]], Q); + quadricAdd(vertex_quadrics[remap[i1]], Q); ++ ++ quadricAdd(vertex_no_attrib_quadrics[remap[i0]], Q); ++ quadricAdd(vertex_no_attrib_quadrics[remap[i1]], Q); + } + } + } +@@ -848,7 +883,7 @@ static size_t pickEdgeCollapses(Collapse* collapses, const unsigned int* indices + return collapse_count; + } + +-static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const Vector3* vertex_positions, const Quadric* vertex_quadrics, const unsigned int* remap) ++static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const Vector3* vertex_positions, const Quadric* vertex_quadrics, const Quadric* vertex_no_attrib_quadrics, const unsigned int* remap) + { + for (size_t i = 0; i < collapse_count; ++i) + { +@@ -868,10 +903,14 @@ static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const + float ei = quadricError(qi, vertex_positions[i1]); + float ej = quadricError(qj, vertex_positions[j1]); + ++ const Quadric& naqi = vertex_no_attrib_quadrics[remap[i0]]; ++ const Quadric& naqj = vertex_no_attrib_quadrics[remap[j0]]; ++ + // pick edge direction with minimal error + c.v0 = ei <= ej ? i0 : j0; + c.v1 = ei <= ej ? i1 : j1; + c.error = ei <= ej ? ei : ej; ++ c.distance_error = ei <= ej ? quadricErrorNoAttributes(naqi, vertex_positions[i1]) : quadricErrorNoAttributes(naqj, vertex_positions[j1]); + } + } + +@@ -968,7 +1007,7 @@ static void sortEdgeCollapses(unsigned int* sort_order, const Collapse* collapse + } + } + +-static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* collapse_locked, Quadric* vertex_quadrics, const Collapse* collapses, size_t collapse_count, const unsigned int* collapse_order, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_kind, const Vector3* vertex_positions, const EdgeAdjacency& adjacency, size_t triangle_collapse_goal, float error_limit, float& result_error) ++static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* collapse_locked, Quadric* vertex_quadrics, Quadric* vertex_no_attrib_quadrics, const Collapse* collapses, size_t collapse_count, const unsigned int* collapse_order, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_kind, const Vector3* vertex_positions, const EdgeAdjacency& adjacency, size_t triangle_collapse_goal, float error_limit, float& result_error) + { + size_t edge_collapses = 0; + size_t triangle_collapses = 0; +@@ -1030,6 +1069,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* + assert(collapse_remap[r1] == r1); + + quadricAdd(vertex_quadrics[r1], vertex_quadrics[r0]); ++ quadricAdd(vertex_no_attrib_quadrics[r1], vertex_no_attrib_quadrics[r0]); + + if (vertex_kind[i0] == Kind_Complex) + { +@@ -1067,7 +1107,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* + triangle_collapses += (vertex_kind[i0] == Kind_Border) ? 1 : 2; + edge_collapses++; + +- result_error = result_error < c.error ? c.error : result_error; ++ result_error = result_error < c.distance_error ? c.distance_error : result_error; + } + + #if TRACE +@@ -1455,9 +1495,11 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned + + Quadric* vertex_quadrics = allocator.allocate<Quadric>(vertex_count); + memset(vertex_quadrics, 0, vertex_count * sizeof(Quadric)); ++ Quadric* vertex_no_attrib_quadrics = allocator.allocate<Quadric>(vertex_count); ++ memset(vertex_no_attrib_quadrics, 0, vertex_count * sizeof(Quadric)); + +- fillFaceQuadrics(vertex_quadrics, indices, index_count, vertex_positions, remap); +- fillEdgeQuadrics(vertex_quadrics, indices, index_count, vertex_positions, remap, vertex_kind, loop, loopback); ++ fillFaceQuadrics(vertex_quadrics, vertex_no_attrib_quadrics, indices, index_count, vertex_positions, remap); ++ fillEdgeQuadrics(vertex_quadrics, vertex_no_attrib_quadrics, indices, index_count, vertex_positions, remap, vertex_kind, loop, loopback); + + if (result != indices) + memcpy(result, indices, index_count * sizeof(unsigned int)); +@@ -1488,7 +1530,7 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned + if (edge_collapse_count == 0) + break; + +- rankEdgeCollapses(edge_collapses, edge_collapse_count, vertex_positions, vertex_quadrics, remap); ++ rankEdgeCollapses(edge_collapses, edge_collapse_count, vertex_positions, vertex_quadrics, vertex_no_attrib_quadrics, remap); + + #if TRACE > 1 + dumpEdgeCollapses(edge_collapses, edge_collapse_count, vertex_kind); +@@ -1507,7 +1549,7 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned + printf("pass %d: ", int(pass_count++)); + #endif + +- size_t collapses = performEdgeCollapses(collapse_remap, collapse_locked, vertex_quadrics, edge_collapses, edge_collapse_count, collapse_order, remap, wedge, vertex_kind, vertex_positions, adjacency, triangle_collapse_goal, error_limit, result_error); ++ size_t collapses = performEdgeCollapses(collapse_remap, collapse_locked, vertex_quadrics, vertex_no_attrib_quadrics, edge_collapses, edge_collapse_count, collapse_order, remap, wedge, vertex_kind, vertex_positions, adjacency, triangle_collapse_goal, error_limit, result_error); + + // no edges can be collapsed any more due to hitting the error limit or triangle collapse limit + if (collapses == 0) diff --git a/thirdparty/meshoptimizer/simplifier.cpp b/thirdparty/meshoptimizer/simplifier.cpp index 0f10ebef4b..cf5db4e119 100644 --- a/thirdparty/meshoptimizer/simplifier.cpp +++ b/thirdparty/meshoptimizer/simplifier.cpp @@ -20,7 +20,7 @@ #define TRACESTATS(i) (void)0 #endif -#define ATTRIBUTES 8 +#define ATTRIBUTES 3 // This work is based on: // Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997 @@ -445,6 +445,7 @@ struct Collapse float error; unsigned int errorui; }; + float distance_error; }; static float normalize(Vector3& v) @@ -525,6 +526,34 @@ static float quadricError(const Quadric& Q, const Vector3& v) return fabsf(r) * s; } +static float quadricErrorNoAttributes(const Quadric& Q, const Vector3& v) +{ + float rx = Q.b0; + float ry = Q.b1; + float rz = Q.b2; + + rx += Q.a10 * v.y; + ry += Q.a21 * v.z; + rz += Q.a20 * v.x; + + rx *= 2; + ry *= 2; + rz *= 2; + + rx += Q.a00 * v.x; + ry += Q.a11 * v.y; + rz += Q.a22 * v.z; + + float r = Q.c; + r += rx * v.x; + r += ry * v.y; + r += rz * v.z; + + float s = Q.w == 0.f ? 0.f : 1.f / Q.w; + + return fabsf(r) * s; +} + static void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, float w) { float aw = a * w; @@ -680,7 +709,7 @@ static void quadricUpdateAttributes(Quadric& Q, const Vector3& p0, const Vector3 } #endif -static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap) +static void fillFaceQuadrics(Quadric* vertex_quadrics, Quadric* vertex_no_attrib_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap) { for (size_t i = 0; i < index_count; i += 3) { @@ -690,6 +719,9 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indic Quadric Q; quadricFromTriangle(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], 1.f); + quadricAdd(vertex_no_attrib_quadrics[remap[i0]], Q); + quadricAdd(vertex_no_attrib_quadrics[remap[i1]], Q); + quadricAdd(vertex_no_attrib_quadrics[remap[i2]], Q); #if ATTRIBUTES quadricUpdateAttributes(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], Q.w); @@ -700,7 +732,7 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indic } } -static void fillEdgeQuadrics(Quadric* vertex_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap, const unsigned char* vertex_kind, const unsigned int* loop, const unsigned int* loopback) +static void fillEdgeQuadrics(Quadric* vertex_quadrics, Quadric* vertex_no_attrib_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap, const unsigned char* vertex_kind, const unsigned int* loop, const unsigned int* loopback) { for (size_t i = 0; i < index_count; i += 3) { @@ -744,6 +776,9 @@ static void fillEdgeQuadrics(Quadric* vertex_quadrics, const unsigned int* indic quadricAdd(vertex_quadrics[remap[i0]], Q); quadricAdd(vertex_quadrics[remap[i1]], Q); + + quadricAdd(vertex_no_attrib_quadrics[remap[i0]], Q); + quadricAdd(vertex_no_attrib_quadrics[remap[i1]], Q); } } } @@ -848,7 +883,7 @@ static size_t pickEdgeCollapses(Collapse* collapses, const unsigned int* indices return collapse_count; } -static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const Vector3* vertex_positions, const Quadric* vertex_quadrics, const unsigned int* remap) +static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const Vector3* vertex_positions, const Quadric* vertex_quadrics, const Quadric* vertex_no_attrib_quadrics, const unsigned int* remap) { for (size_t i = 0; i < collapse_count; ++i) { @@ -868,10 +903,14 @@ static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const float ei = quadricError(qi, vertex_positions[i1]); float ej = quadricError(qj, vertex_positions[j1]); + const Quadric& naqi = vertex_no_attrib_quadrics[remap[i0]]; + const Quadric& naqj = vertex_no_attrib_quadrics[remap[j0]]; + // pick edge direction with minimal error c.v0 = ei <= ej ? i0 : j0; c.v1 = ei <= ej ? i1 : j1; c.error = ei <= ej ? ei : ej; + c.distance_error = ei <= ej ? quadricErrorNoAttributes(naqi, vertex_positions[i1]) : quadricErrorNoAttributes(naqj, vertex_positions[j1]); } } @@ -968,7 +1007,7 @@ static void sortEdgeCollapses(unsigned int* sort_order, const Collapse* collapse } } -static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* collapse_locked, Quadric* vertex_quadrics, const Collapse* collapses, size_t collapse_count, const unsigned int* collapse_order, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_kind, const Vector3* vertex_positions, const EdgeAdjacency& adjacency, size_t triangle_collapse_goal, float error_limit, float& result_error) +static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* collapse_locked, Quadric* vertex_quadrics, Quadric* vertex_no_attrib_quadrics, const Collapse* collapses, size_t collapse_count, const unsigned int* collapse_order, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_kind, const Vector3* vertex_positions, const EdgeAdjacency& adjacency, size_t triangle_collapse_goal, float error_limit, float& result_error) { size_t edge_collapses = 0; size_t triangle_collapses = 0; @@ -1030,6 +1069,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* assert(collapse_remap[r1] == r1); quadricAdd(vertex_quadrics[r1], vertex_quadrics[r0]); + quadricAdd(vertex_no_attrib_quadrics[r1], vertex_no_attrib_quadrics[r0]); if (vertex_kind[i0] == Kind_Complex) { @@ -1067,7 +1107,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* triangle_collapses += (vertex_kind[i0] == Kind_Border) ? 1 : 2; edge_collapses++; - result_error = result_error < c.error ? c.error : result_error; + result_error = result_error < c.distance_error ? c.distance_error : result_error; } #if TRACE @@ -1455,9 +1495,11 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned Quadric* vertex_quadrics = allocator.allocate<Quadric>(vertex_count); memset(vertex_quadrics, 0, vertex_count * sizeof(Quadric)); + Quadric* vertex_no_attrib_quadrics = allocator.allocate<Quadric>(vertex_count); + memset(vertex_no_attrib_quadrics, 0, vertex_count * sizeof(Quadric)); - fillFaceQuadrics(vertex_quadrics, indices, index_count, vertex_positions, remap); - fillEdgeQuadrics(vertex_quadrics, indices, index_count, vertex_positions, remap, vertex_kind, loop, loopback); + fillFaceQuadrics(vertex_quadrics, vertex_no_attrib_quadrics, indices, index_count, vertex_positions, remap); + fillEdgeQuadrics(vertex_quadrics, vertex_no_attrib_quadrics, indices, index_count, vertex_positions, remap, vertex_kind, loop, loopback); if (result != indices) memcpy(result, indices, index_count * sizeof(unsigned int)); @@ -1488,7 +1530,7 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned if (edge_collapse_count == 0) break; - rankEdgeCollapses(edge_collapses, edge_collapse_count, vertex_positions, vertex_quadrics, remap); + rankEdgeCollapses(edge_collapses, edge_collapse_count, vertex_positions, vertex_quadrics, vertex_no_attrib_quadrics, remap); #if TRACE > 1 dumpEdgeCollapses(edge_collapses, edge_collapse_count, vertex_kind); @@ -1507,7 +1549,7 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned printf("pass %d: ", int(pass_count++)); #endif - size_t collapses = performEdgeCollapses(collapse_remap, collapse_locked, vertex_quadrics, edge_collapses, edge_collapse_count, collapse_order, remap, wedge, vertex_kind, vertex_positions, adjacency, triangle_collapse_goal, error_limit, result_error); + size_t collapses = performEdgeCollapses(collapse_remap, collapse_locked, vertex_quadrics, vertex_no_attrib_quadrics, edge_collapses, edge_collapse_count, collapse_order, remap, wedge, vertex_kind, vertex_positions, adjacency, triangle_collapse_goal, error_limit, result_error); // no edges can be collapsed any more due to hitting the error limit or triangle collapse limit if (collapses == 0) |