summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuan Linietsky <reduzio@gmail.com>2021-09-30 14:49:11 -0300
committerGitHub <noreply@github.com>2021-09-30 14:49:11 -0300
commitc370b4c4d06694401dda71e033286f5383f9e645 (patch)
treeba8e8e644e438c2a64a781e81ba36f84b3f5460c
parentd2b8560d7a871a7bab27e68835f86e69e47e79ad (diff)
parent9e1810695c926286ddfe3e1614a6a54044583ac0 (diff)
Merge pull request #52544 from JFonS/lod_fixes
Auto LOD fixes and improvements
-rw-r--r--core/math/static_raycaster.cpp40
-rw-r--r--core/math/static_raycaster.h111
-rw-r--r--editor/import/resource_importer_scene.cpp15
-rw-r--r--editor/import/scene_importer_mesh.cpp499
-rw-r--r--editor/import/scene_importer_mesh.h14
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp3
-rw-r--r--modules/raycast/lightmap_raycaster.cpp9
-rw-r--r--modules/raycast/lightmap_raycaster.h2
-rw-r--r--modules/raycast/register_types.cpp5
-rw-r--r--modules/raycast/static_raycaster.cpp137
-rw-r--r--modules/raycast/static_raycaster.h64
-rw-r--r--servers/rendering_server.h2
-rw-r--r--thirdparty/README.md4
-rw-r--r--thirdparty/meshoptimizer/patches/attribute-aware-simplify-distance-only-metric.patch176
-rw-r--r--thirdparty/meshoptimizer/simplifier.cpp62
15 files changed, 1044 insertions, 99 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/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/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/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/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)