Merge pull request #52544 from JFonS/lod_fixes
Auto LOD fixes and improvements
This commit is contained in:
commit
c370b4c4d0
|
@ -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>();
|
||||||
|
}
|
|
@ -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
|
|
@ -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/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/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::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;
|
} break;
|
||||||
case INTERNAL_IMPORT_CATEGORY_MATERIAL: {
|
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));
|
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
|
//do mesh processing
|
||||||
|
|
||||||
bool generate_lods = p_generate_lods;
|
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 create_shadow_meshes = p_create_shadow_meshes;
|
||||||
bool bake_lightmaps = p_light_bake_mode == LIGHT_BAKE_STATIC_LIGHTMAPS;
|
bool bake_lightmaps = p_light_bake_mode == LIGHT_BAKE_STATIC_LIGHTMAPS;
|
||||||
String save_to_file;
|
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")) {
|
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"];
|
save_to_file = mesh_settings["save_to_file/path"];
|
||||||
if (!save_to_file.is_resource_file()) {
|
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) {
|
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) {
|
if (create_shadow_meshes) {
|
||||||
src_mesh_node->get_mesh()->create_shadow_mesh();
|
src_mesh_node->get_mesh()->create_shadow_mesh();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,99 @@
|
||||||
|
|
||||||
#include "scene_importer_mesh.h"
|
#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 "scene/resources/surface_tool.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#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) {
|
void EditorSceneImporterMesh::add_blend_shape(const String &p_name) {
|
||||||
ERR_FAIL_COND(surfaces.size() > 0);
|
ERR_FAIL_COND(surfaces.size() > 0);
|
||||||
blend_shapes.push_back(p_name);
|
blend_shapes.push_back(p_name);
|
||||||
|
@ -157,29 +245,14 @@ void EditorSceneImporterMesh::set_surface_material(int p_surface, const Ref<Mate
|
||||||
mesh.unref();
|
mesh.unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
Basis EditorSceneImporterMesh::compute_rotation_matrix_from_ortho_6d(Vector3 p_x_raw, Vector3 p_y_raw) {
|
void EditorSceneImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (!SurfaceTool::simplify_scale_func) {
|
if (!SurfaceTool::simplify_scale_func) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!SurfaceTool::simplify_sloppy_func) {
|
if (!SurfaceTool::simplify_with_attrib_func) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!SurfaceTool::simplify_with_attrib_func) {
|
if (!SurfaceTool::optimize_vertex_cache_func) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,67 +263,343 @@ void EditorSceneImporterMesh::generate_lods() {
|
||||||
|
|
||||||
surfaces.write[i].lods.clear();
|
surfaces.write[i].lods.clear();
|
||||||
Vector<Vector3> vertices = surfaces[i].arrays[RS::ARRAY_VERTEX];
|
Vector<Vector3> vertices = surfaces[i].arrays[RS::ARRAY_VERTEX];
|
||||||
Vector<int> indices = surfaces[i].arrays[RS::ARRAY_INDEX];
|
PackedInt32Array indices = surfaces[i].arrays[RS::ARRAY_INDEX];
|
||||||
if (indices.size() == 0) {
|
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
|
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();
|
const Vector3 *vertices_ptr = vertices.ptr();
|
||||||
Vector<float> attributes;
|
const int *indices_ptr = indices.ptr();
|
||||||
Vector<float> normal_weights;
|
|
||||||
int32_t attribute_count = 6;
|
if (normals.is_empty()) {
|
||||||
if (normals.size()) {
|
normals.resize(vertices.size());
|
||||||
attributes.resize(normals.size() * attribute_count);
|
Vector3 *n_ptr = normals.ptrw();
|
||||||
for (int32_t normal_i = 0; normal_i < normals.size(); normal_i++) {
|
for (unsigned int j = 0; j < index_count; j += 3) {
|
||||||
Basis basis;
|
const Vector3 &v0 = vertices_ptr[indices_ptr[j + 0]];
|
||||||
basis.set_euler(normals[normal_i]);
|
const Vector3 &v1 = vertices_ptr[indices_ptr[j + 1]];
|
||||||
Vector3 basis_x = basis.get_axis(0);
|
const Vector3 &v2 = vertices_ptr[indices_ptr[j + 2]];
|
||||||
Vector3 basis_y = basis.get_axis(1);
|
Vector3 n = vec3_cross(v0 - v2, v0 - v1).normalized();
|
||||||
basis = compute_rotation_matrix_from_ortho_6d(basis_x, basis_y);
|
n_ptr[j + 0] = n;
|
||||||
basis_x = basis.get_axis(0);
|
n_ptr[j + 1] = n;
|
||||||
basis_y = basis.get_axis(1);
|
n_ptr[j + 2] = n;
|
||||||
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;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
attribute_count = 0;
|
|
||||||
}
|
}
|
||||||
const int min_indices = 10;
|
|
||||||
const float error_tolerance = 1.44224'95703; // Cube root of 3
|
float normal_merge_threshold = Math::cos(Math::deg2rad(p_normal_merge_angle));
|
||||||
const float threshold = 1.0 / error_tolerance;
|
float normal_pre_split_threshold = Math::cos(Math::deg2rad(MIN(180.0f, p_normal_split_angle * 2.0f)));
|
||||||
int index_target = indices.size() * threshold;
|
float normal_split_threshold = Math::cos(Math::deg2rad(p_normal_split_angle));
|
||||||
float max_mesh_error_percentage = 1e0f;
|
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 mesh_error = 0.0f;
|
||||||
float scale = SurfaceTool::simplify_scale_func((const float *)vertices_ptr, vertex_count, sizeof(Vector3));
|
|
||||||
while (index_target > min_indices) {
|
unsigned int index_target = 12; // Start with the smallest target, 4 triangles
|
||||||
Vector<int> new_indices;
|
unsigned int last_index_count = 0;
|
||||||
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);
|
int split_vertex_count = vertex_count;
|
||||||
if ((int)new_len > (index_target * error_tolerance)) {
|
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;
|
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;
|
Surface::LOD lod;
|
||||||
lod.distance = mesh_error * scale;
|
lod.distance = MAX(mesh_error * scale, CMP_EPSILON2);
|
||||||
if (Math::is_zero_approx(mesh_error)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (new_len <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
new_indices.resize(new_len);
|
|
||||||
lod.indices = new_indices;
|
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);
|
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;
|
Map<Vector3, int> unique_vertices;
|
||||||
const Vector3 *vptr = vertices.ptr();
|
const Vector3 *vptr = vertices.ptr();
|
||||||
for (int j = 0; j < vertex_count; j++) {
|
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);
|
Map<Vector3, int>::Element *E = unique_vertices.find(v);
|
||||||
|
|
||||||
|
@ -397,9 +746,9 @@ void EditorSceneImporterMesh::create_shadow_mesh() {
|
||||||
index_wptr = new_indices.ptrw();
|
index_wptr = new_indices.ptrw();
|
||||||
|
|
||||||
for (int k = 0; k < index_count; k++) {
|
for (int k = 0; k < index_count; k++) {
|
||||||
int index = index_rptr[j];
|
int index = index_rptr[k];
|
||||||
ERR_FAIL_INDEX(index, vertex_count);
|
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;
|
lods[surfaces[i].lods[j].distance] = new_indices;
|
||||||
|
@ -436,9 +785,9 @@ void EditorSceneImporterMesh::_set_data(const Dictionary &p_data) {
|
||||||
if (s.has("lods")) {
|
if (s.has("lods")) {
|
||||||
lods = s["lods"];
|
lods = s["lods"];
|
||||||
}
|
}
|
||||||
Array blend_shapes;
|
Array b_shapes;
|
||||||
if (s.has("blend_shapes")) {
|
if (s.has("b_shapes")) {
|
||||||
blend_shapes = s["blend_shapes"];
|
b_shapes = s["b_shapes"];
|
||||||
}
|
}
|
||||||
Ref<Material> material;
|
Ref<Material> material;
|
||||||
if (s.has("material")) {
|
if (s.has("material")) {
|
||||||
|
@ -448,7 +797,7 @@ void EditorSceneImporterMesh::_set_data(const Dictionary &p_data) {
|
||||||
if (s.has("flags")) {
|
if (s.has("flags")) {
|
||||||
flags = s["flags"];
|
flags = s["flags"];
|
||||||
}
|
}
|
||||||
add_surface(prim, arr, blend_shapes, lods, material, name, flags);
|
add_surface(prim, arr, b_shapes, lods, material, name, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#define EDITOR_SCENE_IMPORTER_MESH_H
|
#define EDITOR_SCENE_IMPORTER_MESH_H
|
||||||
|
|
||||||
#include "core/io/resource.h"
|
#include "core/io/resource.h"
|
||||||
|
#include "core/templates/local_vector.h"
|
||||||
#include "scene/resources/concave_polygon_shape_3d.h"
|
#include "scene/resources/concave_polygon_shape_3d.h"
|
||||||
#include "scene/resources/convex_polygon_shape_3d.h"
|
#include "scene/resources/convex_polygon_shape_3d.h"
|
||||||
#include "scene/resources/mesh.h"
|
#include "scene/resources/mesh.h"
|
||||||
|
@ -55,12 +56,20 @@ class EditorSceneImporterMesh : public Resource {
|
||||||
Vector<BlendShape> blend_shape_data;
|
Vector<BlendShape> blend_shape_data;
|
||||||
struct LOD {
|
struct LOD {
|
||||||
Vector<int> indices;
|
Vector<int> indices;
|
||||||
float distance;
|
float distance = 0.0f;
|
||||||
};
|
};
|
||||||
Vector<LOD> lods;
|
Vector<LOD> lods;
|
||||||
Ref<Material> material;
|
Ref<Material> material;
|
||||||
String name;
|
String name;
|
||||||
uint32_t flags = 0;
|
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<Surface> surfaces;
|
||||||
Vector<String> blend_shapes;
|
Vector<String> blend_shapes;
|
||||||
|
@ -71,7 +80,6 @@ class EditorSceneImporterMesh : public Resource {
|
||||||
Ref<EditorSceneImporterMesh> shadow_mesh;
|
Ref<EditorSceneImporterMesh> shadow_mesh;
|
||||||
|
|
||||||
Size2i lightmap_size_hint;
|
Size2i lightmap_size_hint;
|
||||||
Basis compute_rotation_matrix_from_ortho_6d(Vector3 p_x_raw, Vector3 y_raw);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _set_data(const Dictionary &p_data);
|
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 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();
|
void create_shadow_mesh();
|
||||||
Ref<EditorSceneImporterMesh> get_shadow_mesh() const;
|
Ref<EditorSceneImporterMesh> get_shadow_mesh() const;
|
||||||
|
|
|
@ -2601,6 +2601,9 @@ void Node3DEditorViewport::_project_settings_changed() {
|
||||||
|
|
||||||
const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling");
|
const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling");
|
||||||
viewport->set_use_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) {
|
void Node3DEditorViewport::_notification(int p_what) {
|
||||||
|
|
|
@ -168,7 +168,7 @@ void LightmapRaycasterEmbree::clear_mesh_filter() {
|
||||||
filter_meshes.clear();
|
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));
|
print_error("Embree error: " + String(p_str));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,16 +179,11 @@ LightmapRaycasterEmbree::LightmapRaycasterEmbree() {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
embree_device = rtcNewDevice(nullptr);
|
embree_device = rtcNewDevice(nullptr);
|
||||||
rtcSetDeviceErrorFunction(embree_device, &embree_error_handler, nullptr);
|
rtcSetDeviceErrorFunction(embree_device, &embree_lm_error_handler, nullptr);
|
||||||
embree_scene = rtcNewScene(embree_device);
|
embree_scene = rtcNewScene(embree_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
LightmapRaycasterEmbree::~LightmapRaycasterEmbree() {
|
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) {
|
if (embree_scene != nullptr) {
|
||||||
rtcReleaseScene(embree_scene);
|
rtcReleaseScene(embree_scene);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,9 @@
|
||||||
|
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
|
|
||||||
|
#include "core/io/image.h"
|
||||||
#include "core/object/object.h"
|
#include "core/object/object.h"
|
||||||
#include "scene/3d/lightmapper.h"
|
#include "scene/3d/lightmapper.h"
|
||||||
#include "scene/resources/mesh.h"
|
|
||||||
|
|
||||||
#include <embree3/rtcore.h>
|
#include <embree3/rtcore.h>
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,14 @@
|
||||||
|
|
||||||
#include "lightmap_raycaster.h"
|
#include "lightmap_raycaster.h"
|
||||||
#include "raycast_occlusion_cull.h"
|
#include "raycast_occlusion_cull.h"
|
||||||
|
#include "static_raycaster.h"
|
||||||
|
|
||||||
RaycastOcclusionCull *raycast_occlusion_cull = nullptr;
|
RaycastOcclusionCull *raycast_occlusion_cull = nullptr;
|
||||||
|
|
||||||
void register_raycast_types() {
|
void register_raycast_types() {
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
LightmapRaycasterEmbree::make_default_raycaster();
|
LightmapRaycasterEmbree::make_default_raycaster();
|
||||||
|
StaticRaycasterEmbree::make_default_raycaster();
|
||||||
#endif
|
#endif
|
||||||
raycast_occlusion_cull = memnew(RaycastOcclusionCull);
|
raycast_occlusion_cull = memnew(RaycastOcclusionCull);
|
||||||
}
|
}
|
||||||
|
@ -46,4 +48,7 @@ void unregister_raycast_types() {
|
||||||
if (raycast_occlusion_cull) {
|
if (raycast_occlusion_cull) {
|
||||||
memdelete(raycast_occlusion_cull);
|
memdelete(raycast_occlusion_cull);
|
||||||
}
|
}
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
StaticRaycasterEmbree::free();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -295,7 +295,7 @@ public:
|
||||||
|
|
||||||
AABB aabb;
|
AABB aabb;
|
||||||
struct LOD {
|
struct LOD {
|
||||||
float edge_length;
|
float edge_length = 0.0f;
|
||||||
Vector<uint8_t> index_data;
|
Vector<uint8_t> index_data;
|
||||||
};
|
};
|
||||||
Vector<LOD> lods;
|
Vector<LOD> lods;
|
||||||
|
|
|
@ -373,7 +373,9 @@ Files extracted from upstream repository:
|
||||||
- `LICENSE.md`.
|
- `LICENSE.md`.
|
||||||
|
|
||||||
An [experimental upstream feature](https://github.com/zeux/meshoptimizer/tree/simplify-attr),
|
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
|
## miniupnpc
|
||||||
|
|
176
thirdparty/meshoptimizer/patches/attribute-aware-simplify-distance-only-metric.patch
vendored
Normal file
176
thirdparty/meshoptimizer/patches/attribute-aware-simplify-distance-only-metric.patch
vendored
Normal 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)
|
|
@ -20,7 +20,7 @@
|
||||||
#define TRACESTATS(i) (void)0
|
#define TRACESTATS(i) (void)0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define ATTRIBUTES 8
|
#define ATTRIBUTES 3
|
||||||
|
|
||||||
// This work is based on:
|
// This work is based on:
|
||||||
// Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997
|
// Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997
|
||||||
|
@ -445,6 +445,7 @@ struct Collapse
|
||||||
float error;
|
float error;
|
||||||
unsigned int errorui;
|
unsigned int errorui;
|
||||||
};
|
};
|
||||||
|
float distance_error;
|
||||||
};
|
};
|
||||||
|
|
||||||
static float normalize(Vector3& v)
|
static float normalize(Vector3& v)
|
||||||
|
@ -525,6 +526,34 @@ static float quadricError(const Quadric& Q, const Vector3& v)
|
||||||
return fabsf(r) * s;
|
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)
|
static void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, float w)
|
||||||
{
|
{
|
||||||
float aw = a * w;
|
float aw = a * w;
|
||||||
|
@ -680,7 +709,7 @@ static void quadricUpdateAttributes(Quadric& Q, const Vector3& p0, const Vector3
|
||||||
}
|
}
|
||||||
#endif
|
#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)
|
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;
|
Quadric Q;
|
||||||
quadricFromTriangle(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], 1.f);
|
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
|
#if ATTRIBUTES
|
||||||
quadricUpdateAttributes(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], Q.w);
|
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)
|
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[i0]], Q);
|
||||||
quadricAdd(vertex_quadrics[remap[i1]], 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;
|
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)
|
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 ei = quadricError(qi, vertex_positions[i1]);
|
||||||
float ej = quadricError(qj, vertex_positions[j1]);
|
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
|
// pick edge direction with minimal error
|
||||||
c.v0 = ei <= ej ? i0 : j0;
|
c.v0 = ei <= ej ? i0 : j0;
|
||||||
c.v1 = ei <= ej ? i1 : j1;
|
c.v1 = ei <= ej ? i1 : j1;
|
||||||
c.error = ei <= ej ? ei : ej;
|
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 edge_collapses = 0;
|
||||||
size_t triangle_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);
|
assert(collapse_remap[r1] == r1);
|
||||||
|
|
||||||
quadricAdd(vertex_quadrics[r1], vertex_quadrics[r0]);
|
quadricAdd(vertex_quadrics[r1], vertex_quadrics[r0]);
|
||||||
|
quadricAdd(vertex_no_attrib_quadrics[r1], vertex_no_attrib_quadrics[r0]);
|
||||||
|
|
||||||
if (vertex_kind[i0] == Kind_Complex)
|
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;
|
triangle_collapses += (vertex_kind[i0] == Kind_Border) ? 1 : 2;
|
||||||
edge_collapses++;
|
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
|
#if TRACE
|
||||||
|
@ -1455,9 +1495,11 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned
|
||||||
|
|
||||||
Quadric* vertex_quadrics = allocator.allocate<Quadric>(vertex_count);
|
Quadric* vertex_quadrics = allocator.allocate<Quadric>(vertex_count);
|
||||||
memset(vertex_quadrics, 0, vertex_count * sizeof(Quadric));
|
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);
|
fillFaceQuadrics(vertex_quadrics, vertex_no_attrib_quadrics, indices, index_count, vertex_positions, remap);
|
||||||
fillEdgeQuadrics(vertex_quadrics, indices, index_count, vertex_positions, remap, vertex_kind, loop, loopback);
|
fillEdgeQuadrics(vertex_quadrics, vertex_no_attrib_quadrics, indices, index_count, vertex_positions, remap, vertex_kind, loop, loopback);
|
||||||
|
|
||||||
if (result != indices)
|
if (result != indices)
|
||||||
memcpy(result, indices, index_count * sizeof(unsigned int));
|
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)
|
if (edge_collapse_count == 0)
|
||||||
break;
|
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
|
#if TRACE > 1
|
||||||
dumpEdgeCollapses(edge_collapses, edge_collapse_count, vertex_kind);
|
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++));
|
printf("pass %d: ", int(pass_count++));
|
||||||
#endif
|
#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
|
// no edges can be collapsed any more due to hitting the error limit or triangle collapse limit
|
||||||
if (collapses == 0)
|
if (collapses == 0)
|
||||||
|
|
Loading…
Reference in New Issue