Auto LOD fixes and improvements

* Fixed LODs for shadow meshes.
* Added a merging step before simplification. This helps with tesselated
  meshes that were previously left untouched. The angle difference at
  wich edges ar considered "hard" can be tweaked as an import setting.
* LODs will now start with the highest decimation possible and keep
  doubling (approximately) the number of triangles from there. This
  makes sure that very low triangle counts are included when possible.
* Given more weight to normal preservation.
* Modified MeshOptimizer to report distance-based error instead of
  including attributes in the reported metrics.
* Added attribute transference between the original mesh and the
  various LODs. Right now only normals are taken into account,
  but it could be expanded to other attributes in the future.
This commit is contained in:
jfons 2021-09-07 17:44:50 +02:00
parent 10801b90f9
commit 9e1810695c
15 changed files with 1045 additions and 100 deletions

View File

@ -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>();
}

View File

@ -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

View File

@ -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();
}

View File

@ -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;
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;
}
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;
}
} 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)) {
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;
}
if (new_index_count <= 0 || (new_index_count >= (index_count * 0.75f))) {
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;
}
}
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 = mesh_error * scale;
if (Math::is_zero_approx(mesh_error)) {
break;
}
if (new_len <= 0) {
break;
}
new_indices.resize(new_len);
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);
}
}
}

View File

@ -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;

View File

@ -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) {

View File

@ -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);
}

View File

@ -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>

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -371,7 +371,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

View File

@ -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)

View File

@ -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)