Browse Source

Merge pull request #52544 from JFonS/lod_fixes

Auto LOD fixes and improvements
Juan Linietsky 3 years ago
parent
commit
c370b4c4d0

+ 40 - 0
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>();
+}

+ 111 - 0
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

+ 14 - 1
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();
 				}

+ 424 - 75
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);
 		}
 	}
 }

+ 11 - 3
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;

+ 3 - 0
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) {

+ 2 - 7
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);
 	}

+ 1 - 1
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>
 

+ 5 - 0
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
 }

+ 137 - 0
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

+ 64 - 0
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

+ 1 - 1
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;

+ 3 - 1
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

+ 176 - 0
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)

+ 52 - 10
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)