From e4cf48e227ccd4b7a3194f218b32b52fe415ab5a Mon Sep 17 00:00:00 2001 From: Relintai Date: Sat, 23 May 2020 20:13:38 +0200 Subject: [PATCH] Initial commit. (A copy of my fast quadratic mesh simplifier module.) --- .gitignore | 8 + LICENSE | 20 + README.md | 7 + SCsub | 29 + config.py | 40 + doc_classes/FastQuadraticMeshSimplifier.xml | 63 + fast_quadratic_mesh_simplifier.cpp | 132 ++ fast_quadratic_mesh_simplifier.h | 75 ++ mesh_utils.h | 509 ++++++++ register_types.cpp | 35 + register_types.h | 32 + simplify.h | 1292 +++++++++++++++++++ 12 files changed, 2242 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SCsub create mode 100644 config.py create mode 100644 doc_classes/FastQuadraticMeshSimplifier.xml create mode 100644 fast_quadratic_mesh_simplifier.cpp create mode 100644 fast_quadratic_mesh_simplifier.h create mode 100644 mesh_utils.h create mode 100644 register_types.cpp create mode 100644 register_types.h create mode 100644 simplify.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7001be7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.import +*.d +*.o +*.meta +*.obj +*.pyc +*.bc +*.os \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5d45b71 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2020 Péter Magyar +Copyright (c) 2017-2020 Mattias Edlund + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3030757 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Fast Quadratic Mesh Simplifier + +A port of https://github.com/Whinarn/UnityMeshSimplifier to a GODOT engine c++ module. + +For future reference it's based on e8ff4e8862735197c3308cfe926eeba68e0d2edb. + +Porting is mostly done, but it does needs some debugging (it has a crash if smart linking is enabled). diff --git a/SCsub b/SCsub new file mode 100644 index 0000000..622d246 --- /dev/null +++ b/SCsub @@ -0,0 +1,29 @@ +import os + +Import('env') + +module_env = env.Clone() + +#Should work with just arrays +#if os.path.isdir('../mesh_data_resource'): +# module_env.Append(CPPDEFINES=['MESH_DATA_RESOURCE_PRESENT']) + +sources = [ + + "register_types.cpp", + + "fast_quadratic_mesh_simplifier.cpp", +] + +if ARGUMENTS.get('custom_modules_shared', 'no') == 'yes': + # Shared lib compilation + module_env.Append(CCFLAGS=['-fPIC']) + module_env['LIBS'] = [] + shared_lib = module_env.SharedLibrary(target='#bin/mesh_utils', source=sources) + shared_lib_shim = shared_lib[0].name.rsplit('.', 1)[0] + env.Append(LIBS=[shared_lib_shim]) + env.Append(LIBPATH=['#bin']) +else: + # Static compilation + module_env.add_source_files(env.modules_sources, sources) + diff --git a/config.py b/config.py new file mode 100644 index 0000000..93e9cc8 --- /dev/null +++ b/config.py @@ -0,0 +1,40 @@ + +# Copyright (c) 2020 Péter Magyar +# Copyright(c) 2017-2020 Mattias Edlund + +# 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. + + + +def can_build(env, platform): + return True + + +def configure(env): + pass + + +def get_doc_classes(): + return [ + "FastQuadraticMeshSimplifier", + ] + +def get_doc_path(): + return "doc_classes" + diff --git a/doc_classes/FastQuadraticMeshSimplifier.xml b/doc_classes/FastQuadraticMeshSimplifier.xml new file mode 100644 index 0000000..dbc7f8b --- /dev/null +++ b/doc_classes/FastQuadraticMeshSimplifier.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fast_quadratic_mesh_simplifier.cpp b/fast_quadratic_mesh_simplifier.cpp new file mode 100644 index 0000000..d1a65d4 --- /dev/null +++ b/fast_quadratic_mesh_simplifier.cpp @@ -0,0 +1,132 @@ +#include "fast_quadratic_mesh_simplifier.h" + +/* + +Copyright (c) 2020 Péter Magyar +Copyright(c) 2017-2020 Mattias Edlund + +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. + +*/ + +int FastQuadraticMeshSimplifier::get_max_iteration_count() const { + return simplify._max_iteration_count; +} +void FastQuadraticMeshSimplifier::set_max_iteration_count(const int value) { + simplify._max_iteration_count = value; +} + +int FastQuadraticMeshSimplifier::get_max_lossless_iteration_count() const { + return simplify._max_lossless_iteration_count; +} +void FastQuadraticMeshSimplifier::set_max_lossless_iteration_count(const int value) { + simplify._max_lossless_iteration_count = value; +} + +bool FastQuadraticMeshSimplifier::get_enable_smart_link() const { + return simplify._enable_smart_link; +} +void FastQuadraticMeshSimplifier::set_enable_smart_link(const bool value) { + simplify._enable_smart_link = value; +} + +bool FastQuadraticMeshSimplifier::get_preserve_border_edges() const { + return simplify._preserve_border_dges; +} +void FastQuadraticMeshSimplifier::set_preserve_border_edges(const bool value) { + simplify._preserve_border_dges = value; +} + +bool FastQuadraticMeshSimplifier::get_preserve_uv_seam_edges() const { + return simplify._preserve_uv_seam_edges; +} +void FastQuadraticMeshSimplifier::set_preserve_uv_seam_edges(const bool value) { + simplify._preserve_uv_seam_edges = value; +} + +bool FastQuadraticMeshSimplifier::get_preserve_uv_foldover_edges() const { + return simplify._preserve_uv_foldover_edges; +} +void FastQuadraticMeshSimplifier::set_preserve_uv_foldover_edges(const bool value) { + simplify._preserve_uv_foldover_edges = value; +} + +int FastQuadraticMeshSimplifier::get_format() const { + return simplify._format; +} +void FastQuadraticMeshSimplifier::set_format(const int value) { + simplify._format = value; +} + +void FastQuadraticMeshSimplifier::initialize(const Array &arrays) { + simplify.initialize(arrays); +} + +Array FastQuadraticMeshSimplifier::get_arrays() { + return simplify.get_arrays(); +} + +void FastQuadraticMeshSimplifier::simplify_mesh(int target_count, double agressiveness, bool verbose) { + simplify.simplify_mesh(target_count, agressiveness, verbose); +} + +void FastQuadraticMeshSimplifier::simplify_mesh_lossless(bool verbose) { + simplify.simplify_mesh_lossless(verbose); +} + +FastQuadraticMeshSimplifier::FastQuadraticMeshSimplifier() { +} + +FastQuadraticMeshSimplifier::~FastQuadraticMeshSimplifier() { +} + +void FastQuadraticMeshSimplifier::_bind_methods() { + ClassDB::bind_method(D_METHOD("initialize", "arrays"), &FastQuadraticMeshSimplifier::initialize); + ClassDB::bind_method(D_METHOD("get_arrays"), &FastQuadraticMeshSimplifier::get_arrays); + ClassDB::bind_method(D_METHOD("simplify_mesh", "target_count", "agressiveness", "verbose"), &FastQuadraticMeshSimplifier::simplify_mesh, DEFVAL(7), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("simplify_mesh_lossless", "verbose"), &FastQuadraticMeshSimplifier::simplify_mesh_lossless, DEFVAL(false)); + + ClassDB::bind_method(D_METHOD("get_max_iteration_count"), &FastQuadraticMeshSimplifier::get_max_iteration_count); + ClassDB::bind_method(D_METHOD("set_max_iteration_count", "value"), &FastQuadraticMeshSimplifier::set_max_iteration_count); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_iteration_count"), "set_max_iteration_count", "get_max_iteration_count"); + + ClassDB::bind_method(D_METHOD("get_max_lossless_iteration_count"), &FastQuadraticMeshSimplifier::get_max_lossless_iteration_count); + ClassDB::bind_method(D_METHOD("set_max_lossless_iteration_count", "value"), &FastQuadraticMeshSimplifier::set_max_lossless_iteration_count); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_lossless_iteration_count"), "set_max_lossless_iteration_count", "get_max_lossless_iteration_count"); + + ClassDB::bind_method(D_METHOD("get_enable_smart_link"), &FastQuadraticMeshSimplifier::get_enable_smart_link); + ClassDB::bind_method(D_METHOD("set_enable_smart_link", "value"), &FastQuadraticMeshSimplifier::set_enable_smart_link); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_smart_link"), "set_enable_smart_link", "get_enable_smart_link"); + + ClassDB::bind_method(D_METHOD("get_preserve_border_edges"), &FastQuadraticMeshSimplifier::get_preserve_border_edges); + ClassDB::bind_method(D_METHOD("set_preserve_border_edges", "value"), &FastQuadraticMeshSimplifier::set_preserve_border_edges); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "preserve_border_edges"), "set_preserve_border_edges", "get_preserve_border_edges"); + + ClassDB::bind_method(D_METHOD("get_preserve_uv_seam_edges"), &FastQuadraticMeshSimplifier::get_preserve_uv_seam_edges); + ClassDB::bind_method(D_METHOD("set_preserve_uv_seam_edges", "value"), &FastQuadraticMeshSimplifier::set_preserve_uv_seam_edges); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "preserve_uv_seam_edges"), "set_preserve_uv_seam_edges", "get_preserve_uv_seam_edges"); + + ClassDB::bind_method(D_METHOD("get_preserve_uv_foldover_edges"), &FastQuadraticMeshSimplifier::get_preserve_uv_foldover_edges); + ClassDB::bind_method(D_METHOD("set_preserve_uv_foldover_edges", "value"), &FastQuadraticMeshSimplifier::set_preserve_uv_foldover_edges); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "preserve_uv_foldover_edges"), "set_preserve_uv_foldover_edges", "get_preserve_uv_foldover_edges"); + + ClassDB::bind_method(D_METHOD("get_format"), &FastQuadraticMeshSimplifier::get_format); + ClassDB::bind_method(D_METHOD("set_format", "value"), &FastQuadraticMeshSimplifier::set_format); + ADD_PROPERTY(PropertyInfo(Variant::INT, "format"), "set_format", "get_format"); +} diff --git a/fast_quadratic_mesh_simplifier.h b/fast_quadratic_mesh_simplifier.h new file mode 100644 index 0000000..209fe42 --- /dev/null +++ b/fast_quadratic_mesh_simplifier.h @@ -0,0 +1,75 @@ +#ifndef FAST_QUADRATIC_MESH_SIMPLIFIER_H +#define FAST_QUADRATIC_MESH_SIMPLIFIER_H + +/* + +Copyright (c) 2020 Péter Magyar +Copyright(c) 2017-2020 Mattias Edlund + +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 "core/reference.h" + +#include "core/array.h" + +#include "simplify.h" + +class FastQuadraticMeshSimplifier : public Reference { + GDCLASS(FastQuadraticMeshSimplifier, Reference); + +public: + int get_max_iteration_count() const; + void set_max_iteration_count(const int value); + + int get_max_lossless_iteration_count() const; + void set_max_lossless_iteration_count(const int value); + + bool get_enable_smart_link() const; + void set_enable_smart_link(const bool value); + + bool get_preserve_border_edges() const; + void set_preserve_border_edges(const bool value); + + bool get_preserve_uv_seam_edges() const; + void set_preserve_uv_seam_edges(const bool value); + + bool get_preserve_uv_foldover_edges() const; + void set_preserve_uv_foldover_edges(const bool value); + + int get_format() const; + void set_format(const int value); + + void initialize(const Array &arrays); + Array get_arrays(); + void simplify_mesh(int target_count, double agressiveness = 7, bool verbose = false); + void simplify_mesh_lossless(bool verbose = false); + + FastQuadraticMeshSimplifier(); + ~FastQuadraticMeshSimplifier(); + +protected: + static void _bind_methods(); + +private: + Simplify::FQMS simplify; +}; + +#endif diff --git a/mesh_utils.h b/mesh_utils.h new file mode 100644 index 0000000..9a8e8cf --- /dev/null +++ b/mesh_utils.h @@ -0,0 +1,509 @@ +#ifndef VOXELMAN_MESH_UTILS_H +#define VOXELMAN_MESH_UTILS_H + +/* + +Copyright (c) 2020 Péter Magyar +Copyright(c) 2017-2020 Mattias Edlund + +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 "core/color.h" +#include "core/hashfuncs.h" +#include "core/math/vector2.h" +#include "core/math/vector3.h" +#include "core/vector.h" + +namespace FQMS { + +struct Vertex { + + Vector3 vertex; + Color color; + Vector3 normal; // normal, binormal, tangent + Vector3 binormal; + Vector3 tangent; + Vector2 uv; + Vector2 uv2; + Vector bones; + Vector weights; + + bool operator==(const Vertex &p_vertex) const { + + if (vertex != p_vertex.vertex) + return false; + + if (uv != p_vertex.uv) + return false; + + if (uv2 != p_vertex.uv2) + return false; + + if (normal != p_vertex.normal) + return false; + + if (binormal != p_vertex.binormal) + return false; + + if (color != p_vertex.color) + return false; + + if (bones.size() != p_vertex.bones.size()) + return false; + + for (int i = 0; i < bones.size(); i++) { + if (bones[i] != p_vertex.bones[i]) + return false; + } + + for (int i = 0; i < weights.size(); i++) { + if (weights[i] != p_vertex.weights[i]) + return false; + } + + return true; + } + + Vertex() {} +}; + +struct VertexHasher { + static _FORCE_INLINE_ uint32_t hash(const Vertex &p_vtx) { + + uint32_t h = hash_djb2_buffer((const uint8_t *)&p_vtx.vertex, sizeof(real_t) * 3); + h = hash_djb2_buffer((const uint8_t *)&p_vtx.normal, sizeof(real_t) * 3, h); + h = hash_djb2_buffer((const uint8_t *)&p_vtx.binormal, sizeof(real_t) * 3, h); + h = hash_djb2_buffer((const uint8_t *)&p_vtx.tangent, sizeof(real_t) * 3, h); + h = hash_djb2_buffer((const uint8_t *)&p_vtx.uv, sizeof(real_t) * 2, h); + h = hash_djb2_buffer((const uint8_t *)&p_vtx.uv2, sizeof(real_t) * 2, h); + h = hash_djb2_buffer((const uint8_t *)&p_vtx.color, sizeof(real_t) * 4, h); + h = hash_djb2_buffer((const uint8_t *)p_vtx.bones.ptr(), p_vtx.bones.size() * sizeof(int), h); + h = hash_djb2_buffer((const uint8_t *)p_vtx.weights.ptr(), p_vtx.weights.size() * sizeof(float), h); + return h; + } +}; + +struct WeightSort { + int index; + float weight; + bool operator<(const WeightSort &p_right) const { + return weight < p_right.weight; + } +}; + +/// A symmetric matrix. +struct SymmetricMatrix { + /// The m11 component. + double m0; + /// The m12 component. + double m1; + /// The m13 component. + double m2; + /// The m14 component. + double m3; + /// The m22 component. + double m4; + /// The m23 component. + double m5; + /// The m24 component. + double m6; + /// The m33 component. + double m7; + /// The m34 component. + double m8; + /// The m44 component. + double m9; + + _FORCE_INLINE_ const double get(int p_index) const { + CRASH_BAD_INDEX(p_index, 10); + + switch (p_index) { + case 0: + return m0; + case 1: + return m1; + case 2: + return m2; + case 3: + return m3; + case 4: + return m4; + case 5: + return m5; + case 6: + return m6; + case 7: + return m7; + case 8: + return m8; + case 9: + return m9; + } + + return 0; + } + + SymmetricMatrix() { + m0 = 0; + m1 = 0; + m2 = 0; + m3 = 0; + m4 = 0; + m5 = 0; + m6 = 0; + m7 = 0; + m8 = 0; + m9 = 0; + } + + /// Creates a symmetric matrix with a value in each component. + SymmetricMatrix(double c) { + m0 = c; + m1 = c; + m2 = c; + m3 = c; + m4 = c; + m5 = c; + m6 = c; + m7 = c; + m8 = c; + m9 = c; + } + + /// Creates a symmetric matrix. + SymmetricMatrix(double p_m0, double p_m1, double p_m2, double p_m3, + double p_m4, double p_m5, double p_m6, double p_m7, double p_m8, double p_m9) { + m0 = p_m0; + m1 = p_m1; + m2 = p_m2; + m3 = p_m3; + m4 = p_m4; + m5 = p_m5; + m6 = p_m6; + m7 = p_m7; + m8 = p_m8; + m9 = p_m9; + } + + /// Creates a symmetric matrix from a plane. + SymmetricMatrix(double a, double b, double c, double d) { + m0 = a * a; + m1 = a * b; + m2 = a * c; + m3 = a * d; + + m4 = b * b; + m5 = b * c; + m6 = b * d; + + m7 = c * c; + m8 = c * d; + + m9 = d * d; + } + + SymmetricMatrix operator+(const SymmetricMatrix &p_m) const { + return SymmetricMatrix(m0 + p_m.m0, m1 + p_m.m1, m2 + p_m.m2, m3 + p_m.m3, + m4 + p_m.m4, m5 + p_m.m5, m6 + p_m.m6, + m7 + p_m.m7, m8 + p_m.m8, + m9 + p_m.m9); + } + + void operator+=(const SymmetricMatrix &p_m) { + m0 += p_m.m0; + m1 += p_m.m1; + m2 += p_m.m2; + m3 += p_m.m3; + m4 += p_m.m4; + m5 += p_m.m5; + m6 += p_m.m6; + m7 += p_m.m7; + m8 += p_m.m8; + m9 += p_m.m9; + } + + /// Determinant(0, 1, 2, 1, 4, 5, 2, 5, 7) + double Determinant1() { + double det = + m0 * m4 * m7 + + m2 * m1 * m5 + + m1 * m5 * m2 - + m2 * m4 * m2 - + m0 * m5 * m5 - + m1 * m1 * m7; + return det; + } + + /// Determinant(1, 2, 3, 4, 5, 6, 5, 7, 8) + double Determinant2() { + double det = + m1 * m5 * m8 + + m3 * m4 * m7 + + m2 * m6 * m5 - + m3 * m5 * m5 - + m1 * m6 * m7 - + m2 * m4 * m8; + return det; + } + + double Determinant3() { + double det = + m0 * m5 * m8 + + m3 * m1 * m7 + + m2 * m6 * m2 - + m3 * m5 * m2 - + m0 * m6 * m7 - + m2 * m1 * m8; + return det; + } + + /// Determinant(0, 1, 3, 1, 4, 6, 2, 5, 8) + double Determinant4() { + double det = + m0 * m4 * m8 + + m3 * m1 * m5 + + m1 * m6 * m2 - + m3 * m4 * m2 - + m0 * m6 * m5 - + m1 * m1 * m8; + return det; + } + + double Determinant(int a11, int a12, int a13, + int a21, int a22, int a23, + int a31, int a32, int a33) { + double det = + get(a11) * get(a22) * get(a33) + + get(a13) * get(a21) * get(a32) + + get(a12) * get(a23) * get(a31) - + get(a13) * get(a22) * get(a31) - + get(a11) * get(a23) * get(a32) - + get(a12) * get(a21) * get(a33); + return det; + } + + void from_plane(double a, double b, double c, double d) { + m0 = a * a; + m1 = a * b; + m2 = a * c; + m3 = a * d; + + m4 = b * b; + m5 = b * c; + m6 = b * d; + + m7 = c * c; + m8 = c * d; + + m9 = d * d; + } + + void reset() { + m0 = 0; + m1 = 0; + m2 = 0; + m3 = 0; + m4 = 0; + m5 = 0; + m6 = 0; + m7 = 0; + m8 = 0; + m9 = 0; + } +}; + +struct MUTriangle { + int v0; + int v1; + int v2; + int subMeshIndex; + + int va0; + int va1; + int va2; + + double err0; + double err1; + double err2; + double err3; + + bool deleted; + bool dirty; + Vector3 n; + + _FORCE_INLINE_ int get(int p_index) { + return (p_index == 0 ? v0 : (p_index == 1 ? v1 : v2)); + } + + _FORCE_INLINE_ void set(int p_index, int value) { + CRASH_BAD_INDEX(p_index, 3); + + switch (p_index) { + case 0: + v0 = value; + break; + case 1: + v1 = value; + break; + case 2: + v2 = value; + break; + } + } + + MUTriangle() { + v0 = 0; + v1 = 0; + v2 = 0; + subMeshIndex = 0; + + va0 = 0; + va1 = 0; + va2 = 0; + + err0 = err1 = err2 = err3 = 0; + deleted = dirty = false; + } + + MUTriangle(int p_v0, int p_v1, int p_v2, int p_subMeshIndex) { + v0 = p_v0; + v1 = p_v1; + v2 = p_v2; + subMeshIndex = p_subMeshIndex; + + va0 = p_v0; + va1 = p_v1; + va2 = p_v2; + + err0 = err1 = err2 = err3 = 0; + deleted = dirty = false; + } + + Vector3 GetAttributeIndices() { + Vector3 attributeIndices; + + attributeIndices[0] = va0; + attributeIndices[1] = va1; + attributeIndices[2] = va2; + + return attributeIndices; + } + + void SetAttributeIndex(int index, int value) { + CRASH_BAD_INDEX(index, 3); + + switch (index) { + case 0: + va0 = value; + break; + case 1: + va1 = value; + break; + case 2: + va2 = value; + break; + } + } + + Vector3 GetErrors() { + Vector3 err; + + err[0] = err0; + err[1] = err1; + err[2] = err2; + + return err; + } +}; + +struct MUVertex { + Vertex vertex; + int tstart; + int tcount; + SymmetricMatrix q; + bool border_edge; + bool uv_seam_edge; + bool uv_foldover_edge; + + MUVertex() { + tstart = 0; + tcount = 0; + border_edge = true; + uv_seam_edge = false; + uv_foldover_edge = false; + } + + MUVertex(float x, float y, float z) { + vertex.vertex = Vector3(x, y, z); + tstart = 0; + tcount = 0; + border_edge = true; + uv_seam_edge = false; + uv_foldover_edge = false; + } + + MUVertex(const Vertex &p_vertex) { + vertex = p_vertex; + tstart = 0; + tcount = 0; + border_edge = true; + uv_seam_edge = false; + uv_foldover_edge = false; + } +}; + +struct MURef { + int tid; + int tvertex; + + MURef() { + tid = 0; + tvertex = 0; + } + + void Set(int p_tid, int p_tvertex) { + tid = p_tid; + tvertex = p_tvertex; + } +}; + +struct BorderVertex { + int index; + int hash; + + BorderVertex() { + index = 0; + hash = 0; + } + + BorderVertex(int p_index, int p_hash) { + index = p_index; + hash = p_hash; + } +}; + +struct BorderVertexComparer { + _FORCE_INLINE_ bool operator()(const BorderVertex &a, const BorderVertex &b) const { return a.hash < b.hash; } +}; + +} // namespace FQMS + +#endif diff --git a/register_types.cpp b/register_types.cpp new file mode 100644 index 0000000..e7ac422 --- /dev/null +++ b/register_types.cpp @@ -0,0 +1,35 @@ +#include "register_types.h" + +#include "fast_quadratic_mesh_simplifier.h" + +/* + +Copyright (c) 2020 Péter Magyar +Copyright(c) 2017-2020 Mattias Edlund + +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. + +*/ + +void register_mesh_utils_types() { + ClassDB::register_class(); +} + +void unregister_mesh_utils_types() { +} diff --git a/register_types.h b/register_types.h new file mode 100644 index 0000000..c842f67 --- /dev/null +++ b/register_types.h @@ -0,0 +1,32 @@ +#ifndef FQMS_REGISTER_TYPES_H +#define FQMS_REGISTER_TYPES_H + +/* + +Copyright (c) 2020 Péter Magyar +Copyright(c) 2017-2020 Mattias Edlund + +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. + +*/ + +void register_mesh_utils_types(); +void unregister_mesh_utils_types(); + +#endif diff --git a/simplify.h b/simplify.h new file mode 100644 index 0000000..b9bbf64 --- /dev/null +++ b/simplify.h @@ -0,0 +1,1292 @@ +#ifndef FAST_QUADRATIC_MESH_SIMPLIFIER_SIMPLIFY_H +#define FAST_QUADRATIC_MESH_SIMPLIFIER_SIMPLIFY_H + +///////////////////////////////////////////// +// +// Mesh Simplification Tutorial +// +// (C) by Sven Forstmann in 2014 +// +// License : MIT +// http://opensource.org/licenses/MIT +// +//https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification +// +// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile + +#include "core/array.h" +#include "core/math/vector2.h" +#include "core/math/vector3.h" +#include "core/ustring.h" +#include "scene/resources/mesh.h" + +#include //FLT_EPSILON, DBL_EPSILON +#include +#include +#include +#include + +#include "core/version.h" + +#if VERSION_MAJOR < 4 +#include "servers/visual_server.h" +#else +#include "servers/rendering_server.h" + +typedef class RenderingServer VisualServer; +typedef class RenderingServer VS; + +#define PoolVector Vector +#endif + +namespace Simplify { + +struct vector3 { + double x, y, z; +}; + +struct vec3f { + double x, y, z; + + inline vec3f(void) {} + + //inline vec3f operator =( vector3 a ) + // { vec3f b ; b.x = a.x; b.y = a.y; b.z = a.z; return b;} + + inline vec3f(vector3 a) { + x = a.x; + y = a.y; + z = a.z; + } + + inline vec3f(const double X, const double Y, const double Z) { + x = X; + y = Y; + z = Z; + } + + inline vec3f operator+(const vec3f &a) const { return vec3f(x + a.x, y + a.y, z + a.z); } + + inline vec3f operator+=(const vec3f &a) const { return vec3f(x + a.x, y + a.y, z + a.z); } + + inline vec3f operator*(const double a) const { return vec3f(x * a, y * a, z * a); } + + inline vec3f operator*(const vec3f a) const { return vec3f(x * a.x, y * a.y, z * a.z); } + + inline vec3f v3() const { return vec3f(x, y, z); } + + inline vec3f operator=(const vector3 a) { + x = a.x; + y = a.y; + z = a.z; + return *this; + } + + inline vec3f operator=(const vec3f a) { + x = a.x; + y = a.y; + z = a.z; + return *this; + } + + inline vec3f operator/(const vec3f a) const { return vec3f(x / a.x, y / a.y, z / a.z); } + + inline vec3f operator-(const vec3f &a) const { return vec3f(x - a.x, y - a.y, z - a.z); } + + inline vec3f operator/(const double a) const { return vec3f(x / a, y / a, z / a); } + + inline double dot(const vec3f &a) const { return a.x * x + a.y * y + a.z * z; } + + inline vec3f cross(const vec3f &a, const vec3f &b) { + x = a.y * b.z - a.z * b.y; + y = a.z * b.x - a.x * b.z; + z = a.x * b.y - a.y * b.x; + return *this; + } + + inline double angle(const vec3f &v) { + vec3f a = v, b = *this; + double dot = v.x * x + v.y * y + v.z * z; + double len = a.length() * b.length(); + if (len == 0) len = 0.00001f; + double input = dot / len; + if (input < -1) input = -1; + if (input > 1) input = 1; + return (double)acos(input); + } + + inline double angle2(const vec3f &v, const vec3f &w) { + vec3f a = v, b = *this; + double dot = a.x * b.x + a.y * b.y + a.z * b.z; + double len = a.length() * b.length(); + if (len == 0) len = 1; + + vec3f plane; + plane.cross(b, w); + + if (plane.x * a.x + plane.y * a.y + plane.z * a.z > 0) + return (double)-acos(dot / len); + + return (double)acos(dot / len); + } + + inline vec3f rot_x(double a) { + double yy = cos(a) * y + sin(a) * z; + double zz = cos(a) * z - sin(a) * y; + y = yy; + z = zz; + return *this; + } + inline vec3f rot_y(double a) { + double xx = cos(-a) * x + sin(-a) * z; + double zz = cos(-a) * z - sin(-a) * x; + x = xx; + z = zz; + return *this; + } + inline void clamp(double min, double max) { + if (x < min) x = min; + if (y < min) y = min; + if (z < min) z = min; + if (x > max) x = max; + if (y > max) y = max; + if (z > max) z = max; + } + inline vec3f rot_z(double a) { + double yy = cos(a) * y + sin(a) * x; + double xx = cos(a) * x - sin(a) * y; + y = yy; + x = xx; + return *this; + } + inline vec3f invert() { + x = -x; + y = -y; + z = -z; + return *this; + } + inline vec3f frac() { + return vec3f( + x - double(int(x)), + y - double(int(y)), + z - double(int(z))); + } + + inline vec3f integer() { + return vec3f( + double(int(x)), + double(int(y)), + double(int(z))); + } + + inline double length() const { + return (double)sqrt(x * x + y * y + z * z); + } + + inline vec3f normalize(double desired_length = 1) { + double square = sqrt(x * x + y * y + z * z); + /* + if (square <= 0.00001f ) + { + x=1;y=0;z=0; + return *this; + }*/ + //double len = desired_length / square; + x /= square; + y /= square; + z /= square; + + return *this; + } + static vec3f normalize(vec3f a); + + static void random_init(); + static double random_double(); + static vec3f random(); + + static int random_number; + + double random_double_01(double a) { + double rnf = a * 14.434252 + a * 364.2343 + a * 4213.45352 + a * 2341.43255 + a * 254341.43535 + a * 223454341.3523534245 + 23453.423412; + int rni = ((int)rnf) % 100000; + return double(rni) / (100000.0f - 1.0f); + } + + vec3f random01_fxyz() { + x = (double)random_double_01(x); + y = (double)random_double_01(y); + z = (double)random_double_01(z); + return *this; + } +}; + +static vec3f barycentric(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c) { + vec3f v0 = b - a; + vec3f v1 = c - a; + vec3f v2 = p - a; + double d00 = v0.dot(v0); + double d01 = v0.dot(v1); + double d11 = v1.dot(v1); + double d20 = v2.dot(v0); + double d21 = v2.dot(v1); + double denom = d00 * d11 - d01 * d01; + double v = (d11 * d20 - d01 * d21) / denom; + double w = (d00 * d21 - d01 * d20) / denom; + double u = 1.0 - v - w; + return vec3f(u, v, w); +} + +static vec3f interpolate(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c, const vec3f attrs[3]) { + vec3f bary = barycentric(p, a, b, c); + vec3f out = vec3f(0, 0, 0); + out = out + attrs[0] * bary.x; + out = out + attrs[1] * bary.y; + out = out + attrs[2] * bary.z; + return out; +} + +static double min(double v1, double v2) { + return fmin(v1, v2); +} + +class SymetricMatrix { + +public: + // Constructor + + SymetricMatrix(double c = 0) { + for (unsigned int i = 0; i < 10; ++i) + m[i] = c; + } + + SymetricMatrix(double m11, double m12, double m13, double m14, + double m22, double m23, double m24, + double m33, double m34, + double m44) { + m[0] = m11; + m[1] = m12; + m[2] = m13; + m[3] = m14; + m[4] = m22; + m[5] = m23; + m[6] = m24; + m[7] = m33; + m[8] = m34; + m[9] = m44; + } + + // Make plane + + SymetricMatrix(double a, double b, double c, double d) { + m[0] = a * a; + m[1] = a * b; + m[2] = a * c; + m[3] = a * d; + m[4] = b * b; + m[5] = b * c; + m[6] = b * d; + m[7] = c * c; + m[8] = c * d; + m[9] = d * d; + } + + double operator[](int c) const { return m[c]; } + + // Determinant + + double det(int a11, int a12, int a13, + int a21, int a22, int a23, + int a31, int a32, int a33) { + double det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] + m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] - m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33]; + return det; + } + + const SymetricMatrix operator+(const SymetricMatrix &n) const { + return SymetricMatrix(m[0] + n[0], m[1] + n[1], m[2] + n[2], m[3] + n[3], + m[4] + n[4], m[5] + n[5], m[6] + n[6], + m[7] + n[7], m[8] + n[8], + m[9] + n[9]); + } + + SymetricMatrix &operator+=(const SymetricMatrix &n) { + m[0] += n[0]; + m[1] += n[1]; + m[2] += n[2]; + m[3] += n[3]; + m[4] += n[4]; + m[5] += n[5]; + m[6] += n[6]; + m[7] += n[7]; + m[8] += n[8]; + m[9] += n[9]; + return *this; + } + + double m[10]; +}; +/////////////////////////////////////////// + +struct BorderVertex { + int index; + int hash; +}; + +static bool compare_border_vertex(const BorderVertex &i1, const BorderVertex &i2) { + return (i1.hash < i2.hash); +} + +class FQMS { + +public: + struct Triangle { + int v[3]; + double err[4]; + int deleted, dirty; + vec3f n; + vec3f uvs[3]; + vec3f uv2s[3]; + Color color[3]; + int material; + }; + + struct Vertex { + vec3f p; + int tstart, tcount; + SymetricMatrix q; + int border; + bool seam; + bool foldover; + }; + + struct Ref { + int tid, tvertex; + }; + + std::vector triangles; + std::vector vertices; + std::vector refs; + + int _max_iteration_count; + int _max_lossless_iteration_count; + bool _enable_smart_link; + bool _preserve_border_dges; + bool _preserve_uv_seam_edges; + bool _preserve_uv_foldover_edges; + int _format; + double _vertex_link_distance; + + // Helper functions + + // + // Main simplification function + // + // target_count : target nr. of triangles + // agressiveness : sharpness to increase the threshold. + // 5..8 are good numbers + // more iterations yield higher quality + // + + void simplify_mesh(int target_count, double agressiveness = 7, bool verbose = false) { + ERR_FAIL_COND_MSG(_enable_smart_link, "FastQuadraticMeshSimplifier: enable_smart_link setting is not yet suppored!"); + + // init + for (unsigned int i = 0; i < triangles.size(); ++i) { + triangles[i].deleted = 0; + } + + // main iteration loop + int deleted_triangles = 0; + std::vector deleted0, deleted1; + int triangle_count = triangles.size(); + + for (int iteration = 0; iteration < _max_iteration_count; iteration++) { + if (triangle_count - deleted_triangles <= target_count) break; + + // update mesh once in a while + if (iteration % 5 == 0) { + update_mesh(iteration); + } + + // clear dirty flag + for (unsigned int i = 0; i < triangles.size(); ++i) { + triangles[i].dirty = 0; + } + + // + // All triangles with edges below the threshold will be removed + // + // The following numbers works well for most models. + // If it does not, try to adjust the 3 parameters + // + double threshold = 0.000000001 * pow(double(iteration + 3), agressiveness); + + // target number of triangles reached ? Then break + if ((verbose) && (iteration % 5 == 0)) { + print_line("iteration " + String::num(iteration) + " - triangles " + String::num(triangle_count - deleted_triangles) + " threshold " + String::num(threshold)); + } + + // remove vertices & mark deleted triangles + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle &t = triangles[i]; + + if (t.err[3] > threshold) + continue; + + if (t.deleted) + continue; + + if (t.dirty) + continue; + + for (int j = 0; j < 3; ++j) { + if (t.err[j] < threshold) { + + int i0 = t.v[j]; + Vertex &v0 = vertices[i0]; + int i1 = t.v[(j + 1) % 3]; + Vertex &v1 = vertices[i1]; + // Border check + if (v0.border != v1.border) + continue; + + //if (v0.border || v1.border) continue; + + // Compute vertex to collapse to + vec3f p; + calculate_error(i0, i1, p); + deleted0.resize(v0.tcount); // normals temporarily + deleted1.resize(v1.tcount); // normals temporarily + // don't remove if flipped + + if (flipped(p, i0, i1, v0, v1, deleted0)) + continue; + + if (flipped(p, i1, i0, v1, v0, deleted1)) + continue; + + if ((_format & VisualServer::ARRAY_FORMAT_TEX_UV) != 0) { + update_uvs(i0, v0, p, deleted0); + update_uvs(i0, v1, p, deleted1); + } + + if ((_format & VisualServer::ARRAY_FORMAT_TEX_UV2) != 0) { + update_uv2s(i0, v0, p, deleted0); + update_uv2s(i0, v1, p, deleted1); + } + + // not flipped, so remove edge + v0.p = p; + v0.q = v1.q + v0.q; + int tstart = refs.size(); + + update_triangles(i0, v0, deleted0, deleted_triangles); + update_triangles(i0, v1, deleted1, deleted_triangles); + + int tcount = refs.size() - tstart; + + if (tcount <= v0.tcount) { + // save ram + if (tcount) memcpy(&refs[v0.tstart], &refs[tstart], tcount * sizeof(Ref)); + } else { + // append + v0.tstart = tstart; + } + + v0.tcount = tcount; + break; + } + } + + // done? + if (triangle_count - deleted_triangles <= target_count) break; + } + } + // clean up mesh + compact_mesh(); + } //simplify_mesh() + + void simplify_mesh_lossless(bool verbose = false) { + ERR_FAIL_COND_MSG(_enable_smart_link, "FastQuadraticMeshSimplifier: enable_smart_link setting is not yet suppored!"); + + // init + for (unsigned int i = 0; i < triangles.size(); ++i) + triangles[i].deleted = 0; + + // main iteration loop + int deleted_triangles = 0; + std::vector deleted0, deleted1; + //unsigned int triangle_count = triangles.size(); + + for (int iteration = 0; iteration < _max_lossless_iteration_count; iteration++) { + // update mesh constantly + update_mesh(iteration); + // clear dirty flag + for (unsigned int i = 0; i < triangles.size(); ++i) + triangles[i].dirty = 0; + // + // All triangles with edges below the threshold will be removed + // + // The following numbers works well for most models. + // If it does not, try to adjust the 3 parameters + // + double threshold = DBL_EPSILON; //1.0E-3 EPS; + if (verbose) { + print_line("lossless iteration " + String::num(iteration)); + } + + // remove vertices & mark deleted triangles + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle &t = triangles[i]; + + if (t.err[3] > threshold) + continue; + + if (t.deleted) + continue; + + if (t.dirty) + continue; + + for (int j = 0; j < 3; ++j) { + if (t.err[j] < threshold) { + int i0 = t.v[j]; + Vertex &v0 = vertices[i0]; + int i1 = t.v[(j + 1) % 3]; + Vertex &v1 = vertices[i1]; + + // Border check + if (v0.border != v1.border) + continue; + + // Compute vertex to collapse to + vec3f p; + calculate_error(i0, i1, p); + + deleted0.resize(v0.tcount); // normals temporarily + deleted1.resize(v1.tcount); // normals temporarily + + // don't remove if flipped + if (flipped(p, i0, i1, v0, v1, deleted0)) + continue; + + if (flipped(p, i1, i0, v1, v0, deleted1)) + continue; + + if ((_format & VisualServer::ARRAY_FORMAT_TEX_UV) != 0) { + update_uvs(i0, v0, p, deleted0); + update_uvs(i0, v1, p, deleted1); + } + + if ((_format & VisualServer::ARRAY_FORMAT_TEX_UV2) != 0) { + update_uv2s(i0, v0, p, deleted0); + update_uv2s(i0, v1, p, deleted1); + } + + // not flipped, so remove edge + v0.p = p; + v0.q = v1.q + v0.q; + int tstart = refs.size(); + + update_triangles(i0, v0, deleted0, deleted_triangles); + update_triangles(i0, v1, deleted1, deleted_triangles); + + int tcount = refs.size() - tstart; + + if (tcount <= v0.tcount) { + // save ram + if (tcount) memcpy(&refs[v0.tstart], &refs[tstart], tcount * sizeof(Ref)); + } else { + // append + v0.tstart = tstart; + } + + v0.tcount = tcount; + break; + } + } + } + + if (deleted_triangles <= 0) + break; + + deleted_triangles = 0; + } //for each iteration + // clean up mesh + compact_mesh(); + } //simplify_mesh_lossless() + + // Check if a triangle flips when this edge is removed + + bool flipped(vec3f p, int i0, int i1, Vertex &v0, Vertex &v1, std::vector &deleted) { + + for (int k = 0; k < v0.tcount; ++k) { + Triangle &t = triangles[refs[v0.tstart + k].tid]; + + if (t.deleted) + continue; + + int s = refs[v0.tstart + k].tvertex; + int id1 = t.v[(s + 1) % 3]; + int id2 = t.v[(s + 2) % 3]; + + if (id1 == i1 || id2 == i1) // delete ? + { + deleted[k] = 1; + continue; + } + vec3f d1 = vertices[id1].p - p; + d1.normalize(); + vec3f d2 = vertices[id2].p - p; + d2.normalize(); + if (fabs(d1.dot(d2)) > 0.999) return true; + vec3f n; + n.cross(d1, d2); + n.normalize(); + deleted[k] = 0; + if (n.dot(t.n) < 0.2) return true; + } + return false; + } + + // update_uvs + + void update_uvs(int i0, const Vertex &v, const vec3f &p, std::vector &deleted) { + for (int k = 0; k < v.tcount; ++k) { + Ref &r = refs[v.tstart + k]; + Triangle &t = triangles[r.tid]; + + if (t.deleted) + continue; + + if (deleted[k]) + continue; + + vec3f p1 = vertices[t.v[0]].p; + vec3f p2 = vertices[t.v[1]].p; + vec3f p3 = vertices[t.v[2]].p; + + t.uvs[r.tvertex] = interpolate(p, p1, p2, p3, t.uvs); + } + } + + void update_uv2s(int i0, const Vertex &v, const vec3f &p, std::vector &deleted) { + for (int k = 0; k < v.tcount; ++k) { + Ref &r = refs[v.tstart + k]; + Triangle &t = triangles[r.tid]; + + if (t.deleted) + continue; + + if (deleted[k]) + continue; + + vec3f p1 = vertices[t.v[0]].p; + vec3f p2 = vertices[t.v[1]].p; + vec3f p3 = vertices[t.v[2]].p; + + t.uv2s[r.tvertex] = interpolate(p, p1, p2, p3, t.uv2s); + } + } + + // Update triangle connections and edge error after a edge is collapsed + + void update_triangles(int i0, Vertex &v, std::vector &deleted, int &deleted_triangles) { + vec3f p; + + for (int k = 0; k < v.tcount; ++k) { + Ref &r = refs[v.tstart + k]; + Triangle &t = triangles[r.tid]; + + if (t.deleted) + continue; + + if (deleted[k]) { + t.deleted = 1; + deleted_triangles++; + continue; + } + + t.v[r.tvertex] = i0; + + t.dirty = 1; + t.err[0] = calculate_error(t.v[0], t.v[1], p); + t.err[1] = calculate_error(t.v[1], t.v[2], p); + t.err[2] = calculate_error(t.v[2], t.v[0], p); + t.err[3] = min(t.err[0], min(t.err[1], t.err[2])); + refs.push_back(r); + } + } + + // compact triangles, compute edge error and build reference list + + void update_mesh(int iteration) { + if (iteration > 0) // compact triangles + { + int dst = 0; + for (unsigned int i = 0; i < triangles.size(); ++i) { + if (!triangles[i].deleted) { + triangles[dst++] = triangles[i]; + } + } + + triangles.resize(dst); + } + + // + // Init Quadrics by Plane & Edge Errors + // + // required at the beginning ( iteration == 0 ) + // recomputing during the simplification is not required, + // but mostly improves the result for closed meshes + // + if (iteration == 0) { + for (unsigned int i = 0; i < vertices.size(); ++i) { + vertices[i].q = SymetricMatrix(0.0); + } + + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle &t = triangles[i]; + vec3f n, p[3]; + for (int j = 0; j < 3; ++j) { + p[j] = vertices[t.v[j]].p; + } + + n.cross(p[1] - p[0], p[2] - p[0]); + n.normalize(); + t.n = n; + for (int j = 0; j < 3; ++j) { + vertices[t.v[j]].q = vertices[t.v[j]].q + SymetricMatrix(n.x, n.y, n.z, -n.dot(p[0])); + } + } + for (unsigned int i = 0; i < triangles.size(); ++i) { + // Calc Edge Error + Triangle &t = triangles[i]; + vec3f p; + + for (int j = 0; j < 3; ++j) { + t.err[j] = calculate_error(t.v[j], t.v[(j + 1) % 3], p); + } + + t.err[3] = min(t.err[0], min(t.err[1], t.err[2])); + } + } + + // Init Reference ID list + for (unsigned int i = 0; i < vertices.size(); ++i) { + vertices[i].tstart = 0; + vertices[i].tcount = 0; + } + + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle &t = triangles[i]; + + for (int j = 0; j < 3; ++j) { + vertices[t.v[j]].tcount++; + } + } + + int tstart = 0; + for (unsigned int i = 0; i < vertices.size(); ++i) { + Vertex &v = vertices[i]; + v.tstart = tstart; + tstart += v.tcount; + v.tcount = 0; + } + + // Write References + refs.resize(triangles.size() * 3); + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle &t = triangles[i]; + + for (unsigned int j = 0; j < 3; ++j) { + Vertex &v = vertices[t.v[j]]; + refs[v.tstart + v.tcount].tid = i; + refs[v.tstart + v.tcount].tvertex = j; + v.tcount++; + } + } + + // Identify boundary : vertices[].border=0,1 + if (iteration == 0) { + std::vector vcount, vids; + + for (unsigned int i = 0; i < vertices.size(); ++i) { + Vertex &v = vertices[i]; + + v.border = 0; + v.seam = false; + v.foldover = false; + } + + int border_vertex_count = 0; + double border_min_x = DBL_MIN; + double border_max_x = DBL_MAX; + + for (unsigned int i = 0; i < vertices.size(); ++i) { + Vertex &v = vertices[i]; + vcount.clear(); + vids.clear(); + + for (int j = 0; j < v.tcount; ++j) { + int kt = refs[v.tstart + j].tid; + Triangle &t = triangles[kt]; + + for (int k = 0; k < 3; ++k) { + unsigned int ofs = 0; + int id = t.v[k]; + + while (ofs < vcount.size()) { + if (vids[ofs] == id) + break; + + ofs++; + } + + if (ofs == vcount.size()) { + vcount.push_back(1); + vids.push_back(id); + } else { + vcount[ofs]++; + } + } + } + + for (unsigned int j = 0; j < vcount.size(); ++j) { + if (vcount[j] == 1) { + Vertex &vv = vertices[vids[j]]; + + vv.border = 1; + ++border_vertex_count; + + if (_enable_smart_link) { + if (vv.p.x < border_min_x) + border_min_x = vv.p.x; + + if (vv.p.x < border_max_x) + border_max_x = vv.p.x; + } + } + } + + if (_enable_smart_link) { + std::vector border_vertices; + + double border_area_width = border_max_x - border_min_x; + + for (unsigned int j = 0; j < vertices.size(); j++) { + Vertex &vj = vertices[i]; + + if (v.border) { + BorderVertex bv; + bv.hash = static_cast(((((vj.p.x - border_min_x) / border_area_width) * 2.0) - 1.0) * INT_MAX); + bv.index = j; + + border_vertices.push_back(bv); + } + } + + std::sort(border_vertices.begin(), border_vertices.end(), compare_border_vertex); + + // Calculate the maximum hash distance based on the maximum vertex link distance + int tdst = static_cast((_vertex_link_distance / border_area_width) * INT_MAX); + int hash_max_distance = MAX(tdst, 1); + + // Then find identical border vertices and bind them together as one + for (unsigned int j = 0; j < border_vertices.size(); ++j) { + BorderVertex &bv = border_vertices[j]; + + if (bv.index == -1) + continue; + + //var myPoint = vertices[myIndex].p; + for (unsigned int k = i + 1; k < border_vertices.size(); ++k) { + BorderVertex &obv = border_vertices[k]; + + //int otherIndex = obv.index; + //var otherPoint = vertices[otherIndex].p; + if (obv.index == -1) + continue; + + else if ((obv.hash - bv.hash) > hash_max_distance) // There is no point to continue beyond this point + break; + + Vertex &vj = vertices[j]; + Vertex &ov = vertices[k]; + + double sqr_x = ((vj.p.x - ov.p.x) * (vj.p.x - ov.p.x)); + double sqr_y = ((vj.p.y - ov.p.y) * (vj.p.y - ov.p.y)); + double sqr_z = ((vj.p.z - ov.p.z) * (vj.p.z - ov.p.z)); + double sqr_magnitude = sqr_x + sqr_y + sqr_z; + + if (sqr_magnitude <= _vertex_link_distance) { + obv.index = -1; // NOTE: This makes sure that the "other" vertex is not processed again + vj.border = false; + ov.border = false; + /* + if (AreUVsTheSame(0, myIndex, otherIndex)) { + vertices[myIndex].foldover = true; + vertices[otherIndex].foldover = true; + } else { + vertices[myIndex].seam = true; + vertices[otherIndex].seam = true; + } + + int other_triangle_count = ov.tcount; + int other_triangle_start = ov.tstart; + for (int k = 0; k < other_triangle_count; k++) { + Ref &r = refs[other_triangle_start + k]; + + Triangle &t = triangles[r.tid]; + t.v[r.tvertex] = myIndex; + } + */ + } + } + } + + // Update the references again + //update_references(); + } + } + } + } + + // Finally compact mesh before exiting + + void compact_mesh() { + int dst = 0; + + for (unsigned int i = 0; i < vertices.size(); ++i) { + vertices[i].tcount = 0; + } + + for (unsigned int i = 0; i < triangles.size(); ++i) { + if (!triangles[i].deleted) { + Triangle &t = triangles[i]; + + triangles[dst++] = t; + + for (int j = 0; j < 3; ++j) { + vertices[t.v[j]].tcount = 1; + } + } + } + + triangles.resize(dst); + dst = 0; + for (unsigned int i = 0; i < vertices.size(); ++i) { + if (vertices[i].tcount) { + vertices[i].tstart = dst; + vertices[dst].p = vertices[i].p; + dst++; + } + } + + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle &t = triangles[i]; + + for (int j = 0; j < 3; ++j) { + t.v[j] = vertices[t.v[j]].tstart; + } + } + vertices.resize(dst); + } + + // Error between vertex and Quadric + + double vertex_error(SymetricMatrix q, double x, double y, double z) { + return q[0] * x * x + 2 * q[1] * x * y + 2 * q[2] * x * z + 2 * q[3] * x + q[4] * y * y + 2 * q[5] * y * z + 2 * q[6] * y + q[7] * z * z + 2 * q[8] * z + q[9]; + } + + // Error for one edge + + double calculate_error(int id_v1, int id_v2, vec3f &p_result) { + // compute interpolated vertex + + SymetricMatrix q = vertices[id_v1].q + vertices[id_v2].q; + bool border = vertices[id_v1].border & vertices[id_v2].border; + double error = 0; + double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7); + if (det != 0 && !border) { + + // q_delta is invertible + p_result.x = -1 / det * (q.det(1, 2, 3, 4, 5, 6, 5, 7, 8)); // vx = A41/det(q_delta) + p_result.y = 1 / det * (q.det(0, 2, 3, 1, 5, 6, 2, 7, 8)); // vy = A42/det(q_delta) + p_result.z = -1 / det * (q.det(0, 1, 3, 1, 4, 6, 2, 5, 8)); // vz = A43/det(q_delta) + + error = vertex_error(q, p_result.x, p_result.y, p_result.z); + } else { + // det = 0 -> try to find best result + vec3f p1 = vertices[id_v1].p; + vec3f p2 = vertices[id_v2].p; + vec3f p3 = (p1 + p2) / 2; + double error1 = vertex_error(q, p1.x, p1.y, p1.z); + double error2 = vertex_error(q, p2.x, p2.y, p2.z); + double error3 = vertex_error(q, p3.x, p3.y, p3.z); + error = min(error1, min(error2, error3)); + if (error1 == error) p_result = p1; + if (error2 == error) p_result = p2; + if (error3 == error) p_result = p3; + } + return error; + } + + void initialize(const Array &arrays) { + ERR_FAIL_COND(arrays.size() != ArrayMesh::ARRAY_MAX); + + PoolVector pvertices = arrays.get(ArrayMesh::ARRAY_VERTEX); + PoolVector pnormals = arrays.get(ArrayMesh::ARRAY_NORMAL); + PoolVector pcolors = arrays.get(ArrayMesh::ARRAY_COLOR); + PoolVector puvs = arrays.get(ArrayMesh::ARRAY_TEX_UV); + PoolVector puv2s = arrays.get(ArrayMesh::ARRAY_TEX_UV2); + + _format = 0; + + if (pnormals.size() > 0) + _format |= VisualServer::ARRAY_FORMAT_NORMAL; + + if (pcolors.size() > 0) + _format |= VisualServer::ARRAY_FORMAT_COLOR; + + if (puvs.size() > 0) + _format |= VisualServer::ARRAY_FORMAT_TEX_UV; + + if (puv2s.size() > 0) + _format |= VisualServer::ARRAY_FORMAT_TEX_UV2; + + //_vertices.resize(vertices.size()); + for (int i = 0; i < pvertices.size(); ++i) { + Vector3 v3 = pvertices[i]; + + Vertex vert; + vert.p.x = v3.x; + vert.p.y = v3.y; + vert.p.z = v3.z; + + vertices.push_back(vert); + } + + std::vector uvs; + + for (int i = 0; i < puvs.size(); ++i) { + Vector2 v2 = puvs[i]; + + vec3f uv; + uv.x = v2.x; + uv.y = v2.y; + uv.z = 0; + + uvs.push_back(uv); + } + + PoolVector pindices = arrays.get(ArrayMesh::ARRAY_INDEX); + + if ((pindices.size() % 3) != 0) { + ERR_FAIL_MSG("The index array length must be a multiple of 3 in order to represent triangles."); + } + + //std::vector > uvMap; + + for (int i = 0; i < pindices.size(); i += 3) { + Triangle t; + + int i0 = pindices[i]; + int i1 = pindices[i + 1]; + int i2 = pindices[i + 2]; + + t.v[0] = i0; + t.v[1] = i1; + t.v[2] = i2; + + if ((_format & VisualServer::ARRAY_FORMAT_COLOR) != 0) { + t.color[0] = pcolors[i0]; + t.color[1] = pcolors[i1]; + t.color[2] = pcolors[i2]; + } + + if ((_format & VisualServer::ARRAY_FORMAT_NORMAL) != 0) { + Vector3 v = pnormals[i0]; + + vec3f vn(v.x, v.y, v.z); + + t.n = vn; + } + + if ((_format & VisualServer::ARRAY_FORMAT_TEX_UV) != 0) { + Vector2 tv0 = puvs[i0]; + Vector2 tv1 = puvs[i1]; + Vector2 tv2 = puvs[i2]; + + t.uvs[0] = vec3f(tv0.x, tv0.y, 0); + t.uvs[1] = vec3f(tv1.x, tv1.y, 0); + t.uvs[2] = vec3f(tv2.x, tv2.y, 0); + } + + if ((_format & VisualServer::ARRAY_FORMAT_TEX_UV2) != 0) { + Vector2 tv0 = puvs[i0]; + Vector2 tv1 = puvs[i1]; + Vector2 tv2 = puvs[i2]; + + t.uv2s[0] = vec3f(tv0.x, tv0.y, 0); + t.uv2s[1] = vec3f(tv1.x, tv1.y, 0); + t.uv2s[2] = vec3f(tv2.x, tv2.y, 0); + } + + //std::vector indices; + //indices.push_back(pindices[i]); + //indices.push_back(pindices[i + 1]); + //indices.push_back(pindices[i + 2]); + //uvMap.push_back(indices); + + t.material = 0; + triangles.push_back(t); + } + + //if (uvs.size()) { + // for (int i = 0; i < triangles.size(); ++i) { + // for (int j = 0; j < 3; ++j) + // triangles[i].uvs[j] = uvs[uvMap[i][j]]; + // } + //} + } + + Array get_arrays() { + Array arr; + + arr.resize(ArrayMesh::ARRAY_MAX); + + PoolVector pvertices; + PoolVector pnormals; + PoolVector pcolors; + PoolVector puvs; + PoolVector puv2s; + PoolVector pindices; + + pvertices.resize(vertices.size()); + for (int i = 0; i < pvertices.size(); ++i) { + Vector3 v; + vec3f vf = vertices[i].p; + v.x = vf.x; + v.y = vf.y; + v.z = vf.z; + + pvertices.set(i, v); + } + + if ((_format & VisualServer::ARRAY_FORMAT_COLOR) != 0) { + pcolors.resize(pvertices.size()); + + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle t = triangles[i]; + + if (!t.deleted) { + pcolors.set(t.v[0], t.color[0]); + pcolors.set(t.v[1], t.color[1]); + pcolors.set(t.v[2], t.color[2]); + } + } + + arr.set(ArrayMesh::ARRAY_COLOR, pcolors); + } + + if ((_format & VisualServer::ARRAY_FORMAT_NORMAL) != 0) { + pnormals.resize(pvertices.size()); + + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle t = triangles[i]; + + if (!t.deleted) { + Vector3 v(t.n.x, t.n.y, t.n.z); + + pnormals.set(t.v[0], v); + pnormals.set(t.v[1], v); + pnormals.set(t.v[2], v); + } + } + + arr.set(ArrayMesh::ARRAY_NORMAL, pnormals); + } + + if ((_format & VisualServer::ARRAY_FORMAT_TEX_UV) != 0) { + puvs.resize(pvertices.size()); + + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle t = triangles[i]; + + if (!t.deleted) { + Vector2 v1(t.uvs[0].x, t.uvs[0].y); + Vector2 v2(t.uvs[1].x, t.uvs[1].y); + Vector2 v3(t.uvs[2].x, t.uvs[2].y); + + puvs.set(t.v[0], v1); + puvs.set(t.v[1], v2); + puvs.set(t.v[2], v3); + } + } + + arr.set(ArrayMesh::ARRAY_TEX_UV, puvs); + } + + if ((_format & VisualServer::ARRAY_FORMAT_TEX_UV2) != 0) { + puv2s.resize(pvertices.size()); + + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle t = triangles[i]; + + if (!t.deleted) { + Vector2 v1(t.uv2s[0].x, t.uv2s[0].y); + Vector2 v2(t.uv2s[1].x, t.uv2s[1].y); + Vector2 v3(t.uv2s[2].x, t.uv2s[2].y); + + puv2s.set(t.v[0], v1); + puv2s.set(t.v[1], v2); + puv2s.set(t.v[2], v3); + } + } + + arr.set(ArrayMesh::ARRAY_TEX_UV2, puv2s); + } + + //pindices.resize(_mu_triangles.size() * 3); + for (unsigned int i = 0; i < triangles.size(); ++i) { + Triangle t = triangles[i]; + + if (!t.deleted) { + pindices.push_back(t.v[0]); + pindices.push_back(t.v[1]); + pindices.push_back(t.v[2]); + + //print_error(String::num(t.v[0]) + " " + String::num(t.v[1]) + " " + String::num(t.v[2]) + " "); + } + } + + arr.set(ArrayMesh::ARRAY_VERTEX, pvertices); + arr.set(ArrayMesh::ARRAY_INDEX, pindices); + + return arr; + } + + FQMS() { + _max_iteration_count = 100; + _max_lossless_iteration_count = 9990; + _enable_smart_link = false; + _preserve_border_dges = false; + _preserve_uv_seam_edges = false; + _preserve_uv_foldover_edges = false; + _format = 0; + _vertex_link_distance = sqrt(DBL_EPSILON); + } + + ~FQMS() { + triangles.clear(); + vertices.clear(); + refs.clear(); + } +}; // namespace Simplify + +} // namespace Simplify + +// namespace Simplify +/////////////////////////////////////////// + +#endif