diff options
116 files changed, 6411 insertions, 4730 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 8b8aff3e9e..f939fca984 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -402,7 +402,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b #ifdef OSX_ENABLED if (!found) { // Attempt to load PCK from macOS .app bundle resources. - found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().plus_file(exec_basename + ".pck")); + found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().plus_file(exec_basename + ".pck")) || _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().plus_file(exec_filename + ".pck")); } #endif diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp index d12108bca0..33ba0ba704 100644 --- a/core/crypto/crypto.cpp +++ b/core/crypto/crypto.cpp @@ -65,6 +65,22 @@ void X509Certificate::_bind_methods() { ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load); } +/// HMACContext + +void HMACContext::_bind_methods() { + ClassDB::bind_method(D_METHOD("start", "hash_type", "key"), &HMACContext::start); + ClassDB::bind_method(D_METHOD("update", "data"), &HMACContext::update); + ClassDB::bind_method(D_METHOD("finish"), &HMACContext::finish); +} + +HMACContext *(*HMACContext::_create)() = nullptr; +HMACContext *HMACContext::create() { + if (_create) { + return _create(); + } + ERR_FAIL_V_MSG(nullptr, "HMACContext is not available when the mbedtls module is disabled."); +} + /// Crypto void (*Crypto::_load_default_certificates)(String p_path) = nullptr; @@ -82,6 +98,35 @@ void Crypto::load_default_certificates(String p_path) { } } +PackedByteArray Crypto::hmac_digest(HashingContext::HashType p_hash_type, PackedByteArray p_key, PackedByteArray p_msg) { + Ref<HMACContext> ctx = Ref<HMACContext>(HMACContext::create()); + ERR_FAIL_COND_V_MSG(ctx.is_null(), PackedByteArray(), "HMAC is not available witout mbedtls module."); + Error err = ctx->start(p_hash_type, p_key); + ERR_FAIL_COND_V(err != OK, PackedByteArray()); + err = ctx->update(p_msg); + ERR_FAIL_COND_V(err != OK, PackedByteArray()); + return ctx->finish(); +} + +// Compares two HMACS for equality without leaking timing information in order to prevent timing attakcs. +// @see: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy +bool Crypto::constant_time_compare(PackedByteArray p_trusted, PackedByteArray p_received) { + const uint8_t *t = p_trusted.ptr(); + const uint8_t *r = p_received.ptr(); + int tlen = p_trusted.size(); + int rlen = p_received.size(); + // If the lengths are different then nothing else matters. + if (tlen != rlen) { + return false; + } + + uint8_t v = 0; + for (int i = 0; i < tlen; i++) { + v |= t[i] ^ r[i]; + } + return v == 0; +} + void Crypto::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_random_bytes", "size"), &Crypto::generate_random_bytes); ClassDB::bind_method(D_METHOD("generate_rsa", "size"), &Crypto::generate_rsa); @@ -90,6 +135,8 @@ void Crypto::_bind_methods() { ClassDB::bind_method(D_METHOD("verify", "hash_type", "hash", "signature", "key"), &Crypto::verify); ClassDB::bind_method(D_METHOD("encrypt", "key", "plaintext"), &Crypto::encrypt); ClassDB::bind_method(D_METHOD("decrypt", "key", "ciphertext"), &Crypto::decrypt); + ClassDB::bind_method(D_METHOD("hmac_digest", "hash_type", "key", "msg"), &Crypto::hmac_digest); + ClassDB::bind_method(D_METHOD("constant_time_compare", "trusted", "received"), &Crypto::constant_time_compare); } /// Resource loader/saver diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h index 8325f043bf..5cacddaea0 100644 --- a/core/crypto/crypto.h +++ b/core/crypto/crypto.h @@ -67,6 +67,23 @@ public: virtual Error save(String p_path) = 0; }; +class HMACContext : public Reference { + GDCLASS(HMACContext, Reference); + +protected: + static void _bind_methods(); + static HMACContext *(*_create)(); + +public: + static HMACContext *create(); + + virtual Error start(HashingContext::HashType p_hash_type, PackedByteArray p_key) = 0; + virtual Error update(PackedByteArray p_data) = 0; + virtual PackedByteArray finish() = 0; + + HMACContext() {} +}; + class Crypto : public Reference { GDCLASS(Crypto, Reference); @@ -88,6 +105,12 @@ public: virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_plaintext) = 0; virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_ciphertext) = 0; + PackedByteArray hmac_digest(HashingContext::HashType p_hash_type, PackedByteArray p_key, PackedByteArray p_msg); + + // Compares two PackedByteArrays for equality without leaking timing information in order to prevent timing attacks. + // @see: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy + bool constant_time_compare(PackedByteArray p_trusted, PackedByteArray p_received); + Crypto() {} }; diff --git a/core/doc_data.cpp b/core/doc_data.cpp new file mode 100644 index 0000000000..d84ac6d05b --- /dev/null +++ b/core/doc_data.cpp @@ -0,0 +1,126 @@ +/*************************************************************************/ +/* doc_data.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "doc_data.h" + +void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const PropertyInfo &p_retinfo) { + if (p_retinfo.type == Variant::INT && p_retinfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + p_method.return_enum = p_retinfo.class_name; + if (p_method.return_enum.begins_with("_")) { //proxy class + p_method.return_enum = p_method.return_enum.substr(1, p_method.return_enum.length()); + } + p_method.return_type = "int"; + } else if (p_retinfo.class_name != StringName()) { + p_method.return_type = p_retinfo.class_name; + } else if (p_retinfo.type == Variant::ARRAY && p_retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) { + p_method.return_type = p_retinfo.hint_string + "[]"; + } else if (p_retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { + p_method.return_type = p_retinfo.hint_string; + } else if (p_retinfo.type == Variant::NIL && p_retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { + p_method.return_type = "Variant"; + } else if (p_retinfo.type == Variant::NIL) { + p_method.return_type = "void"; + } else { + p_method.return_type = Variant::get_type_name(p_retinfo.type); + } +} + +void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const PropertyInfo &p_arginfo) { + p_argument.name = p_arginfo.name; + + if (p_arginfo.type == Variant::INT && p_arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + p_argument.enumeration = p_arginfo.class_name; + if (p_argument.enumeration.begins_with("_")) { //proxy class + p_argument.enumeration = p_argument.enumeration.substr(1, p_argument.enumeration.length()); + } + p_argument.type = "int"; + } else if (p_arginfo.class_name != StringName()) { + p_argument.type = p_arginfo.class_name; + } else if (p_arginfo.type == Variant::ARRAY && p_arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { + p_argument.type = p_arginfo.hint_string + "[]"; + } else if (p_arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { + p_argument.type = p_arginfo.hint_string; + } else if (p_arginfo.type == Variant::NIL) { + // Parameters cannot be void, so PROPERTY_USAGE_NIL_IS_VARIANT is not necessary + p_argument.type = "Variant"; + } else { + p_argument.type = Variant::get_type_name(p_arginfo.type); + } +} + +void DocData::property_doc_from_scriptmemberinfo(DocData::PropertyDoc &p_property, const ScriptMemberInfo &p_memberinfo) { + p_property.name = p_memberinfo.propinfo.name; + p_property.description = p_memberinfo.doc_string; + + if (p_memberinfo.propinfo.type == Variant::OBJECT) { + p_property.type = p_memberinfo.propinfo.class_name; + } else if (p_memberinfo.propinfo.type == Variant::NIL && p_memberinfo.propinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { + p_property.type = "Variant"; + } else { + p_property.type = Variant::get_type_name(p_memberinfo.propinfo.type); + } + + p_property.setter = p_memberinfo.setter; + p_property.getter = p_memberinfo.getter; + + if (p_memberinfo.has_default_value && p_memberinfo.default_value.get_type() != Variant::OBJECT) { + p_property.default_value = p_memberinfo.default_value.get_construct_string().replace("\n", ""); + } + + p_property.overridden = false; +} + +void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc) { + p_method.name = p_methodinfo.name; + p_method.description = p_desc; + + return_doc_from_retinfo(p_method, p_methodinfo.return_val); + + for (int i = 0; i < p_methodinfo.arguments.size(); i++) { + DocData::ArgumentDoc argument; + argument_doc_from_arginfo(argument, p_methodinfo.arguments[i]); + int default_arg_index = i - (p_methodinfo.arguments.size() - p_methodinfo.default_arguments.size()); + if (default_arg_index >= 0) { + Variant default_arg = p_methodinfo.default_arguments[default_arg_index]; + argument.default_value = default_arg.get_construct_string(); + } + p_method.arguments.push_back(argument); + } +} + +void DocData::constant_doc_from_variant(DocData::ConstantDoc &p_const, const StringName &p_name, const Variant &p_value, const String &p_desc) { + p_const.name = p_name; + p_const.value = p_value; + p_const.description = p_desc; +} + +void DocData::signal_doc_from_methodinfo(DocData::MethodDoc &p_signal, const MethodInfo &p_methodinfo, const String &p_desc) { + return method_doc_from_methodinfo(p_signal, p_methodinfo, p_desc); +} diff --git a/editor/doc_data.h b/core/doc_data.h index 2cb475d137..bf016792ea 100644 --- a/editor/doc_data.h +++ b/core/doc_data.h @@ -35,6 +35,16 @@ #include "core/templates/map.h" #include "core/variant/variant.h" +struct ScriptMemberInfo { + PropertyInfo propinfo; + String doc_string; + StringName setter; + StringName getter; + + bool has_default_value = false; + Variant default_value; +}; + class DocData { public: struct ArgumentDoc { @@ -87,6 +97,12 @@ public: } }; + struct EnumDoc { + String name = "@unnamed_enum"; + String description; + Vector<DocData::ConstantDoc> values; + }; + struct PropertyDoc { String name; String type; @@ -115,27 +131,22 @@ public: Vector<MethodDoc> methods; Vector<MethodDoc> signals; Vector<ConstantDoc> constants; + Map<String, String> enums; Vector<PropertyDoc> properties; Vector<PropertyDoc> theme_properties; + bool is_script_doc = false; + String script_path; bool operator<(const ClassDoc &p_class) const { return name < p_class.name; } }; - String version; - - Map<String, ClassDoc> class_list; - Error _load(Ref<XMLParser> parser); - -public: - void merge_from(const DocData &p_data); - void remove_from(const DocData &p_data); - void generate(bool p_basic_types = false); - Error load_classes(const String &p_dir); - static Error erase_classes(const String &p_dir); - Error save_classes(const String &p_default_path, const Map<String, String> &p_class_path); - - Error load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size); + static void return_doc_from_retinfo(DocData::MethodDoc &p_method, const PropertyInfo &p_retinfo); + static void argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const PropertyInfo &p_arginfo); + static void property_doc_from_scriptmemberinfo(DocData::PropertyDoc &p_property, const ScriptMemberInfo &p_memberinfo); + static void method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc); + static void constant_doc_from_variant(DocData::ConstantDoc &p_const, const StringName &p_name, const Variant &p_value, const String &p_desc); + static void signal_doc_from_methodinfo(DocData::MethodDoc &p_signal, const MethodInfo &p_methodinfo, const String &p_desc); }; #endif // DOC_DATA_H diff --git a/core/input/input.cpp b/core/input/input.cpp index 00a7e63a73..153656e44a 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -1211,7 +1211,7 @@ void Input::parse_mapping(String p_mapping) { ERR_CONTINUE_MSG(output.length() < 1 || input.length() < 2, String(entry[idx] + "\nInvalid device mapping entry: " + entry[idx])); - if (output == "platform") { + if (output == "platform" || output == "hint") { continue; } diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 41bc5e84b0..e04e642f6b 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -84,10 +84,6 @@ Ref<InputEvent> InputEvent::xformed_by(const Transform2D &p_xform, const Vector2 return Ref<InputEvent>((InputEvent *)this); } -String InputEvent::as_text() const { - return String(); -} - bool InputEvent::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const { return false; } @@ -198,6 +194,33 @@ void InputEventWithModifiers::set_modifiers_from_event(const InputEventWithModif set_metakey(event->get_metakey()); } +String InputEventWithModifiers::as_text() const { + Vector<String> mod_names; + + if (get_control()) { + mod_names.push_back(find_keycode_name(KEY_CONTROL)); + } + if (get_shift()) { + mod_names.push_back(find_keycode_name(KEY_SHIFT)); + } + if (get_alt()) { + mod_names.push_back(find_keycode_name(KEY_ALT)); + } + if (get_metakey()) { + mod_names.push_back(find_keycode_name(KEY_META)); + } + + if (!mod_names.empty()) { + return String("+").join(mod_names); + } else { + return "None"; + } +} + +String InputEventWithModifiers::to_string() { + return as_text(); +} + void InputEventWithModifiers::_bind_methods() { ClassDB::bind_method(D_METHOD("set_store_command", "enable"), &InputEventWithModifiers::set_store_command); ClassDB::bind_method(D_METHOD("is_storing_command"), &InputEventWithModifiers::is_storing_command); @@ -326,24 +349,31 @@ uint32_t InputEventKey::get_physical_keycode_with_modifiers() const { } String InputEventKey::as_text() const { - String kc = keycode_get_string(keycode); + String kc; + + if (keycode == 0) { + kc = keycode_get_string(physical_keycode) + " (" + RTR("Physical") + ")"; + } else { + kc = keycode_get_string(keycode); + } + if (kc == String()) { return kc; } - if (get_metakey()) { - kc = find_keycode_name(KEY_META) + ("+" + kc); - } - if (get_alt()) { - kc = find_keycode_name(KEY_ALT) + ("+" + kc); - } - if (get_shift()) { - kc = find_keycode_name(KEY_SHIFT) + ("+" + kc); - } - if (get_control()) { - kc = find_keycode_name(KEY_CONTROL) + ("+" + kc); + String mods_text = InputEventWithModifiers::as_text(); + return mods_text == "" ? kc : mods_text + "+" + kc; +} + +String InputEventKey::to_string() { + String p = is_pressed() ? "true" : "false"; + String e = is_echo() ? "true" : "false"; + + if (keycode == 0) { + return vformat("InputEventKey: keycode=%s mods=%s physical=%s pressed=%s echo=%s", itos(physical_keycode) + " " + keycode_get_string(physical_keycode), InputEventWithModifiers::as_text(), "true", p, e); + } else { + return vformat("InputEventKey: keycode=%s mods=%s physical=%s pressed=%s echo=%s", itos(keycode) + " " + keycode_get_string(keycode), InputEventWithModifiers::as_text(), "false", p, e); } - return kc; } bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const { @@ -536,41 +566,74 @@ bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event, bool *p return match; } +static const char *_mouse_button_descriptions[9] = { + TTRC("Left Mouse Button"), + TTRC("Right Mouse Button"), + TTRC("Middle Mouse Button"), + TTRC("Mouse Wheel Up"), + TTRC("Mouse Wheel Down"), + TTRC("Mouse Wheel Left"), + TTRC("Mouse Wheel Right"), + TTRC("Mouse Thumb Button 1"), + TTRC("Mouse Thumb Button 2") +}; + String InputEventMouseButton::as_text() const { - String button_index_string = ""; - switch (get_button_index()) { + // Modifiers + String mods_text = InputEventWithModifiers::as_text(); + String full_string = mods_text == "" ? "" : mods_text + "+"; + + // Button + int idx = get_button_index(); + switch (idx) { case BUTTON_LEFT: - button_index_string = "BUTTON_LEFT"; - break; case BUTTON_RIGHT: - button_index_string = "BUTTON_RIGHT"; - break; case BUTTON_MIDDLE: - button_index_string = "BUTTON_MIDDLE"; - break; case BUTTON_WHEEL_UP: - button_index_string = "BUTTON_WHEEL_UP"; - break; case BUTTON_WHEEL_DOWN: - button_index_string = "BUTTON_WHEEL_DOWN"; - break; case BUTTON_WHEEL_LEFT: - button_index_string = "BUTTON_WHEEL_LEFT"; - break; case BUTTON_WHEEL_RIGHT: - button_index_string = "BUTTON_WHEEL_RIGHT"; - break; case BUTTON_XBUTTON1: - button_index_string = "BUTTON_XBUTTON1"; + case BUTTON_XBUTTON2: + full_string += RTR(_mouse_button_descriptions[idx - 1]); // button index starts from 1, array index starts from 0, so subtract 1 + break; + default: + full_string += RTR("Button") + " #" + itos(idx); break; + } + + // Double Click + if (doubleclick) { + full_string += " (" + RTR("Double Click") + ")"; + } + + return full_string; +} + +String InputEventMouseButton::to_string() { + String p = is_pressed() ? "true" : "false"; + String d = doubleclick ? "true" : "false"; + + int idx = get_button_index(); + String button_string = itos(idx); + + switch (idx) { + case BUTTON_LEFT: + case BUTTON_RIGHT: + case BUTTON_MIDDLE: + case BUTTON_WHEEL_UP: + case BUTTON_WHEEL_DOWN: + case BUTTON_WHEEL_LEFT: + case BUTTON_WHEEL_RIGHT: + case BUTTON_XBUTTON1: case BUTTON_XBUTTON2: - button_index_string = "BUTTON_XBUTTON2"; + button_string += " (" + RTR(_mouse_button_descriptions[idx - 1]) + ")"; // button index starts from 1, array index starts from 0, so subtract 1 break; default: - button_index_string = itos(get_button_index()); break; } - return "InputEventMouseButton : button_index=" + button_index_string + ", pressed=" + (pressed ? "true" : "false") + ", position=(" + String(get_position()) + "), button_mask=" + itos(get_button_mask()) + ", doubleclick=" + (doubleclick ? "true" : "false"); + + return vformat("InputEventMouseButton: button_index=%s pressed=%s position=(%s) button_mask=%s doubleclick=%s", button_index, p, String(get_position()), itos(get_button_mask()), d); } void InputEventMouseButton::_bind_methods() { @@ -653,27 +716,32 @@ Ref<InputEvent> InputEventMouseMotion::xformed_by(const Transform2D &p_xform, co } String InputEventMouseMotion::as_text() const { - String button_mask_string = ""; + return vformat(RTR("Mouse motion at position (%s) with speed (%s)"), String(get_position()), String(get_speed())); +} + +String InputEventMouseMotion::to_string() { + int button_mask = get_button_mask(); + String button_mask_string = itos(button_mask); switch (get_button_mask()) { case BUTTON_MASK_LEFT: - button_mask_string = "BUTTON_MASK_LEFT"; + button_mask_string += " (" + RTR(_mouse_button_descriptions[BUTTON_LEFT - 1]) + ")"; break; case BUTTON_MASK_MIDDLE: - button_mask_string = "BUTTON_MASK_MIDDLE"; + button_mask_string += " (" + RTR(_mouse_button_descriptions[BUTTON_MIDDLE - 1]) + ")"; break; case BUTTON_MASK_RIGHT: - button_mask_string = "BUTTON_MASK_RIGHT"; + button_mask_string += " (" + RTR(_mouse_button_descriptions[BUTTON_RIGHT - 1]) + ")"; break; case BUTTON_MASK_XBUTTON1: - button_mask_string = "BUTTON_MASK_XBUTTON1"; + button_mask_string += " (" + RTR(_mouse_button_descriptions[BUTTON_XBUTTON1 - 1]) + ")"; break; case BUTTON_MASK_XBUTTON2: - button_mask_string = "BUTTON_MASK_XBUTTON2"; + button_mask_string += " (" + RTR(_mouse_button_descriptions[BUTTON_XBUTTON2 - 1]) + ")"; break; default: - button_mask_string = itos(get_button_mask()); break; } + return "InputEventMouseMotion : button_mask=" + button_mask_string + ", position=(" + String(get_position()) + "), relative=(" + String(get_relative()) + "), speed=(" + String(get_speed()) + "), pressure=(" + rtos(get_pressure()) + "), tilt=(" + String(get_tilt()) + ")"; } @@ -796,7 +864,26 @@ bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool * return match; } +static const char *_joy_axis_descriptions[JOY_AXIS_MAX] = { + TTRC("Left Stick X-Axis, Joystick 0 X-Axis"), + TTRC("Left Stick Y-Axis, Joystick 0 Y-Axis"), + TTRC("Right Stick X-Axis, Joystick 1 X-Axis"), + TTRC("Right Stick Y-Axis, Joystick 1 Y-Axis"), + TTRC("Joystick 2 X-Axis, Left Trigger, Sony L2, Xbox LT"), + TTRC("Joystick 2 Y-Axis, Right Trigger, Sony R2, Xbox RT"), + TTRC("Joystick 3 X-Axis"), + TTRC("Joystick 3 Y-Axis"), + TTRC("Joystick 4 X-Axis"), + TTRC("Joystick 4 Y-Axis"), +}; + String InputEventJoypadMotion::as_text() const { + String desc = axis < JOY_AXIS_MAX ? RTR(_joy_axis_descriptions[axis]) : TTR("Unknown Joypad Axis"); + + return vformat(TTR("Joypad Motion on Axis %s (%s) with Value %s"), itos(axis), desc, String(Variant(axis_value))); +} + +String InputEventJoypadMotion::to_string() { return "InputEventJoypadMotion : axis=" + itos(axis) + ", axis_value=" + String(Variant(axis_value)); } @@ -869,7 +956,39 @@ bool InputEventJoypadButton::shortcut_match(const Ref<InputEvent> &p_event) cons return button_index == button->button_index; } +static const char *_joy_button_descriptions[JOY_BUTTON_SDL_MAX] = { + TTRC("Bottom Action, Sony Cross, Xbox A, Nintendo B"), + TTRC("Right Action, Sony Circle, Xbox B, Nintendo A"), + TTRC("Left Action, Sony Square, Xbox X, Nintendo Y"), + TTRC("Top Action, Sony Triangle, Xbox Y, Nintendo X"), + TTRC("Back, Sony Select, Xbox Back, Nintendo -"), + TTRC("Guide, Sony PS, Xbox Home"), + TTRC("Start, Nintendo +"), + TTRC("Left Stick, Sony L3, Xbox L/LS"), + TTRC("Right Stick, Sony R3, Xbox R/RS"), + TTRC("Left Shoulder, Sony L1, Xbox LB"), + TTRC("Right Shoulder, Sony R1, Xbox RB"), + TTRC("D-pad Up"), + TTRC("D-pad Down"), + TTRC("D-pad Left"), + TTRC("D-pad Right"), +}; + String InputEventJoypadButton::as_text() const { + String text = "Joypad Button " + itos(button_index); + + if (button_index < JOY_BUTTON_SDL_MAX) { + text += vformat(" (%s)", _joy_button_descriptions[button_index]); + } + + if (pressure != 0) { + text += ", Pressure:" + String(Variant(pressure)); + } + + return text; +} + +String InputEventJoypadButton::to_string() { return "InputEventJoypadButton : button_index=" + itos(button_index) + ", pressed=" + (pressed ? "true" : "false") + ", pressure=" + String(Variant(pressure)); } @@ -927,6 +1046,12 @@ Ref<InputEvent> InputEventScreenTouch::xformed_by(const Transform2D &p_xform, co } String InputEventScreenTouch::as_text() const { + String status = pressed ? RTR("touched") : RTR("released"); + + return vformat(RTR("Screen %s at (%s) with %s touch points"), status, String(get_position()), itos(index)); +} + +String InputEventScreenTouch::to_string() { return "InputEventScreenTouch : index=" + itos(index) + ", pressed=" + (pressed ? "true" : "false") + ", position=(" + String(get_position()) + ")"; } @@ -996,6 +1121,10 @@ Ref<InputEvent> InputEventScreenDrag::xformed_by(const Transform2D &p_xform, con } String InputEventScreenDrag::as_text() const { + return vformat(RTR("Screen dragged with %s touch points at position (%s) with speed of (%s)"), itos(index), String(get_position()), String(get_speed())); +} + +String InputEventScreenDrag::to_string() { return "InputEventScreenDrag : index=" + itos(index) + ", position=(" + String(get_position()) + "), relative=(" + String(get_relative()) + "), speed=(" + String(get_speed()) + ")"; } @@ -1079,6 +1208,10 @@ bool InputEventAction::action_match(const Ref<InputEvent> &p_event, bool *p_pres } String InputEventAction::as_text() const { + return vformat(RTR("Input Action %s was %s"), action, pressed ? "pressed" : "released"); +} + +String InputEventAction::to_string() { return "InputEventAction : action=" + action + ", pressed=(" + (pressed ? "true" : "false"); } @@ -1142,6 +1275,10 @@ Ref<InputEvent> InputEventMagnifyGesture::xformed_by(const Transform2D &p_xform, } String InputEventMagnifyGesture::as_text() const { + return vformat(RTR("Magnify Gesture at (%s) with factor %s"), String(get_position()), rtos(get_factor())); +} + +String InputEventMagnifyGesture::to_string() { return "InputEventMagnifyGesture : factor=" + rtos(get_factor()) + ", position=(" + String(get_position()) + ")"; } @@ -1178,6 +1315,10 @@ Ref<InputEvent> InputEventPanGesture::xformed_by(const Transform2D &p_xform, con } String InputEventPanGesture::as_text() const { + return vformat(RTR("Pan Gesture at (%s) with delta (%s)"), String(get_position()), String(get_delta())); +} + +String InputEventPanGesture::to_string() { return "InputEventPanGesture : delta=(" + String(get_delta()) + "), position=(" + String(get_position()) + ")"; } @@ -1255,7 +1396,11 @@ int InputEventMIDI::get_controller_value() const { } String InputEventMIDI::as_text() const { - return "InputEventMIDI : channel=(" + itos(get_channel()) + "), message=(" + itos(get_message()) + ")"; + return vformat(RTR("MIDI Input on Channel=%s Message=%s"), itos(channel), itos(message)); +} + +String InputEventMIDI::to_string() { + return vformat("InputEvenMIDI: channel=%s message=%s pitch=%s velocity=%s pressure=%s", itos(channel), itos(message), itos(pitch), itos(velocity), itos(pressure)); } void InputEventMIDI::_bind_methods() { diff --git a/core/input/input_event.h b/core/input/input_event.h index 0eae3a2bbe..6a71a24c8b 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -132,7 +132,7 @@ public: virtual bool is_pressed() const; virtual bool is_echo() const; - virtual String as_text() const; + virtual String as_text() const = 0; virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const; @@ -207,6 +207,9 @@ public: void set_modifiers_from_event(const InputEventWithModifiers *event); + virtual String as_text() const override; + virtual String to_string() override; + InputEventWithModifiers() {} }; @@ -249,6 +252,7 @@ public: virtual bool is_action_type() const override { return true; } virtual String as_text() const override; + virtual String to_string() override; InputEventKey() {} }; @@ -306,6 +310,7 @@ public: virtual bool is_action_type() const override { return true; } virtual String as_text() const override; + virtual String to_string() override; InputEventMouseButton() {} }; @@ -336,6 +341,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; virtual String as_text() const override; + virtual String to_string() override; virtual bool accumulate(const Ref<InputEvent> &p_event) override; @@ -363,6 +369,7 @@ public: virtual bool is_action_type() const override { return true; } virtual String as_text() const override; + virtual String to_string() override; InputEventJoypadMotion() {} }; @@ -391,6 +398,7 @@ public: virtual bool is_action_type() const override { return true; } virtual String as_text() const override; + virtual String to_string() override; InputEventJoypadButton() {} }; @@ -416,6 +424,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; virtual String as_text() const override; + virtual String to_string() override; InputEventScreenTouch() {} }; @@ -445,6 +454,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; virtual String as_text() const override; + virtual String to_string() override; InputEventScreenDrag() {} }; @@ -476,6 +486,7 @@ public: virtual bool shortcut_match(const Ref<InputEvent> &p_event) const override; virtual bool is_action_type() const override { return true; } virtual String as_text() const override; + virtual String to_string() override; InputEventAction() {} }; @@ -506,6 +517,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; virtual String as_text() const override; + virtual String to_string() override; InputEventMagnifyGesture() {} }; @@ -523,6 +535,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; virtual String as_text() const override; + virtual String to_string() override; InputEventPanGesture() {} }; @@ -568,6 +581,7 @@ public: int get_controller_value() const; virtual String as_text() const override; + virtual String to_string() override; InputEventMIDI() {} }; diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index d31a9a0194..30bbd43c18 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -102,6 +102,7 @@ public: virtual String get_resource_type() const = 0; virtual float get_priority() const { return 1.0; } virtual int get_import_order() const { return 0; } + virtual int get_format_version() const { return 0; } struct ImportOption { PropertyInfo option; diff --git a/core/object/script_language.h b/core/object/script_language.h index 3fd56c2f15..72586780ae 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -31,6 +31,7 @@ #ifndef SCRIPT_LANGUAGE_H #define SCRIPT_LANGUAGE_H +#include "core/doc_data.h" #include "core/io/multiplayer_api.h" #include "core/io/resource.h" #include "core/templates/map.h" @@ -145,6 +146,10 @@ public: virtual void set_source_code(const String &p_code) = 0; virtual Error reload(bool p_keep_state = false) = 0; +#ifdef TOOLS_ENABLED + virtual const Vector<DocData::ClassDoc> &get_documentation() const = 0; +#endif // TOOLS_ENABLED + virtual bool has_method(const StringName &p_method) const = 0; virtual MethodInfo get_method_info(const StringName &p_method) const = 0; @@ -310,6 +315,7 @@ public: virtual Script *create_script() const = 0; virtual bool has_named_classes() const = 0; virtual bool supports_builtin_mode() const = 0; + virtual bool supports_documentation() const { return false; } virtual bool can_inherit_from_file() { return false; } virtual int find_function(const String &p_function, const String &p_code) const = 0; virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const = 0; diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 7e32f215e7..9883ce12a0 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -168,6 +168,7 @@ void register_core_types() { ClassDB::register_class<AESContext>(); ClassDB::register_custom_instance_class<X509Certificate>(); ClassDB::register_custom_instance_class<CryptoKey>(); + ClassDB::register_custom_instance_class<HMACContext>(); ClassDB::register_custom_instance_class<Crypto>(); ClassDB::register_custom_instance_class<StreamPeerSSL>(); diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp index 01f5b7df59..3bb3fa3634 100644 --- a/core/variant/variant_construct.cpp +++ b/core/variant/variant_construct.cpp @@ -777,18 +777,23 @@ String Variant::get_constructor_argument_name(Variant::Type p_type, int p_constr return construct_data[p_type][p_constructor].arg_names[p_argument]; } -void VariantInternal::object_assign(Variant *v, const Variant *o) { - if (o->_get_obj().obj && o->_get_obj().id.is_reference()) { - Reference *reference = static_cast<Reference *>(o->_get_obj().obj); - if (!reference->reference()) { - v->_get_obj().obj = nullptr; - v->_get_obj().id = ObjectID(); - return; +void VariantInternal::object_assign(Variant *v, const Object *o) { + if (o) { + if (o->is_reference()) { + Reference *reference = const_cast<Reference *>(static_cast<const Reference *>(o)); + if (!reference->init_ref()) { + v->_get_obj().obj = nullptr; + v->_get_obj().id = ObjectID(); + return; + } } - } - v->_get_obj().obj = const_cast<Object *>(o->_get_obj().obj); - v->_get_obj().id = o->_get_obj().id; + v->_get_obj().obj = const_cast<Object *>(o); + v->_get_obj().id = o->get_instance_id(); + } else { + v->_get_obj().obj = nullptr; + v->_get_obj().id = ObjectID(); + } } void Variant::get_constructor_list(Type p_type, List<MethodInfo> *r_list) { diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h index bf7e46eed7..804abf8fbc 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -100,21 +100,14 @@ public: case Variant::PACKED_COLOR_ARRAY: init_color_array(v); break; + case Variant::OBJECT: + object_assign_null(v); + break; default: break; } } - _FORCE_INLINE_ static void set_object(Variant *v, Object *obj) { - if (obj) { - v->_get_obj().obj = obj; - v->_get_obj().id = obj->get_instance_id(); - } else { - v->_get_obj().obj = nullptr; - v->_get_obj().id = ObjectID(); - } - } - // Atomic types. _FORCE_INLINE_ static bool *get_bool(Variant *v) { return &v->_data._bool; } _FORCE_INLINE_ static const bool *get_bool(const Variant *v) { return &v->_data._bool; } @@ -285,7 +278,11 @@ public: v->clear(); } - static void object_assign(Variant *v, const Variant *o); //needs to use reference, do away + static void object_assign(Variant *v, const Object *o); // Needs Reference, so it's implemented elsewhere. + + _FORCE_INLINE_ static void object_assign(Variant *v, const Variant *o) { + object_assign(v, o->_get_obj().obj); + } _FORCE_INLINE_ static void object_assign_null(Variant *v) { v->_get_obj().obj = nullptr; diff --git a/doc/classes/BaseButton.xml b/doc/classes/BaseButton.xml index 378df1ce65..45ef4cb14c 100644 --- a/doc/classes/BaseButton.xml +++ b/doc/classes/BaseButton.xml @@ -65,6 +65,9 @@ <member name="shortcut" type="Shortcut" setter="set_shortcut" getter="get_shortcut"> [Shortcut] associated to the button. </member> + <member name="shortcut_context" type="Node" setter="set_shortcut_context" getter="get_shortcut_context"> + The [Node] which must be a parent of the focused GUI [Control] for the shortcut to be activated. If [code]null[/code], the shortcut can be activated when any control is focused (a global shortcut). This allows shortcuts to be accepted only when the user has a certain area of the GUI focused. + </member> <member name="shortcut_in_tooltip" type="bool" setter="set_shortcut_in_tooltip" getter="is_shortcut_in_tooltip_enabled" default="true"> If [code]true[/code], the button will add information about its shortcut in the tooltip. </member> diff --git a/doc/classes/Crypto.xml b/doc/classes/Crypto.xml index b3bbbae94f..1f6cb40cde 100644 --- a/doc/classes/Crypto.xml +++ b/doc/classes/Crypto.xml @@ -73,6 +73,18 @@ <tutorials> </tutorials> <methods> + <method name="constant_time_compare"> + <return type="bool"> + </return> + <argument index="0" name="trusted" type="PackedByteArray"> + </argument> + <argument index="1" name="received" type="PackedByteArray"> + </argument> + <description> + Compares two [PackedByteArray]s for equality without leaking timing information in order to prevent timing attacks. + See [url=https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy]this blog post[/url] for more information. + </description> + </method> <method name="decrypt"> <return type="PackedByteArray"> </return> @@ -147,6 +159,20 @@ [/codeblocks] </description> </method> + <method name="hmac_digest"> + <return type="PackedByteArray"> + </return> + <argument index="0" name="hash_type" type="int" enum="HashingContext.HashType"> + </argument> + <argument index="1" name="key" type="PackedByteArray"> + </argument> + <argument index="2" name="msg" type="PackedByteArray"> + </argument> + <description> + Generates an [url=https://en.wikipedia.org/wiki/HMAC]HMAC[/url] digest of [code]msg[/code] using [code]key[/code]. The [code]hash_type[/code] parameter is the hashing algorithm that is used for the inner and outer hashes. + Currently, only [constant HashingContext.HASH_SHA256] and [constant HashingContext.HASH_SHA1] are supported. + </description> + </method> <method name="sign"> <return type="PackedByteArray"> </return> diff --git a/doc/classes/Curve3D.xml b/doc/classes/Curve3D.xml index a6a0e0c33d..bda04f010b 100644 --- a/doc/classes/Curve3D.xml +++ b/doc/classes/Curve3D.xml @@ -145,7 +145,7 @@ <argument index="1" name="cubic" type="bool" default="false"> </argument> <description> - Returns a point within the curve at position [code]offset[/code], where [code]offset[/code] is measured as a pixel distance along the curve. + Returns a point within the curve at position [code]offset[/code], where [code]offset[/code] is measured as a distance in 3D units along the curve. To do that, it finds the two cached points where the [code]offset[/code] lies between, then interpolates the values. This interpolation is cubic if [code]cubic[/code] is set to [code]true[/code], or linear if set to [code]false[/code]. Cubic interpolation tends to follow the curves better, but linear is faster (and often, precise enough). </description> diff --git a/doc/classes/HMACContext.xml b/doc/classes/HMACContext.xml new file mode 100644 index 0000000000..00d528ef8f --- /dev/null +++ b/doc/classes/HMACContext.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="HMACContext" inherits="Reference" version="4.0"> + <brief_description> + Used to create an HMAC for a message using a key. + </brief_description> + <description> + The HMACContext class is useful for advanced HMAC use cases, such as streaming the message as it supports creating the message over time rather than providing it all at once. + [codeblocks] + [gdscript] + extends Node + var ctx = HMACContext.new() + + func _ready(): + var key = "supersecret".to_utf8() + var err = ctx.start(HashingContext.HASH_SHA256, key) + assert(err == OK) + var msg1 = "this is ".to_utf8() + var msg2 = "vewy vewy secret".to_utf8() + err = ctx.update(msg1) + assert(err == OK) + err = ctx.update(msg2) + assert(err == OK) + var hmac = ctx.finish() + print(hmac.hex_encode()) + + [/gdscript] + [csharp] + using Godot; + using System; + using System.Diagnostics; + + public class CryptoNode : Node + { + private HMACContext ctx = new HMACContext(); + public override void _Ready() + { + PackedByteArray key = String("supersecret").to_utf8(); + Error err = ctx.Start(HashingContext.HASH_SHA256, key); + GD.Assert(err == OK); + PackedByteArray msg1 = String("this is ").to_utf8(); + PackedByteArray msg2 = String("vewy vew secret").to_utf8(); + err = ctx.Update(msg1); + GD.Assert(err == OK); + err = ctx.Update(msg2); + GD.Assert(err == OK); + PackedByteArray hmac = ctx.Finish(); + GD.Print(hmac.HexEncode()); + } + } + + [/csharp] + [/codeblocks] + [b]Note:[/b] Not available in HTML5 exports. + </description> + <tutorials> + </tutorials> + <methods> + <method name="finish"> + <return type="PackedByteArray"> + </return> + <description> + Returns the resulting HMAC. If the HMAC failed, an empty [PackedByteArray] is returned. + </description> + </method> + <method name="start"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="hash_type" type="int" enum="HashingContext.HashType"> + </argument> + <argument index="1" name="key" type="PackedByteArray"> + </argument> + <description> + Initializes the HMACContext. This method cannot be called again on the same HMACContext until [method finish] has been called. + </description> + </method> + <method name="update"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="data" type="PackedByteArray"> + </argument> + <description> + Updates the message to be HMACed. This can be called multiple times before [method finish] is called to append [code]data[/code] to the message, but cannot be called until [method start] has been called. + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 08e15d8306..473535ebf5 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -885,6 +885,9 @@ public: void render_target_disable_clear_request(RID p_render_target) override {} void render_target_do_clear_request(RID p_render_target) override {} + void render_target_set_sdf_size_and_scale(RID p_render_target, RS::ViewportSDFOversize p_size, RS::ViewportSDFScale p_scale) override {} + Rect2i render_target_get_sdf_rect(RID p_render_target) const override { return Rect2i(); } + RS::InstanceType get_base_type(RID p_rid) const override { if (mesh_owner.owns(p_rid)) { return RS::INSTANCE_MESH; @@ -943,7 +946,7 @@ public: PolygonID request_polygon(const Vector<int> &p_indices, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), const Vector<int> &p_bones = Vector<int>(), const Vector<float> &p_weights = Vector<float>()) override { return 0; } void free_polygon(PolygonID p_polygon) override {} - void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel) override {} + void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used) override {} void canvas_debug_viewport_shadows(Light *p_lights_with_shadow) override {} RID light_create() override { return RID(); } @@ -952,8 +955,9 @@ public: void light_update_shadow(RID p_rid, int p_shadow_index, const Transform2D &p_light_xform, int p_light_mask, float p_near, float p_far, LightOccluderInstance *p_occluders) override {} void light_update_directional_shadow(RID p_rid, int p_shadow_index, const Transform2D &p_light_xform, int p_light_mask, float p_cull_distance, const Rect2 &p_clip_rect, LightOccluderInstance *p_occluders) override {} + void render_sdf(RID p_render_target, LightOccluderInstance *p_occluders) override {} RID occluder_polygon_create() override { return RID(); } - void occluder_polygon_set_shape_as_lines(RID p_occluder, const Vector<Vector2> &p_lines) override {} + void occluder_polygon_set_shape(RID p_occluder, const Vector<Vector2> &p_points, bool p_closed) override {} void occluder_polygon_set_cull_mode(RID p_occluder, RS::CanvasOccluderPolygonCullMode p_mode) override {} void set_shadow_texture_size(int p_size) override {} diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index 23e6b3bfb6..bf27342b74 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -615,6 +615,7 @@ int RenderingDeviceVulkan::get_format_vertex_size(DataFormat p_format) { case DATA_FORMAT_B8G8R8A8_SNORM: case DATA_FORMAT_B8G8R8A8_UINT: case DATA_FORMAT_B8G8R8A8_SINT: + case DATA_FORMAT_A2B10G10R10_UNORM_PACK32: return 4; case DATA_FORMAT_R16_UNORM: case DATA_FORMAT_R16_SNORM: @@ -3528,7 +3529,7 @@ RenderingDevice::VertexFormatID RenderingDeviceVulkan::vertex_format_create(cons ERR_FAIL_COND_V(used_locations.has(p_vertex_formats[i].location), INVALID_ID); ERR_FAIL_COND_V_MSG(get_format_vertex_size(p_vertex_formats[i].format) == 0, INVALID_ID, - "Data format for attachment (" + itos(i) + ") is not valid for a vertex array."); + "Data format for attachment (" + itos(i) + "), '" + named_formats[p_vertex_formats[i].format] + "', is not valid for a vertex array."); vdcache.bindings[i].binding = i; vdcache.bindings[i].stride = p_vertex_formats[i].stride; diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 8f5b5612fc..823fd7e852 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1562,9 +1562,9 @@ void CodeTextEditor::_set_show_warnings_panel(bool p_show) { void CodeTextEditor::_toggle_scripts_pressed() { if (is_layout_rtl()) { - toggle_scripts_button->set_icon(ScriptEditor::get_singleton()->toggle_scripts_panel() ? get_theme_icon("Back", "EditorIcons") : get_theme_icon("Forward", "EditorIcons")); - } else { toggle_scripts_button->set_icon(ScriptEditor::get_singleton()->toggle_scripts_panel() ? get_theme_icon("Forward", "EditorIcons") : get_theme_icon("Back", "EditorIcons")); + } else { + toggle_scripts_button->set_icon(ScriptEditor::get_singleton()->toggle_scripts_panel() ? get_theme_icon("Back", "EditorIcons") : get_theme_icon("Forward", "EditorIcons")); } } @@ -1687,9 +1687,9 @@ void CodeTextEditor::show_toggle_scripts_button() { void CodeTextEditor::update_toggle_scripts_button() { if (is_layout_rtl()) { - toggle_scripts_button->set_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? get_theme_icon("Back", "EditorIcons") : get_theme_icon("Forward", "EditorIcons")); - } else { toggle_scripts_button->set_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? get_theme_icon("Forward", "EditorIcons") : get_theme_icon("Back", "EditorIcons")); + } else { + toggle_scripts_button->set_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? get_theme_icon("Back", "EditorIcons") : get_theme_icon("Forward", "EditorIcons")); } toggle_scripts_button->set_tooltip(TTR("Toggle Scripts Panel") + " (" + ED_GET_SHORTCUT("script_editor/toggle_scripts_panel")->get_as_text() + ")"); } diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 320e5d8510..2630589912 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -31,6 +31,7 @@ #include "connections_dialog.h" #include "core/string/print_string.h" +#include "editor/doc_tools.h" #include "editor_node.h" #include "editor_scale.h" #include "editor_settings.h" @@ -410,6 +411,7 @@ ConnectDialog::ConnectDialog() { tree = memnew(SceneTreeEditor(false)); tree->set_connecting_signal(true); + tree->set_show_enabled_subscene(true); tree->get_scene_tree()->connect("item_activated", callable_mp(this, &ConnectDialog::_item_activated)); tree->connect("node_selected", callable_mp(this, &ConnectDialog::_tree_node_selected)); tree->set_connect_to_script_mode(true); @@ -997,7 +999,7 @@ void ConnectionsDock::update_tree() { } if (!found) { - DocData *dd = EditorHelp::get_doc_data(); + DocTools *dd = EditorHelp::get_doc_data(); Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(base); while (F && descr == String()) { for (int i = 0; i < F->get().signals.size(); i++) { diff --git a/editor/doc_data.cpp b/editor/doc_tools.cpp index 165a5c8546..5ee9abb183 100644 --- a/editor/doc_data.cpp +++ b/editor/doc_tools.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* doc_data.cpp */ +/* doc_tools.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "doc_data.h" +#include "doc_tools.h" #include "core/config/engine.h" #include "core/config/project_settings.h" @@ -43,22 +43,22 @@ // Used for a hack preserving Mono properties on non-Mono builds. #include "modules/modules_enabled.gen.h" -void DocData::merge_from(const DocData &p_data) { - for (Map<String, ClassDoc>::Element *E = class_list.front(); E; E = E->next()) { - ClassDoc &c = E->get(); +void DocTools::merge_from(const DocTools &p_data) { + for (Map<String, DocData::ClassDoc>::Element *E = class_list.front(); E; E = E->next()) { + DocData::ClassDoc &c = E->get(); if (!p_data.class_list.has(c.name)) { continue; } - const ClassDoc &cf = p_data.class_list[c.name]; + const DocData::ClassDoc &cf = p_data.class_list[c.name]; c.description = cf.description; c.brief_description = cf.brief_description; c.tutorials = cf.tutorials; for (int i = 0; i < c.methods.size(); i++) { - MethodDoc &m = c.methods.write[i]; + DocData::MethodDoc &m = c.methods.write[i]; for (int j = 0; j < cf.methods.size(); j++) { if (cf.methods[j].name != m.name) { @@ -95,7 +95,7 @@ void DocData::merge_from(const DocData &p_data) { continue; } - const MethodDoc &mf = cf.methods[j]; + const DocData::MethodDoc &mf = cf.methods[j]; m.description = mf.description; break; @@ -103,13 +103,13 @@ void DocData::merge_from(const DocData &p_data) { } for (int i = 0; i < c.signals.size(); i++) { - MethodDoc &m = c.signals.write[i]; + DocData::MethodDoc &m = c.signals.write[i]; for (int j = 0; j < cf.signals.size(); j++) { if (cf.signals[j].name != m.name) { continue; } - const MethodDoc &mf = cf.signals[j]; + const DocData::MethodDoc &mf = cf.signals[j]; m.description = mf.description; break; @@ -117,13 +117,13 @@ void DocData::merge_from(const DocData &p_data) { } for (int i = 0; i < c.constants.size(); i++) { - ConstantDoc &m = c.constants.write[i]; + DocData::ConstantDoc &m = c.constants.write[i]; for (int j = 0; j < cf.constants.size(); j++) { if (cf.constants[j].name != m.name) { continue; } - const ConstantDoc &mf = cf.constants[j]; + const DocData::ConstantDoc &mf = cf.constants[j]; m.description = mf.description; break; @@ -131,13 +131,13 @@ void DocData::merge_from(const DocData &p_data) { } for (int i = 0; i < c.properties.size(); i++) { - PropertyDoc &p = c.properties.write[i]; + DocData::PropertyDoc &p = c.properties.write[i]; for (int j = 0; j < cf.properties.size(); j++) { if (cf.properties[j].name != p.name) { continue; } - const PropertyDoc &pf = cf.properties[j]; + const DocData::PropertyDoc &pf = cf.properties[j]; p.description = pf.description; break; @@ -145,13 +145,13 @@ void DocData::merge_from(const DocData &p_data) { } for (int i = 0; i < c.theme_properties.size(); i++) { - PropertyDoc &p = c.theme_properties.write[i]; + DocData::PropertyDoc &p = c.theme_properties.write[i]; for (int j = 0; j < cf.theme_properties.size(); j++) { if (cf.theme_properties[j].name != p.name) { continue; } - const PropertyDoc &pf = cf.theme_properties[j]; + const DocData::PropertyDoc &pf = cf.theme_properties[j]; p.description = pf.description; break; @@ -177,57 +177,29 @@ void DocData::merge_from(const DocData &p_data) { } } -void DocData::remove_from(const DocData &p_data) { - for (Map<String, ClassDoc>::Element *E = p_data.class_list.front(); E; E = E->next()) { +void DocTools::remove_from(const DocTools &p_data) { + for (Map<String, DocData::ClassDoc>::Element *E = p_data.class_list.front(); E; E = E->next()) { if (class_list.has(E->key())) { class_list.erase(E->key()); } } } -static void return_doc_from_retinfo(DocData::MethodDoc &p_method, const PropertyInfo &p_retinfo) { - if (p_retinfo.type == Variant::INT && p_retinfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - p_method.return_enum = p_retinfo.class_name; - if (p_method.return_enum.begins_with("_")) { //proxy class - p_method.return_enum = p_method.return_enum.substr(1, p_method.return_enum.length()); - } - p_method.return_type = "int"; - } else if (p_retinfo.class_name != StringName()) { - p_method.return_type = p_retinfo.class_name; - } else if (p_retinfo.type == Variant::ARRAY && p_retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) { - p_method.return_type = p_retinfo.hint_string + "[]"; - } else if (p_retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { - p_method.return_type = p_retinfo.hint_string; - } else if (p_retinfo.type == Variant::NIL && p_retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { - p_method.return_type = "Variant"; - } else if (p_retinfo.type == Variant::NIL) { - p_method.return_type = "void"; - } else { - p_method.return_type = Variant::get_type_name(p_retinfo.type); - } +void DocTools::add_doc(const DocData::ClassDoc &p_class_doc) { + ERR_FAIL_COND(p_class_doc.name == ""); + class_list[p_class_doc.name] = p_class_doc; } -static void argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const PropertyInfo &p_arginfo) { - p_argument.name = p_arginfo.name; +void DocTools::remove_doc(const String &p_class_name) { + ERR_FAIL_COND(p_class_name == "" || !class_list.has(p_class_name)); + class_list.erase(p_class_name); +} - if (p_arginfo.type == Variant::INT && p_arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - p_argument.enumeration = p_arginfo.class_name; - if (p_argument.enumeration.begins_with("_")) { //proxy class - p_argument.enumeration = p_argument.enumeration.substr(1, p_argument.enumeration.length()); - } - p_argument.type = "int"; - } else if (p_arginfo.class_name != StringName()) { - p_argument.type = p_arginfo.class_name; - } else if (p_arginfo.type == Variant::ARRAY && p_arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { - p_argument.type = p_arginfo.hint_string + "[]"; - } else if (p_arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { - p_argument.type = p_arginfo.hint_string; - } else if (p_arginfo.type == Variant::NIL) { - // Parameters cannot be void, so PROPERTY_USAGE_NIL_IS_VARIANT is not necessary - p_argument.type = "Variant"; - } else { - p_argument.type = Variant::get_type_name(p_arginfo.type); +bool DocTools::has_doc(const String &p_class_name) { + if (p_class_name == "") { + return false; } + return class_list.has(p_class_name); } static Variant get_documentation_default_value(const StringName &p_class_name, const StringName &p_property_name, bool &r_default_value_valid) { @@ -253,7 +225,7 @@ static Variant get_documentation_default_value(const StringName &p_class_name, c return default_value; } -void DocData::generate(bool p_basic_types) { +void DocTools::generate(bool p_basic_types) { List<StringName> classes; ClassDB::get_class_list(&classes); classes.sort_custom<StringName::AlphCompare>(); @@ -277,8 +249,8 @@ void DocData::generate(bool p_basic_types) { cname = cname.substr(1, name.length()); } - class_list[cname] = ClassDoc(); - ClassDoc &c = class_list[cname]; + class_list[cname] = DocData::ClassDoc(); + DocData::ClassDoc &c = class_list[cname]; c.name = cname; c.inherits = ClassDB::get_parent_class(name); @@ -305,7 +277,7 @@ void DocData::generate(bool p_basic_types) { continue; } - PropertyDoc prop; + DocData::PropertyDoc prop; prop.name = E->get().name; @@ -409,7 +381,7 @@ void DocData::generate(bool p_basic_types) { } } - MethodDoc method; + DocData::MethodDoc method; method.name = E->get().name; @@ -432,12 +404,12 @@ void DocData::generate(bool p_basic_types) { for (int i = -1; i < E->get().arguments.size(); i++) { if (i == -1) { #ifdef DEBUG_METHODS_ENABLED - return_doc_from_retinfo(method, E->get().return_val); + DocData::return_doc_from_retinfo(method, E->get().return_val); #endif } else { const PropertyInfo &arginfo = E->get().arguments[i]; - ArgumentDoc argument; - argument_doc_from_arginfo(argument, arginfo); + DocData::ArgumentDoc argument; + DocData::argument_doc_from_arginfo(argument, arginfo); int darg_idx = i - (E->get().arguments.size() - E->get().default_arguments.size()); if (darg_idx >= 0) { @@ -457,12 +429,12 @@ void DocData::generate(bool p_basic_types) { if (signal_list.size()) { for (List<MethodInfo>::Element *EV = signal_list.front(); EV; EV = EV->next()) { - MethodDoc signal; + DocData::MethodDoc signal; signal.name = EV->get().name; for (int i = 0; i < EV->get().arguments.size(); i++) { const PropertyInfo &arginfo = EV->get().arguments[i]; - ArgumentDoc argument; - argument_doc_from_arginfo(argument, arginfo); + DocData::ArgumentDoc argument; + DocData::argument_doc_from_arginfo(argument, arginfo); signal.arguments.push_back(argument); } @@ -475,7 +447,7 @@ void DocData::generate(bool p_basic_types) { ClassDB::get_integer_constant_list(name, &constant_list, true); for (List<String>::Element *E = constant_list.front(); E; E = E->next()) { - ConstantDoc constant; + DocData::ConstantDoc constant; constant.name = E->get(); constant.value = itos(ClassDB::get_integer_constant(name, E->get())); constant.is_value_valid = true; @@ -489,7 +461,7 @@ void DocData::generate(bool p_basic_types) { List<StringName> l; Theme::get_default()->get_constant_list(cname, &l); for (List<StringName>::Element *E = l.front(); E; E = E->next()) { - PropertyDoc pd; + DocData::PropertyDoc pd; pd.name = E->get(); pd.type = "int"; pd.default_value = itos(Theme::get_default()->get_constant(E->get(), cname)); @@ -499,7 +471,7 @@ void DocData::generate(bool p_basic_types) { l.clear(); Theme::get_default()->get_color_list(cname, &l); for (List<StringName>::Element *E = l.front(); E; E = E->next()) { - PropertyDoc pd; + DocData::PropertyDoc pd; pd.name = E->get(); pd.type = "Color"; pd.default_value = Variant(Theme::get_default()->get_color(E->get(), cname)).get_construct_string(); @@ -509,7 +481,7 @@ void DocData::generate(bool p_basic_types) { l.clear(); Theme::get_default()->get_icon_list(cname, &l); for (List<StringName>::Element *E = l.front(); E; E = E->next()) { - PropertyDoc pd; + DocData::PropertyDoc pd; pd.name = E->get(); pd.type = "Texture2D"; c.theme_properties.push_back(pd); @@ -517,7 +489,7 @@ void DocData::generate(bool p_basic_types) { l.clear(); Theme::get_default()->get_font_list(cname, &l); for (List<StringName>::Element *E = l.front(); E; E = E->next()) { - PropertyDoc pd; + DocData::PropertyDoc pd; pd.name = E->get(); pd.type = "Font"; c.theme_properties.push_back(pd); @@ -525,7 +497,7 @@ void DocData::generate(bool p_basic_types) { l.clear(); Theme::get_default()->get_font_size_list(cname, &l); for (List<StringName>::Element *E = l.front(); E; E = E->next()) { - PropertyDoc pd; + DocData::PropertyDoc pd; pd.name = E->get(); pd.type = "int"; c.theme_properties.push_back(pd); @@ -533,7 +505,7 @@ void DocData::generate(bool p_basic_types) { l.clear(); Theme::get_default()->get_stylebox_list(cname, &l); for (List<StringName>::Element *E = l.front(); E; E = E->next()) { - PropertyDoc pd; + DocData::PropertyDoc pd; pd.name = E->get(); pd.type = "StyleBox"; c.theme_properties.push_back(pd); @@ -545,7 +517,7 @@ void DocData::generate(bool p_basic_types) { { // So we can document the concept of Variant even if it's not a usable class per se. - class_list["Variant"] = ClassDoc(); + class_list["Variant"] = DocData::ClassDoc(); class_list["Variant"].name = "Variant"; } @@ -564,8 +536,8 @@ void DocData::generate(bool p_basic_types) { String cname = Variant::get_type_name(Variant::Type(i)); - class_list[cname] = ClassDoc(); - ClassDoc &c = class_list[cname]; + class_list[cname] = DocData::ClassDoc(); + DocData::ClassDoc &c = class_list[cname]; c.name = cname; Callable::CallError cerror; @@ -642,7 +614,7 @@ void DocData::generate(bool p_basic_types) { for (List<MethodInfo>::Element *E = method_list.front(); E; E = E->next()) { MethodInfo &mi = E->get(); - MethodDoc method; + DocData::MethodDoc method; method.name = mi.name; if (method.name == cname) { @@ -653,8 +625,8 @@ void DocData::generate(bool p_basic_types) { for (int j = 0; j < mi.arguments.size(); j++) { PropertyInfo arginfo = mi.arguments[j]; - ArgumentDoc ad; - argument_doc_from_arginfo(ad, mi.arguments[j]); + DocData::ArgumentDoc ad; + DocData::argument_doc_from_arginfo(ad, mi.arguments[j]); ad.name = arginfo.name; int darg_idx = mi.default_arguments.size() - mi.arguments.size() + j; @@ -666,7 +638,7 @@ void DocData::generate(bool p_basic_types) { method.arguments.push_back(ad); } - return_doc_from_retinfo(method, mi.return_val); + DocData::return_doc_from_retinfo(method, mi.return_val); if (mi.flags & METHOD_FLAG_VARARG) { if (method.qualifiers != "") { @@ -682,7 +654,7 @@ void DocData::generate(bool p_basic_types) { v.get_property_list(&properties); for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { PropertyInfo pi = E->get(); - PropertyDoc property; + DocData::PropertyDoc property; property.name = pi.name; property.type = Variant::get_type_name(pi.type); property.default_value = v.get(pi.name).get_construct_string(); @@ -694,7 +666,7 @@ void DocData::generate(bool p_basic_types) { Variant::get_constants_for_type(Variant::Type(i), &constants); for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { - ConstantDoc constant; + DocData::ConstantDoc constant; constant.name = E->get(); Variant value = Variant::get_constant_value(Variant::Type(i), E->get()); constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string(); @@ -707,12 +679,12 @@ void DocData::generate(bool p_basic_types) { { String cname = "@GlobalScope"; - class_list[cname] = ClassDoc(); - ClassDoc &c = class_list[cname]; + class_list[cname] = DocData::ClassDoc(); + DocData::ClassDoc &c = class_list[cname]; c.name = cname; for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { - ConstantDoc cd; + DocData::ConstantDoc cd; cd.name = CoreConstants::get_global_constant_name(i); if (!CoreConstants::get_ignore_value_in_docs(i)) { cd.value = itos(CoreConstants::get_global_constant_value(i)); @@ -729,7 +701,7 @@ void DocData::generate(bool p_basic_types) { //servers (this is kind of hackish) for (List<Engine::Singleton>::Element *E = singletons.front(); E; E = E->next()) { - PropertyDoc pd; + DocData::PropertyDoc pd; Engine::Singleton &s = E->get(); if (!s.ptr) { continue; @@ -749,7 +721,7 @@ void DocData::generate(bool p_basic_types) { Variant::get_utility_function_list(&utility_functions); utility_functions.sort_custom<StringName::AlphCompare>(); for (List<StringName>::Element *E = utility_functions.front(); E; E = E->next()) { - MethodDoc md; + DocData::MethodDoc md; md.name = E->get(); //return if (Variant::has_utility_function_return_value(E->get())) { @@ -759,7 +731,7 @@ void DocData::generate(bool p_basic_types) { pi.usage = PROPERTY_USAGE_NIL_IS_VARIANT; } DocData::ArgumentDoc ad; - argument_doc_from_arginfo(ad, pi); + DocData::argument_doc_from_arginfo(ad, pi); md.return_type = ad.type; } @@ -774,7 +746,7 @@ void DocData::generate(bool p_basic_types) { pi.usage = PROPERTY_USAGE_NIL_IS_VARIANT; } DocData::ArgumentDoc ad; - argument_doc_from_arginfo(ad, pi); + DocData::argument_doc_from_arginfo(ad, pi); md.arguments.push_back(ad); } } @@ -791,7 +763,7 @@ void DocData::generate(bool p_basic_types) { for (int i = 0; i < ScriptServer::get_language_count(); i++) { ScriptLanguage *lang = ScriptServer::get_language(i); String cname = "@" + lang->get_name(); - ClassDoc c; + DocData::ClassDoc c; c.name = cname; // Get functions. @@ -800,7 +772,7 @@ void DocData::generate(bool p_basic_types) { for (List<MethodInfo>::Element *E = minfo.front(); E; E = E->next()) { MethodInfo &mi = E->get(); - MethodDoc md; + DocData::MethodDoc md; md.name = mi.name; if (mi.flags & METHOD_FLAG_VARARG) { @@ -810,11 +782,11 @@ void DocData::generate(bool p_basic_types) { md.qualifiers += "vararg"; } - return_doc_from_retinfo(md, mi.return_val); + DocData::return_doc_from_retinfo(md, mi.return_val); for (int j = 0; j < mi.arguments.size(); j++) { - ArgumentDoc ad; - argument_doc_from_arginfo(ad, mi.arguments[j]); + DocData::ArgumentDoc ad; + DocData::argument_doc_from_arginfo(ad, mi.arguments[j]); int darg_idx = j - (mi.arguments.size() - mi.default_arguments.size()); if (darg_idx >= 0) { @@ -833,7 +805,7 @@ void DocData::generate(bool p_basic_types) { lang->get_public_constants(&cinfo); for (List<Pair<String, Variant>>::Element *E = cinfo.front(); E; E = E->next()) { - ConstantDoc cd; + DocData::ConstantDoc cd; cd.name = E->get().first; cd.value = E->get().second; cd.is_value_valid = true; @@ -911,7 +883,7 @@ static Error _parse_methods(Ref<XMLParser> &parser, Vector<DocData::MethodDoc> & return OK; } -Error DocData::load_classes(const String &p_dir) { +Error DocTools::load_classes(const String &p_dir) { Error err; DirAccessRef da = DirAccess::open(p_dir, &err); if (!da) { @@ -939,7 +911,7 @@ Error DocData::load_classes(const String &p_dir) { return OK; } -Error DocData::erase_classes(const String &p_dir) { +Error DocTools::erase_classes(const String &p_dir) { Error err; DirAccessRef da = DirAccess::open(p_dir, &err); if (!da) { @@ -967,7 +939,7 @@ Error DocData::erase_classes(const String &p_dir) { return OK; } -Error DocData::_load(Ref<XMLParser> parser) { +Error DocTools::_load(Ref<XMLParser> parser) { Error err = OK; while ((err = parser->read()) == OK) { @@ -983,8 +955,8 @@ Error DocData::_load(Ref<XMLParser> parser) { ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); String name = parser->get_attribute_value("name"); - class_list[name] = ClassDoc(); - ClassDoc &c = class_list[name]; + class_list[name] = DocData::ClassDoc(); + DocData::ClassDoc &c = class_list[name]; c.name = name; if (parser->has_attribute("inherits")) { @@ -1012,7 +984,7 @@ Error DocData::_load(Ref<XMLParser> parser) { String name3 = parser->get_node_name(); if (name3 == "link") { - TutorialDoc tutorial; + DocData::TutorialDoc tutorial; if (parser->has_attribute("title")) { tutorial.title = parser->get_attribute_value("title"); } @@ -1041,7 +1013,7 @@ Error DocData::_load(Ref<XMLParser> parser) { String name3 = parser->get_node_name(); if (name3 == "member") { - PropertyDoc prop2; + DocData::PropertyDoc prop2; ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); prop2.name = parser->get_attribute_value("name"); @@ -1078,7 +1050,7 @@ Error DocData::_load(Ref<XMLParser> parser) { String name3 = parser->get_node_name(); if (name3 == "theme_item") { - PropertyDoc prop2; + DocData::PropertyDoc prop2; ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); prop2.name = parser->get_attribute_value("name"); @@ -1106,7 +1078,7 @@ Error DocData::_load(Ref<XMLParser> parser) { String name3 = parser->get_node_name(); if (name3 == "constant") { - ConstantDoc constant2; + DocData::ConstantDoc constant2; ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); constant2.name = parser->get_attribute_value("name"); ERR_FAIL_COND_V(!parser->has_attribute("value"), ERR_FILE_CORRUPT); @@ -1155,9 +1127,9 @@ static void _write_string(FileAccess *f, int p_tablevel, const String &p_string) f->store_string(tab + p_string + "\n"); } -Error DocData::save_classes(const String &p_default_path, const Map<String, String> &p_class_path) { - for (Map<String, ClassDoc>::Element *E = class_list.front(); E; E = E->next()) { - ClassDoc &c = E->get(); +Error DocTools::save_classes(const String &p_default_path, const Map<String, String> &p_class_path) { + for (Map<String, DocData::ClassDoc>::Element *E = class_list.front(); E; E = E->next()) { + DocData::ClassDoc &c = E->get(); String save_path; if (p_class_path.has(c.name)) { @@ -1192,7 +1164,7 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri _write_string(f, 1, "<tutorials>"); for (int i = 0; i < c.tutorials.size(); i++) { - TutorialDoc tutorial = c.tutorials.get(i); + DocData::TutorialDoc tutorial = c.tutorials.get(i); String title_attribute = (!tutorial.title.empty()) ? " title=\"" + tutorial.title.xml_escape() + "\"" : ""; _write_string(f, 2, "<link" + title_attribute + ">" + tutorial.link.xml_escape() + "</link>"); } @@ -1203,7 +1175,7 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri c.methods.sort(); for (int i = 0; i < c.methods.size(); i++) { - const MethodDoc &m = c.methods[i]; + const DocData::MethodDoc &m = c.methods[i]; String qualifiers; if (m.qualifiers != "") { @@ -1222,7 +1194,7 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri } for (int j = 0; j < m.arguments.size(); j++) { - const ArgumentDoc &a = m.arguments[j]; + const DocData::ArgumentDoc &a = m.arguments[j]; String enum_text; if (a.enumeration != String()) { @@ -1261,7 +1233,7 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri additional_attributes += " default=\"" + c.properties[i].default_value.xml_escape(true) + "\""; } - const PropertyDoc &p = c.properties[i]; + const DocData::PropertyDoc &p = c.properties[i]; if (c.properties[i].overridden) { _write_string(f, 2, "<member name=\"" + p.name + "\" type=\"" + p.type + "\" setter=\"" + p.setter + "\" getter=\"" + p.getter + "\" override=\"true\"" + additional_attributes + " />"); @@ -1279,10 +1251,10 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri _write_string(f, 1, "<signals>"); for (int i = 0; i < c.signals.size(); i++) { - const MethodDoc &m = c.signals[i]; + const DocData::MethodDoc &m = c.signals[i]; _write_string(f, 2, "<signal name=\"" + m.name + "\">"); for (int j = 0; j < m.arguments.size(); j++) { - const ArgumentDoc &a = m.arguments[j]; + const DocData::ArgumentDoc &a = m.arguments[j]; _write_string(f, 3, "<argument index=\"" + itos(j) + "\" name=\"" + a.name.xml_escape() + "\" type=\"" + a.type.xml_escape() + "\">"); _write_string(f, 3, "</argument>"); } @@ -1300,7 +1272,7 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri _write_string(f, 1, "<constants>"); for (int i = 0; i < c.constants.size(); i++) { - const ConstantDoc &k = c.constants[i]; + const DocData::ConstantDoc &k = c.constants[i]; if (k.is_value_valid) { if (k.enumeration != String()) { _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\" enum=\"" + k.enumeration + "\">"); @@ -1325,7 +1297,7 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri _write_string(f, 1, "<theme_items>"); for (int i = 0; i < c.theme_properties.size(); i++) { - const PropertyDoc &p = c.theme_properties[i]; + const DocData::PropertyDoc &p = c.theme_properties[i]; if (p.default_value != "") { _write_string(f, 2, "<theme_item name=\"" + p.name + "\" type=\"" + p.type + "\" default=\"" + p.default_value.xml_escape(true) + "\">"); @@ -1346,7 +1318,7 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri return OK; } -Error DocData::load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size) { +Error DocTools::load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size) { Vector<uint8_t> data; data.resize(p_uncompressed_size); Compression::decompress(data.ptrw(), p_uncompressed_size, p_data, p_compressed_size, Compression::MODE_DEFLATE); diff --git a/editor/doc_tools.h b/editor/doc_tools.h new file mode 100644 index 0000000000..db27e38c8b --- /dev/null +++ b/editor/doc_tools.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* doc_tools.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 DOC_TOOLS_H +#define DOC_TOOLS_H + +#include "core/doc_data.h" + +class DocTools { +public: + String version; + Map<String, DocData::ClassDoc> class_list; + + static Error erase_classes(const String &p_dir); + + void merge_from(const DocTools &p_data); + void remove_from(const DocTools &p_data); + void add_doc(const DocData::ClassDoc &p_class_doc); + void remove_doc(const String &p_class_name); + bool has_doc(const String &p_class_name); + void generate(bool p_basic_types = false); + Error load_classes(const String &p_dir); + Error save_classes(const String &p_default_path, const Map<String, String> &p_class_path); + + Error _load(Ref<XMLParser> parser); + Error load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size); +}; + +#endif // DOC_DATA_H diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index c66bc9b3fa..738b88a86b 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -357,10 +357,12 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo List<String> to_check; + String importer_name; String source_file = ""; String source_md5 = ""; Vector<String> dest_files; String dest_md5 = ""; + int version = 0; while (true) { assign = Variant(); @@ -384,6 +386,10 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo for (int i = 0; i < fa.size(); i++) { to_check.push_back(fa[i]); } + } else if (assign == "importer_version") { + version = value; + } else if (assign == "importer") { + importer_name = value; } else if (!p_only_imported_files) { if (assign == "source_file") { source_file = value; @@ -399,6 +405,12 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo memdelete(f); + Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name); + + if (importer->get_format_version() > version) { + return true; // version changed, reimport + } + // Read the md5's from a separate file (so the import parameters aren't dependent on the file version String base_path = ResourceFormatImporter::get_singleton()->get_import_base_path(p_path); FileAccess *md5s = FileAccess::open(base_path + ".md5", FileAccess::READ, &err); @@ -799,6 +811,20 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, DirAccess } } + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptLanguage *lang = ScriptServer::get_language(i); + if (lang->supports_documentation() && fi->type == lang->get_type()) { + Ref<Script> script = ResourceLoader::load(path); + if (script == nullptr) { + continue; + } + const Vector<DocData::ClassDoc> &docs = script->get_documentation(); + for (int j = 0; j < docs.size(); j++) { + EditorHelp::get_doc_data()->add_doc(docs[j]); + } + } + } + p_dir->files.push_back(fi); p_progress.update(idx, total); } @@ -1562,6 +1588,10 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector f->store_line("[remap]"); f->store_line(""); f->store_line("importer=\"" + importer->get_importer_name() + "\""); + int version = importer->get_format_version(); + if (version > 0) { + f->store_line("importer_version=" + itos(importer->get_format_version())); + } if (importer->get_resource_type() != "") { f->store_line("type=\"" + importer->get_resource_type() + "\""); } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 2c49782fd2..4c553950a7 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -40,7 +40,7 @@ #define CONTRIBUTE_URL "https://docs.godotengine.org/en/latest/community/contributing/updating_the_class_reference.html" -DocData *EditorHelp::doc = nullptr; +DocTools *EditorHelp::doc = nullptr; void EditorHelp::_init_colors() { title_color = get_theme_color("accent_color", "Editor"); @@ -388,7 +388,7 @@ void EditorHelp::_update_doc() { } // Descendents - if (ClassDB::class_exists(cd.name)) { + if (cd.is_script_doc || ClassDB::class_exists(cd.name)) { bool found = false; bool prev = false; @@ -494,7 +494,19 @@ void EditorHelp::_update_doc() { Set<String> skip_methods; bool property_descr = false; - if (cd.properties.size()) { + bool has_properties = cd.properties.size() != 0; + if (cd.is_script_doc) { + has_properties = false; + for (int i = 0; i < cd.properties.size(); i++) { + if (cd.properties[i].name.begins_with("_") && cd.properties[i].description.empty()) { + continue; + } + has_properties = true; + break; + } + } + + if (has_properties) { section_line.push_back(Pair<String, int>(TTR("Properties"), class_desc->get_line_count() - 2)); class_desc->push_color(title_color); class_desc->push_font(doc_title_font); @@ -509,6 +521,10 @@ void EditorHelp::_update_doc() { class_desc->set_table_column_expand(1, true); for (int i = 0; i < cd.properties.size(); i++) { + // Ignore undocumented private. + if (cd.properties[i].name.begins_with("_") && cd.properties[i].description.empty()) { + continue; + } property_line[cd.properties[i].name] = class_desc->get_line_count() - 2; //gets overridden if description class_desc->push_cell(); @@ -565,6 +581,32 @@ void EditorHelp::_update_doc() { class_desc->pop(); } + if (cd.is_script_doc && (cd.properties[i].setter != "" || cd.properties[i].getter != "")) { + class_desc->push_color(symbol_color); + class_desc->add_text(" [" + TTR("property:") + " "); + class_desc->pop(); // color + + if (cd.properties[i].setter != "") { + class_desc->push_color(value_color); + class_desc->add_text("setter"); + class_desc->pop(); // color + } + if (cd.properties[i].getter != "") { + if (cd.properties[i].setter != "") { + class_desc->push_color(symbol_color); + class_desc->add_text(", "); + class_desc->pop(); // color + } + class_desc->push_color(value_color); + class_desc->add_text("getter"); + class_desc->pop(); // color + } + + class_desc->push_color(symbol_color); + class_desc->add_text("]"); + class_desc->pop(); // color + } + class_desc->pop(); class_desc->pop(); @@ -590,6 +632,10 @@ void EditorHelp::_update_doc() { continue; } } + // Ignore undocumented private. + if (cd.methods[i].name.begins_with("_") && cd.methods[i].description.empty()) { + continue; + } methods.push_back(cd.methods[i]); } @@ -802,13 +848,17 @@ void EditorHelp::_update_doc() { Vector<DocData::ConstantDoc> constants; for (int i = 0; i < cd.constants.size(); i++) { - if (cd.constants[i].enumeration != String()) { + if (!cd.constants[i].enumeration.empty()) { if (!enums.has(cd.constants[i].enumeration)) { enums[cd.constants[i].enumeration] = Vector<DocData::ConstantDoc>(); } enums[cd.constants[i].enumeration].push_back(cd.constants[i]); } else { + // Ignore undocumented private. + if (cd.constants[i].name.begins_with("_") && cd.constants[i].description.empty()) { + continue; + } constants.push_back(cd.constants[i]); } } @@ -848,6 +898,19 @@ void EditorHelp::_update_doc() { class_desc->add_newline(); class_desc->add_newline(); + // Enum description. + if (e != "@unnamed_enums" && cd.enums.has(e)) { + class_desc->push_color(text_color); + class_desc->push_font(doc_font); + class_desc->push_indent(1); + _add_text(cd.enums[e]); + class_desc->pop(); + class_desc->pop(); + class_desc->pop(); + class_desc->add_newline(); + class_desc->add_newline(); + } + class_desc->push_indent(1); Vector<DocData::ConstantDoc> enum_list = E->get(); @@ -1018,60 +1081,89 @@ void EditorHelp::_update_doc() { class_desc->pop(); // color } + if (cd.is_script_doc && (cd.properties[i].setter != "" || cd.properties[i].getter != "")) { + class_desc->push_color(symbol_color); + class_desc->add_text(" [" + TTR("property:") + " "); + class_desc->pop(); // color + + if (cd.properties[i].setter != "") { + class_desc->push_color(value_color); + class_desc->add_text("setter"); + class_desc->pop(); // color + } + if (cd.properties[i].getter != "") { + if (cd.properties[i].setter != "") { + class_desc->push_color(symbol_color); + class_desc->add_text(", "); + class_desc->pop(); // color + } + class_desc->push_color(value_color); + class_desc->add_text("getter"); + class_desc->pop(); // color + } + + class_desc->push_color(symbol_color); + class_desc->add_text("]"); + class_desc->pop(); // color + } + class_desc->pop(); // font class_desc->pop(); // cell - Map<String, DocData::MethodDoc> method_map; - for (int j = 0; j < methods.size(); j++) { - method_map[methods[j].name] = methods[j]; - } + // Script doc doesn't have setter, getter. + if (!cd.is_script_doc) { + Map<String, DocData::MethodDoc> method_map; + for (int j = 0; j < methods.size(); j++) { + method_map[methods[j].name] = methods[j]; + } - if (cd.properties[i].setter != "") { - class_desc->push_cell(); - class_desc->pop(); // cell + if (cd.properties[i].setter != "") { + class_desc->push_cell(); + class_desc->pop(); // cell - class_desc->push_cell(); - class_desc->push_font(doc_code_font); - class_desc->push_color(text_color); - if (method_map[cd.properties[i].setter].arguments.size() > 1) { - // Setters with additional arguments are exposed in the method list, so we link them here for quick access. - class_desc->push_meta("@method " + cd.properties[i].setter); - class_desc->add_text(cd.properties[i].setter + TTR("(value)")); - class_desc->pop(); - } else { - class_desc->add_text(cd.properties[i].setter + TTR("(value)")); + class_desc->push_cell(); + class_desc->push_font(doc_code_font); + class_desc->push_color(text_color); + if (method_map[cd.properties[i].setter].arguments.size() > 1) { + // Setters with additional arguments are exposed in the method list, so we link them here for quick access. + class_desc->push_meta("@method " + cd.properties[i].setter); + class_desc->add_text(cd.properties[i].setter + TTR("(value)")); + class_desc->pop(); + } else { + class_desc->add_text(cd.properties[i].setter + TTR("(value)")); + } + class_desc->pop(); // color + class_desc->push_color(comment_color); + class_desc->add_text(" setter"); + class_desc->pop(); // color + class_desc->pop(); // font + class_desc->pop(); // cell + method_line[cd.properties[i].setter] = property_line[cd.properties[i].name]; } - class_desc->pop(); // color - class_desc->push_color(comment_color); - class_desc->add_text(" setter"); - class_desc->pop(); // color - class_desc->pop(); // font - class_desc->pop(); // cell - method_line[cd.properties[i].setter] = property_line[cd.properties[i].name]; - } - if (cd.properties[i].getter != "") { - class_desc->push_cell(); - class_desc->pop(); // cell + if (cd.properties[i].getter != "") { + class_desc->push_cell(); + class_desc->pop(); // cell - class_desc->push_cell(); - class_desc->push_font(doc_code_font); - class_desc->push_color(text_color); - if (method_map[cd.properties[i].getter].arguments.size() > 0) { - // Getters with additional arguments are exposed in the method list, so we link them here for quick access. - class_desc->push_meta("@method " + cd.properties[i].getter); - class_desc->add_text(cd.properties[i].getter + "()"); - class_desc->pop(); - } else { - class_desc->add_text(cd.properties[i].getter + "()"); + class_desc->push_cell(); + class_desc->push_font(doc_code_font); + class_desc->push_color(text_color); + if (method_map[cd.properties[i].getter].arguments.size() > 0) { + // Getters with additional arguments are exposed in the method list, so we link them here for quick access. + class_desc->push_meta("@method " + cd.properties[i].getter); + class_desc->add_text(cd.properties[i].getter + "()"); + class_desc->pop(); + } else { + class_desc->add_text(cd.properties[i].getter + "()"); + } + class_desc->pop(); //color + class_desc->push_color(comment_color); + class_desc->add_text(" getter"); + class_desc->pop(); //color + class_desc->pop(); //font + class_desc->pop(); //cell + method_line[cd.properties[i].getter] = property_line[cd.properties[i].name]; } - class_desc->pop(); //color - class_desc->push_color(comment_color); - class_desc->add_text(" getter"); - class_desc->pop(); //color - class_desc->pop(); //font - class_desc->pop(); //cell - method_line[cd.properties[i].getter] = property_line[cd.properties[i].name]; } class_desc->pop(); // table @@ -1082,13 +1174,17 @@ void EditorHelp::_update_doc() { class_desc->push_color(text_color); class_desc->push_font(doc_font); class_desc->push_indent(1); - if (cd.properties[i].description.strip_edges() != String()) { + if (!cd.properties[i].description.strip_edges().empty()) { _add_text(DTR(cd.properties[i].description)); } else { class_desc->add_image(get_theme_icon("Error", "EditorIcons")); class_desc->add_text(" "); class_desc->push_color(comment_color); - class_desc->append_bbcode(TTR("There is currently no description for this property. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text)); + if (cd.is_script_doc) { + class_desc->append_bbcode(TTR("There is currently no description for this property.")); + } else { + class_desc->append_bbcode(TTR("There is currently no description for this property. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text)); + } class_desc->pop(); } class_desc->pop(); @@ -1133,13 +1229,17 @@ void EditorHelp::_update_doc() { class_desc->push_color(text_color); class_desc->push_font(doc_font); class_desc->push_indent(1); - if (methods_filtered[i].description.strip_edges() != String()) { + if (!methods_filtered[i].description.strip_edges().empty()) { _add_text(DTR(methods_filtered[i].description)); } else { class_desc->add_image(get_theme_icon("Error", "EditorIcons")); class_desc->add_text(" "); class_desc->push_color(comment_color); - class_desc->append_bbcode(TTR("There is currently no description for this method. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text)); + if (cd.is_script_doc) { + class_desc->append_bbcode(TTR("There is currently no description for this method.")); + } else { + class_desc->append_bbcode(TTR("There is currently no description for this method. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text)); + } class_desc->pop(); } @@ -1223,7 +1323,7 @@ void EditorHelp::_help_callback(const String &p_topic) { } static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt) { - DocData *doc = EditorHelp::get_doc_data(); + DocTools *doc = EditorHelp::get_doc_data(); String base_path; Ref<Font> doc_font = p_rt->get_theme_font("doc", "EditorFonts"); @@ -1518,9 +1618,9 @@ void EditorHelp::_add_text(const String &p_bbcode) { } void EditorHelp::generate_doc() { - doc = memnew(DocData); + doc = memnew(DocTools); doc->generate(true); - DocData compdoc; + DocTools compdoc; compdoc.load_compressed(_doc_data_compressed, _doc_data_compressed_size, _doc_data_uncompressed_size); doc->merge_from(compdoc); //ensure all is up to date } @@ -1549,6 +1649,12 @@ void EditorHelp::go_to_class(const String &p_class, int p_scroll) { _goto_desc(p_class, p_scroll); } +void EditorHelp::update_doc() { + ERR_FAIL_COND(!doc->class_list.has(edited_class)); + ERR_FAIL_COND(!doc->class_list[edited_class].is_script_doc); + _update_doc(); +} + Vector<Pair<String, int>> EditorHelp::get_sections() { Vector<Pair<String, int>> sections; diff --git a/editor/editor_help.h b/editor/editor_help.h index cdb674cffd..737f841d30 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -32,7 +32,7 @@ #define EDITOR_HELP_H #include "editor/code_editor.h" -#include "editor/doc_data.h" +#include "editor/doc_tools.h" #include "editor/editor_plugin.h" #include "scene/gui/margin_container.h" #include "scene/gui/menu_button.h" @@ -118,7 +118,7 @@ class EditorHelp : public VBoxContainer { RichTextLabel *class_desc; HSplitContainer *h_split; - static DocData *doc; + static DocTools *doc; ConfirmationDialog *search_dialog; LineEdit *search; @@ -166,10 +166,11 @@ protected: public: static void generate_doc(); - static DocData *get_doc_data() { return doc; } + static DocTools *get_doc_data() { return doc; } void go_to_help(const String &p_help); void go_to_class(const String &p_class, int p_scroll = 0); + void update_doc(); Vector<Pair<String, int>> get_sections(); void scroll_to_section(int p_section_index); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 0c419168c0..d88a0e3ff8 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -32,6 +32,7 @@ #include "array_property_edit.h" #include "dictionary_property_edit.h" +#include "editor/doc_tools.h" #include "editor_feature_profile.h" #include "editor_node.h" #include "editor_scale.h" @@ -1708,7 +1709,7 @@ void EditorInspector::update_tree() { StringName type2 = p.name; if (!class_descr_cache.has(type2)) { String descr; - DocData *dd = EditorHelp::get_doc_data(); + DocTools *dd = EditorHelp::get_doc_data(); Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(type2); if (E) { descr = DTR(E->get().brief_description); @@ -1878,7 +1879,7 @@ void EditorInspector::update_tree() { } if (!found) { - DocData *dd = EditorHelp::get_doc_data(); + DocTools *dd = EditorHelp::get_doc_data(); Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(classname); while (F && descr == String()) { for (int i = 0; i < F->get().properties.size(); i++) { diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 88976ed332..c589a3c042 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -676,7 +676,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("panel", "PopupDialog", style_popup); // PopupMenu - theme->set_stylebox("panel", "PopupMenu", style_popup); + const int popup_menu_margin_size = default_margin_size * 1.5 * EDSCALE; + Ref<StyleBoxFlat> style_popup_menu = style_popup->duplicate(); + style_popup_menu->set_default_margin(MARGIN_LEFT, popup_menu_margin_size); + style_popup_menu->set_default_margin(MARGIN_TOP, popup_menu_margin_size); + style_popup_menu->set_default_margin(MARGIN_RIGHT, popup_menu_margin_size); + style_popup_menu->set_default_margin(MARGIN_BOTTOM, popup_menu_margin_size); + + theme->set_stylebox("panel", "PopupMenu", style_popup_menu); theme->set_stylebox("separator", "PopupMenu", style_popup_separator); theme->set_stylebox("labeled_separator_left", "PopupMenu", style_popup_labeled_separator_left); theme->set_stylebox("labeled_separator_right", "PopupMenu", style_popup_labeled_separator_right); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index c65c796e5e..aa19bdf342 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2368,16 +2368,16 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str } if (p_paths.size() == 1) { - p_popup->add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), TTR("Copy Path"), FILE_COPY_PATH); + p_popup->add_icon_shortcut(get_theme_icon("ActionCopy", "EditorIcons"), ED_GET_SHORTCUT("filesystem_dock/copy_path"), FILE_COPY_PATH); if (p_paths[0] != "res://") { - p_popup->add_icon_item(get_theme_icon("Rename", "EditorIcons"), TTR("Rename..."), FILE_RENAME); - p_popup->add_icon_item(get_theme_icon("Duplicate", "EditorIcons"), TTR("Duplicate..."), FILE_DUPLICATE); + p_popup->add_icon_shortcut(get_theme_icon("Rename", "EditorIcons"), ED_GET_SHORTCUT("filesystem_dock/rename"), FILE_RENAME); + p_popup->add_icon_shortcut(get_theme_icon("Duplicate", "EditorIcons"), ED_GET_SHORTCUT("filesystem_dock/duplicate"), FILE_DUPLICATE); } } if (p_paths.size() > 1 || p_paths[0] != "res://") { p_popup->add_icon_item(get_theme_icon("MoveUp", "EditorIcons"), TTR("Move To..."), FILE_MOVE); - p_popup->add_icon_item(get_theme_icon("Remove", "EditorIcons"), TTR("Move to Trash"), FILE_REMOVE); + p_popup->add_icon_shortcut(get_theme_icon("Remove", "EditorIcons"), ED_GET_SHORTCUT("filesystem_dock/delete"), FILE_REMOVE); } if (p_paths.size() == 1) { @@ -2511,7 +2511,11 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) { _tree_rmb_option(FILE_REMOVE); } else if (ED_IS_SHORTCUT("filesystem_dock/rename", p_event)) { _tree_rmb_option(FILE_RENAME); + } else { + return; } + + accept_event(); } } @@ -2526,7 +2530,11 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) { _file_list_rmb_option(FILE_REMOVE); } else if (ED_IS_SHORTCUT("filesystem_dock/rename", p_event)) { _file_list_rmb_option(FILE_RENAME); + } else { + return; } + + accept_event(); } } @@ -2682,8 +2690,8 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { // `KEY_MASK_CMD | KEY_C` conflicts with other editor shortcuts. ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_C); ED_SHORTCUT("filesystem_dock/duplicate", TTR("Duplicate..."), KEY_MASK_CMD | KEY_D); - ED_SHORTCUT("filesystem_dock/delete", TTR("Delete"), KEY_DELETE); - ED_SHORTCUT("filesystem_dock/rename", TTR("Rename")); + ED_SHORTCUT("filesystem_dock/delete", TTR("Move to Trash"), KEY_DELETE); + ED_SHORTCUT("filesystem_dock/rename", TTR("Rename..."), KEY_F2); VBoxContainer *top_vbc = memnew(VBoxContainer); add_child(top_vbc); diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index 12cbaaa885..41099dc8ea 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -844,19 +844,19 @@ Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<ArrayMesh> &p_me for (int k = 0; k < vertex_array.size(); k++) { if (normal_src) { - surftool->add_normal(vertex_array[k].normal); + surftool->set_normal(vertex_array[k].normal); if (binormal_src && tangent_src) { - surftool->add_tangent(vertex_array[k].tangent); + surftool->set_tangent(vertex_array[k].tangent); } } if (uv_src) { - surftool->add_uv(Vector2(vertex_array[k].uv.x, vertex_array[k].uv.y)); + surftool->set_uv(Vector2(vertex_array[k].uv.x, vertex_array[k].uv.y)); } if (uv2_src) { - surftool->add_uv2(Vector2(vertex_array[k].uv2.x, vertex_array[k].uv2.y)); + surftool->set_uv2(Vector2(vertex_array[k].uv2.x, vertex_array[k].uv2.y)); } if (color_src) { - surftool->add_color(vertex_array[k].color); + surftool->set_color(vertex_array[k].color); } if (has_weights) { @@ -876,8 +876,8 @@ Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<ArrayMesh> &p_me } } - surftool->add_bones(bones); - surftool->add_weights(weights); + surftool->set_bones(bones); + surftool->set_weights(weights); } surftool->add_vertex(vertex_array[k].vertex); @@ -923,7 +923,7 @@ Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<ArrayMesh> &p_me mr.push_back(a); } - p_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, d, mr, Dictionary(), p_use_compression ? Mesh::ARRAY_COMPRESS_DEFAULT : 0); + p_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, d, mr, Dictionary(), 0); if (material.is_valid()) { if (p_use_mesh_material) { diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp index 0c860a8965..c1877895ce 100644 --- a/editor/import/editor_scene_importer_gltf.cpp +++ b/editor/import/editor_scene_importer_gltf.cpp @@ -970,8 +970,7 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { return OK; } - bool compress_vert_data = state.import_flags & IMPORT_USE_COMPRESSION; - uint32_t mesh_flags = compress_vert_data ? Mesh::ARRAY_COMPRESS_DEFAULT : 0; + uint32_t mesh_flags = 0; Array meshes = state.json["meshes"]; for (GLTFMeshIndex i = 0; i < meshes.size(); i++) { diff --git a/editor/import/resource_importer_obj.cpp b/editor/import/resource_importer_obj.cpp index 49b47bf4be..d4560a2984 100644 --- a/editor/import/resource_importer_obj.cpp +++ b/editor/import/resource_importer_obj.cpp @@ -210,7 +210,7 @@ static Error _parse_obj(const String &p_path, List<Ref<Mesh>> &r_meshes, bool p_ bool generate_tangents = p_generate_tangents; Vector3 scale_mesh = p_scale_mesh; Vector3 offset_mesh = p_offset_mesh; - int mesh_flags = p_optimize ? Mesh::ARRAY_COMPRESS_DEFAULT : 0; + int mesh_flags = 0; Vector<Vector3> vertices; Vector<Vector3> normals; @@ -294,7 +294,7 @@ static Error _parse_obj(const String &p_path, List<Ref<Mesh>> &r_meshes, bool p_ norm += normals.size() + 1; } ERR_FAIL_INDEX_V(norm, normals.size(), ERR_FILE_CORRUPT); - surf_tool->add_normal(normals[norm]); + surf_tool->set_normal(normals[norm]); } if (face[idx].size() >= 2 && face[idx][1] != String()) { @@ -303,7 +303,7 @@ static Error _parse_obj(const String &p_path, List<Ref<Mesh>> &r_meshes, bool p_ uv += uvs.size() + 1; } ERR_FAIL_INDEX_V(uv, uvs.size(), ERR_FILE_CORRUPT); - surf_tool->add_uv(uvs[uv]); + surf_tool->set_uv(uvs[uv]); } int vtx = face[idx][0].to_int() - 1; @@ -473,6 +473,10 @@ String ResourceImporterOBJ::get_resource_type() const { return "Mesh"; } +int ResourceImporterOBJ::get_format_version() const { + return 1; +} + int ResourceImporterOBJ::get_preset_count() const { return 0; } diff --git a/editor/import/resource_importer_obj.h b/editor/import/resource_importer_obj.h index 4083bc7403..97f747b33c 100644 --- a/editor/import/resource_importer_obj.h +++ b/editor/import/resource_importer_obj.h @@ -54,6 +54,7 @@ public: virtual void get_recognized_extensions(List<String> *p_extensions) const override; virtual String get_save_extension() const override; virtual String get_resource_type() const override; + virtual int get_format_version() const override; virtual int get_preset_count() const override; virtual String get_preset_name(int p_idx) const override; diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 5dcdf6bec4..fc4f673ec4 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -172,6 +172,10 @@ String ResourceImporterScene::get_resource_type() const { return "PackedScene"; } +int ResourceImporterScene::get_format_version() const { + return 1; +} + bool ResourceImporterScene::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { if (p_option.begins_with("animation/")) { if (p_option != "animation/import" && !bool(p_options["animation/import"])) { diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h index 465d11116b..cd61ec01f2 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -133,6 +133,7 @@ public: virtual void get_recognized_extensions(List<String> *p_extensions) const override; virtual String get_save_extension() const override; virtual String get_resource_type() const override; + virtual int get_format_version() const override; virtual int get_preset_count() const override; virtual String get_preset_name(int p_idx) const override; diff --git a/editor/input_map_editor.cpp b/editor/input_map_editor.cpp index 5b6d850096..686fd4c08b 100644 --- a/editor/input_map_editor.cpp +++ b/editor/input_map_editor.cpp @@ -35,47 +35,6 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" -static const char *_button_descriptions[JOY_BUTTON_SDL_MAX] = { - TTRC("Bottom Action, Sony Cross, Xbox A, Nintendo B"), - TTRC("Right Action, Sony Circle, Xbox B, Nintendo A"), - TTRC("Left Action, Sony Square, Xbox X, Nintendo Y"), - TTRC("Top Action, Sony Triangle, Xbox Y, Nintendo X"), - TTRC("Back, Sony Select, Xbox Back, Nintendo -"), - TTRC("Guide, Sony PS, Xbox Home"), - TTRC("Start, Nintendo +"), - TTRC("Left Stick, Sony L3, Xbox L/LS"), - TTRC("Right Stick, Sony R3, Xbox R/RS"), - TTRC("Left Shoulder, Sony L1, Xbox LB"), - TTRC("Right Shoulder, Sony R1, Xbox RB"), - TTRC("D-pad Up"), - TTRC("D-pad Down"), - TTRC("D-pad Left"), - TTRC("D-pad Right"), -}; - -static const char *_axis_descriptions[JOY_AXIS_MAX * 2] = { - TTRC("Left Stick Left, Joystick 0 Left"), - TTRC("Left Stick Right, Joystick 0 Right"), - TTRC("Left Stick Up, Joystick 0 Up"), - TTRC("Left Stick Down, Joystick 0 Down"), - TTRC("Right Stick Left, Joystick 1 Left"), - TTRC("Right Stick Right, Joystick 1 Right"), - TTRC("Right Stick Up, Joystick 1 Up"), - TTRC("Right Stick Down, Joystick 1 Down"), - TTRC("Joystick 2 Left"), - TTRC("Left Trigger, L2, LT, Joystick 2 Right"), - TTRC("Joystick 2 Up"), - TTRC("Right Trigger, R2, RT, Joystick 2 Down"), - TTRC("Joystick 3 Left"), - TTRC("Joystick 3 Right"), - TTRC("Joystick 3 Up"), - TTRC("Joystick 3 Down"), - TTRC("Joystick 4 Left"), - TTRC("Joystick 4 Right"), - TTRC("Joystick 4 Up"), - TTRC("Joystick 4 Down"), -}; - void InputMapEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -395,6 +354,42 @@ void InputMapEditor::_show_last_added(const Ref<InputEvent> &p_event, const Stri } } +// Maps to 2*axis if value is neg, or + 1 if value is pos. +static const char *_joy_axis_descriptions[JOY_AXIS_MAX * 2] = { + TTRC("Left Stick Left, Joystick 0 Left"), + TTRC("Left Stick Right, Joystick 0 Right"), + TTRC("Left Stick Up, Joystick 0 Up"), + TTRC("Left Stick Down, Joystick 0 Down"), + TTRC("Right Stick Left, Joystick 1 Left"), + TTRC("Right Stick Right, Joystick 1 Right"), + TTRC("Right Stick Up, Joystick 1 Up"), + TTRC("Right Stick Down, Joystick 1 Down"), + TTRC("Joystick 2 Left"), + TTRC("Left Trigger, Sony L2, Xbox LT, Joystick 2 Right"), + TTRC("Joystick 2 Up"), + TTRC("Right Trigger, Sony R2, Xbox RT, Joystick 2 Down"), + TTRC("Joystick 3 Left"), + TTRC("Joystick 3 Right"), + TTRC("Joystick 3 Up"), + TTRC("Joystick 3 Down"), + TTRC("Joystick 4 Left"), + TTRC("Joystick 4 Right"), + TTRC("Joystick 4 Up"), + TTRC("Joystick 4 Down"), +}; + +// Separate from `InputEvent::as_text()` since the descriptions need to be different for the input map editor. See #43660. +String InputMapEditor::_get_joypad_motion_event_text(const Ref<InputEventJoypadMotion> &p_event) { + ERR_FAIL_COND_V_MSG(p_event.is_null(), String(), "Provided event is not a valid instance of InputEventJoypadMotion"); + + String desc = TTR("Unknown Joypad Axis"); + if (p_event->get_axis() < JOY_AXIS_MAX) { + desc = RTR(_joy_axis_descriptions[2 * p_event->get_axis() + (p_event->get_axis_value() < 0 ? 0 : 1)]); + } + + return vformat("Joypad Axis %s %s (%s)", itos(p_event->get_axis()), p_event->get_axis_value() < 0 ? "-" : "+", desc); +} + void InputMapEditor::_wait_for_key(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; @@ -481,9 +476,11 @@ void InputMapEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_event) { device_index_label->set_text(TTR("Joypad Axis Index:")); device_index->clear(); for (int i = 0; i < JOY_AXIS_MAX * 2; i++) { - String desc = TTR("Axis") + " " + itos(i / 2) + " " + ((i & 1) ? "+" : "-") + - " (" + TTR(_axis_descriptions[i]) + ")"; - device_index->add_item(desc); + Ref<InputEventJoypadMotion> jm; + jm.instance(); + jm->set_axis(i / 2); + jm->set_axis_value((i & 1) ? 1 : -1); + device_index->add_item(_get_joypad_motion_event_text(jm)); } device_input->popup_centered(Size2(350, 95) * EDSCALE); @@ -502,11 +499,10 @@ void InputMapEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_event) { device_index_label->set_text(TTR("Joypad Button Index:")); device_index->clear(); for (int i = 0; i < JOY_BUTTON_MAX; i++) { - String desc = TTR("Button") + " " + itos(i); - if (i < JOY_BUTTON_SDL_MAX) { - desc += " (" + TTR(_button_descriptions[i]) + ")"; - } - device_index->add_item(desc); + Ref<InputEventJoypadButton> jb; + jb.instance(); + jb->set_button_index(i); + device_index->add_item(jb->as_text()); } device_input->popup_centered(Size2(350, 95) * EDSCALE); @@ -714,14 +710,7 @@ void InputMapEditor::_update_actions() { Ref<InputEventJoypadButton> jb = event; if (jb.is_valid()) { - const int idx = jb->get_button_index(); - String str = _get_device_string(jb->get_device()) + ", " + - TTR("Button") + " " + itos(idx); - if (idx >= 0 && idx < JOY_BUTTON_SDL_MAX) { - str += String() + " (" + TTR(_button_descriptions[jb->get_button_index()]) + ")"; - } - - action2->set_text(0, str); + action2->set_text(0, jb->as_text()); action2->set_icon(0, input_editor->get_theme_icon("JoyButton", "EditorIcons")); } @@ -754,12 +743,8 @@ void InputMapEditor::_update_actions() { Ref<InputEventJoypadMotion> jm = event; if (jm.is_valid()) { - int ax = jm->get_axis(); - int n = 2 * ax + (jm->get_axis_value() < 0 ? 0 : 1); - String str = _get_device_string(jm->get_device()) + ", " + - TTR("Axis") + " " + itos(ax) + " " + (jm->get_axis_value() < 0 ? "-" : "+") + - " (" + _axis_descriptions[n] + ")"; - action2->set_text(0, str); + device_index->add_item(_get_joypad_motion_event_text(jm)); + action2->set_text(0, jm->as_text()); action2->set_icon(0, input_editor->get_theme_icon("JoyAxis", "EditorIcons")); } action2->set_metadata(0, i); diff --git a/editor/input_map_editor.h b/editor/input_map_editor.h index b9a3ce19d4..b59eb97e1d 100644 --- a/editor/input_map_editor.h +++ b/editor/input_map_editor.h @@ -88,6 +88,8 @@ class InputMapEditor : public Control { void _press_a_key_confirm(); void _show_last_added(const Ref<InputEvent> &p_event, const String &p_name); + String _get_joypad_motion_event_text(const Ref<InputEventJoypadMotion> &p_event); + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); diff --git a/editor/node_3d_editor_gizmos.cpp b/editor/node_3d_editor_gizmos.cpp index b7f7d637d2..97bafdd167 100644 --- a/editor/node_3d_editor_gizmos.cpp +++ b/editor/node_3d_editor_gizmos.cpp @@ -1625,13 +1625,13 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { int pointidx = 0; for (int j = 0; j < 3; j++) { bones.write[0] = parent; - surface_tool->add_bones(bones); - surface_tool->add_weights(weights); - surface_tool->add_color(rootcolor); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->set_color(rootcolor); surface_tool->add_vertex(v0 - grests[parent].basis[j].normalized() * dist * 0.05); - surface_tool->add_bones(bones); - surface_tool->add_weights(weights); - surface_tool->add_color(rootcolor); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->set_color(rootcolor); surface_tool->add_vertex(v0 + grests[parent].basis[j].normalized() * dist * 0.05); if (j == closest) { @@ -1654,24 +1654,24 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { point += axis * dist * 0.1; bones.write[0] = parent; - surface_tool->add_bones(bones); - surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->set_color(bonecolor); surface_tool->add_vertex(v0); - surface_tool->add_bones(bones); - surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->set_color(bonecolor); surface_tool->add_vertex(point); bones.write[0] = parent; - surface_tool->add_bones(bones); - surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->set_color(bonecolor); surface_tool->add_vertex(point); bones.write[0] = i; - surface_tool->add_bones(bones); - surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->set_color(bonecolor); surface_tool->add_vertex(v1); points[pointidx++] = point; } @@ -1680,13 +1680,13 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { SWAP(points[1], points[2]); for (int j = 0; j < 4; j++) { bones.write[0] = parent; - surface_tool->add_bones(bones); - surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->set_color(bonecolor); surface_tool->add_vertex(points[j]); - surface_tool->add_bones(bones); - surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->set_color(bonecolor); surface_tool->add_vertex(points[(j + 1) % 4]); } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 5d9c46cdcb..b1bac34f46 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5972,7 +5972,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { p = view_menu->get_popup(); p->set_hide_on_checkable_item_selection(false); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_G), SHOW_GRID); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_MASK_CTRL | KEY_G), SHOW_GRID); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), KEY_H), SHOW_HELPERS); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers")), SHOW_RULERS); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), KEY_Y), SHOW_GUIDES); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index ccf868b3d5..2530b4ecfe 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -63,12 +63,15 @@ #define GIZMO_SCALE_OFFSET (GIZMO_CIRCLE_SIZE + 0.3) #define GIZMO_ARROW_OFFSET (GIZMO_CIRCLE_SIZE + 0.3) -#define ZOOM_MIN_DISTANCE 0.001 -#define ZOOM_MULTIPLIER 1.08 -#define ZOOM_INDICATOR_DELAY_S 1.5 - -#define FREELOOK_MIN_SPEED 0.01 -#define FREELOOK_SPEED_MULTIPLIER 1.08 +#define ZOOM_FREELOOK_MIN 0.01 +#define ZOOM_FREELOOK_MULTIPLIER 1.08 +#define ZOOM_FREELOOK_INDICATOR_DELAY_S 1.5 + +#ifdef REAL_T_IS_DOUBLE +#define ZOOM_FREELOOK_MAX 1'000'000'000'000 +#else +#define ZOOM_FREELOOK_MAX 10'000 +#endif #define MIN_Z 0.01 #define MAX_Z 1000000.0 @@ -1117,7 +1120,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (b.is_valid()) { emit_signal("clicked", this); - float zoom_factor = 1 + (ZOOM_MULTIPLIER - 1) * b->get_factor(); + float zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor(); switch (b->get_button_index()) { case BUTTON_WHEEL_UP: { if (is_freelook_active()) { @@ -2207,34 +2210,28 @@ void Node3DEditorViewport::set_freelook_active(bool active_now) { } void Node3DEditorViewport::scale_cursor_distance(real_t scale) { - // Prevents zero distance which would short-circuit any scaling - if (cursor.distance < ZOOM_MIN_DISTANCE) { - cursor.distance = ZOOM_MIN_DISTANCE; - } - - cursor.distance *= scale; - - if (cursor.distance < ZOOM_MIN_DISTANCE) { - cursor.distance = ZOOM_MIN_DISTANCE; + real_t min_distance = MAX(camera->get_znear() * 4, ZOOM_FREELOOK_MIN); + real_t max_distance = MIN(camera->get_zfar() / 4, ZOOM_FREELOOK_MAX); + if (unlikely(min_distance > max_distance)) { + cursor.distance = (min_distance + max_distance) / 2; + } else { + cursor.distance = CLAMP(cursor.distance * scale, min_distance, max_distance); } - zoom_indicator_delay = ZOOM_INDICATOR_DELAY_S; + zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S; surface->update(); } void Node3DEditorViewport::scale_freelook_speed(real_t scale) { - // Prevents zero distance which would short-circuit any scaling - if (freelook_speed < FREELOOK_MIN_SPEED) { - freelook_speed = FREELOOK_MIN_SPEED; - } - - freelook_speed *= scale; - - if (freelook_speed < FREELOOK_MIN_SPEED) { - freelook_speed = FREELOOK_MIN_SPEED; + real_t min_speed = MAX(camera->get_znear() * 4, ZOOM_FREELOOK_MIN); + real_t max_speed = MIN(camera->get_zfar() / 4, ZOOM_FREELOOK_MAX); + if (unlikely(min_speed > max_speed)) { + freelook_speed = (min_speed + max_speed) / 2; + } else { + freelook_speed = CLAMP(freelook_speed * scale, min_speed, max_speed); } - zoom_indicator_delay = ZOOM_INDICATOR_DELAY_S; + zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S; surface->update(); } @@ -2698,19 +2695,13 @@ void Node3DEditorViewport::_draw() { if (is_freelook_active()) { // Show speed - real_t min_speed = FREELOOK_MIN_SPEED; - real_t max_speed = camera->get_zfar(); + real_t min_speed = MAX(camera->get_znear() * 4, ZOOM_FREELOOK_MIN); + real_t max_speed = MIN(camera->get_zfar() / 4, ZOOM_FREELOOK_MAX); real_t scale_length = (max_speed - min_speed); if (!Math::is_zero_approx(scale_length)) { real_t logscale_t = 1.0 - Math::log(1 + freelook_speed - min_speed) / Math::log(1 + scale_length); - // There is no real maximum speed so that factor can become negative, - // Let's make it look asymptotic instead (will decrease slower and slower). - if (logscale_t < 0.25) { - logscale_t = 0.25 * Math::exp(4.0 * logscale_t - 1.0); - } - // Display the freelook speed to help the user get a better sense of scale. const int precision = freelook_speed < 1.0 ? 2 : 1; draw_indicator_bar( @@ -2725,19 +2716,13 @@ void Node3DEditorViewport::_draw() { } else { // Show zoom - real_t min_distance = ZOOM_MIN_DISTANCE; // TODO Why not pick znear to limit zoom? - real_t max_distance = camera->get_zfar(); + real_t min_distance = MAX(camera->get_znear() * 4, ZOOM_FREELOOK_MIN); + real_t max_distance = MIN(camera->get_zfar() / 4, ZOOM_FREELOOK_MAX); real_t scale_length = (max_distance - min_distance); if (!Math::is_zero_approx(scale_length)) { real_t logscale_t = 1.0 - Math::log(1 + cursor.distance - min_distance) / Math::log(1 + scale_length); - // There is no real maximum distance so that factor can become negative, - // Let's make it look asymptotic instead (will decrease slower and slower). - if (logscale_t < 0.25) { - logscale_t = 0.25 * Math::exp(4.0 * logscale_t - 1.0); - } - // Display the zoom center distance to help the user get a better sense of scale. const int precision = cursor.distance < 1.0 ? 2 : 1; draw_indicator_bar( @@ -2970,11 +2955,11 @@ void Node3DEditorViewport::_menu_option(int p_option) { int idx = view_menu->get_popup()->get_item_index(VIEW_GIZMOS); bool current = view_menu->get_popup()->is_item_checked(idx); current = !current; + uint32_t layers = ((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + index)) | (1 << GIZMO_GRID_LAYER) | (1 << MISC_TOOL_LAYER); if (current) { - camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + index)) | (1 << GIZMO_EDIT_LAYER) | (1 << GIZMO_GRID_LAYER)); - } else { - camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + index)) | (1 << GIZMO_GRID_LAYER)); + layers |= (1 << GIZMO_EDIT_LAYER); } + camera->set_cull_mask(layers); view_menu->get_popup()->set_item_checked(idx, current); } break; @@ -3895,7 +3880,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito surface->set_clip_contents(true); camera = memnew(Camera3D); camera->set_disable_gizmo(true); - camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + p_index)) | (1 << GIZMO_EDIT_LAYER) | (1 << GIZMO_GRID_LAYER)); + camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + p_index)) | (1 << GIZMO_EDIT_LAYER) | (1 << GIZMO_GRID_LAYER) | (1 << MISC_TOOL_LAYER)); viewport->add_child(camera); camera->make_current(); surface->set_focus_mode(FOCUS_ALL); @@ -4514,12 +4499,14 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting( si->sbox_instance, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER); si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2( selection_box_xray->get_rid(), sp->get_world_3d()->get_scenario()); RS::get_singleton()->instance_geometry_set_cast_shadows_setting( si->sbox_instance_xray, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::MISC_TOOL_LAYER); return si; } @@ -5360,7 +5347,7 @@ void Node3DEditor::_init_indicators() { Vector2 ofs = Vector2(Math::cos((Math_PI * 2.0 * k) / m), Math::sin((Math_PI * 2.0 * k) / m)); Vector3 normal = ivec * ofs.x + ivec2 * ofs.y; - surftool->add_normal(basis.xform(normal)); + surftool->set_normal(basis.xform(normal)); surftool->add_vertex(vertex); } } diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 2e98fcad4c..12080ea1f0 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -229,6 +229,7 @@ public: GIZMO_BASE_LAYER = 27, GIZMO_EDIT_LAYER = 26, GIZMO_GRID_LAYER = 25, + MISC_TOOL_LAYER = 24, FRAME_TIME_HISTORY = 20, }; diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 6ee8193291..12790a6746 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1249,13 +1249,35 @@ void ScriptEditor::_menu_option(int p_option) { RES resource = current->get_edited_resource(); Ref<TextFile> text_file = resource; + Ref<Script> script = resource; + if (text_file != nullptr) { current->apply_code(); _save_text_file(text_file, text_file->get_path()); break; } + + if (script != nullptr) { + const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + for (int j = 0; j < documentations.size(); j++) { + const DocData::ClassDoc &doc = documentations.get(j); + if (EditorHelp::get_doc_data()->has_doc(doc.name)) { + EditorHelp::get_doc_data()->remove_doc(doc.name); + } + } + } + editor->save_resource(resource); + if (script != nullptr) { + const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + for (int j = 0; j < documentations.size(); j++) { + const DocData::ClassDoc &doc = documentations.get(j); + EditorHelp::get_doc_data()->add_doc(doc); + update_doc(doc.name); + } + } + } break; case FILE_SAVE_AS: { if (trim_trailing_whitespace_on_save) { @@ -1274,6 +1296,8 @@ void ScriptEditor::_menu_option(int p_option) { RES resource = current->get_edited_resource(); Ref<TextFile> text_file = resource; + Ref<Script> script = resource; + if (text_file != nullptr) { file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); @@ -1289,9 +1313,27 @@ void ScriptEditor::_menu_option(int p_option) { break; } + if (script != nullptr) { + const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + for (int j = 0; j < documentations.size(); j++) { + const DocData::ClassDoc &doc = documentations.get(j); + if (EditorHelp::get_doc_data()->has_doc(doc.name)) { + EditorHelp::get_doc_data()->remove_doc(doc.name); + } + } + } + editor->push_item(resource.ptr()); editor->save_resource_as(resource); + if (script != nullptr) { + const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + for (int j = 0; j < documentations.size(); j++) { + const DocData::ClassDoc &doc = documentations.get(j); + EditorHelp::get_doc_data()->add_doc(doc); + update_doc(doc.name); + } + } } break; case FILE_TOOL_RELOAD: @@ -2318,11 +2360,33 @@ void ScriptEditor::save_all_scripts() { if (edited_res->get_path() != "" && edited_res->get_path().find("local://") == -1 && edited_res->get_path().find("::") == -1) { Ref<TextFile> text_file = edited_res; + Ref<Script> script = edited_res; + if (text_file != nullptr) { _save_text_file(text_file, text_file->get_path()); continue; } + + if (script != nullptr) { + const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + for (int j = 0; j < documentations.size(); j++) { + const DocData::ClassDoc &doc = documentations.get(j); + if (EditorHelp::get_doc_data()->has_doc(doc.name)) { + EditorHelp::get_doc_data()->remove_doc(doc.name); + } + } + } + editor->save_resource(edited_res); //external script, save it + + if (script != nullptr) { + const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + for (int j = 0; j < documentations.size(); j++) { + const DocData::ClassDoc &doc = documentations.get(j); + EditorHelp::get_doc_data()->add_doc(doc); + update_doc(doc.name); + } + } } } @@ -2900,6 +2964,18 @@ void ScriptEditor::_help_class_goto(const String &p_desc) { _save_layout(); } +void ScriptEditor::update_doc(const String &p_name) { + ERR_FAIL_COND(!EditorHelp::get_doc_data()->has_doc(p_name)); + + for (int i = 0; i < tab_container->get_child_count(); i++) { + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + if (eh && eh->get_class() == p_name) { + eh->update_doc(); + return; + } + } +} + void ScriptEditor::_update_selected_editor_menu() { for (int i = 0; i < tab_container->get_child_count(); i++) { bool current = tab_container->get_current_tab() == i; diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index f1453c3d20..cc02a1ccbe 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -482,6 +482,7 @@ public: void close_builtin_scripts_from_scene(const String &p_scene); void goto_help(const String &p_desc) { _help_class_goto(p_desc); } + void update_doc(const String &p_name); bool can_take_away_focus() const; diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 9db1d2ffcf..6112e69299 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -120,9 +120,11 @@ void VisualShaderGraphPlugin::set_connections(List<VisualShader::Connection> &p_ } void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id) { - if (visual_shader->get_shader_type() == p_type && links.has(p_node_id)) { + if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].output_ports.has(p_port_id)) { for (Map<int, Port>::Element *E = links[p_node_id].output_ports.front(); E; E = E->next()) { - E->value().preview_button->set_pressed(false); + if (E->value().preview_button != nullptr) { + E->value().preview_button->set_pressed(false); + } } if (links[p_node_id].preview_visible && !is_dirty() && links[p_node_id].preview_box != nullptr) { @@ -132,7 +134,7 @@ void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p links[p_node_id].preview_visible = false; } - if (p_port_id != -1) { + if (p_port_id != -1 && links[p_node_id].output_ports[p_port_id].preview_button != nullptr) { if (is_dirty()) { links[p_node_id].preview_pos = links[p_node_id].graph_node->get_child_count(); } @@ -497,16 +499,32 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (group_node->is_editable()) { HBoxContainer *hb2 = memnew(HBoxContainer); + String input_port_name = "input" + itos(group_node->get_free_input_port_id()); + String output_port_name = "output" + itos(group_node->get_free_output_port_id()); + + for (int i = 0; i < MAX(vsnode->get_input_port_count(), vsnode->get_output_port_count()); i++) { + if (i < vsnode->get_input_port_count()) { + if (input_port_name == vsnode->get_input_port_name(i)) { + input_port_name = "_" + input_port_name; + } + } + if (i < vsnode->get_output_port_count()) { + if (output_port_name == vsnode->get_output_port_name(i)) { + output_port_name = "_" + output_port_name; + } + } + } + Button *add_input_btn = memnew(Button); add_input_btn->set_text(TTR("Add Input")); - add_input_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_add_input_port), varray(p_id, group_node->get_free_input_port_id(), VisualShaderNode::PORT_TYPE_VECTOR, "input" + itos(group_node->get_free_input_port_id())), CONNECT_DEFERRED); + add_input_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_add_input_port), varray(p_id, group_node->get_free_input_port_id(), VisualShaderNode::PORT_TYPE_VECTOR, input_port_name), CONNECT_DEFERRED); hb2->add_child(add_input_btn); hb2->add_spacer(); Button *add_output_btn = memnew(Button); add_output_btn->set_text(TTR("Add Output")); - add_output_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_add_output_port), varray(p_id, group_node->get_free_output_port_id(), VisualShaderNode::PORT_TYPE_VECTOR, "output" + itos(group_node->get_free_output_port_id())), CONNECT_DEFERRED); + add_output_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_add_output_port), varray(p_id, group_node->get_free_output_port_id(), VisualShaderNode::PORT_TYPE_VECTOR, output_port_name), CONNECT_DEFERRED); hb2->add_child(add_output_btn); node->add_child(hb2); @@ -583,8 +601,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_left); - name_box->connect("text_entered", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_input_port_name), varray(name_box, p_id, i)); - name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, false)); + name_box->connect("text_entered", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_input_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); + name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, false), CONNECT_DEFERRED); Button *remove_btn = memnew(Button); remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Remove", "EditorIcons")); @@ -624,8 +642,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_right); - name_box->connect("text_entered", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_output_port_name), varray(name_box, p_id, i)); - name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, true)); + name_box->connect("text_entered", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_output_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); + name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, true), CONNECT_DEFERRED); OptionButton *type_box = memnew(OptionButton); hb->add_child(type_box); @@ -1335,29 +1353,57 @@ void VisualShaderEditor::_change_output_port_type(int p_type, int p_node, int p_ undo_redo->commit_action(); } -void VisualShaderEditor::_change_input_port_name(const String &p_text, Object *line_edit, int p_node_id, int p_port_id) { +void VisualShaderEditor::_change_input_port_name(const String &p_text, Object *p_line_edit, int p_node_id, int p_port_id) { VisualShader::Type type = get_current_shader_type(); Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node_id); ERR_FAIL_COND(!node.is_valid()); + String prev_name = node->get_input_port_name(p_port_id); + if (prev_name == p_text) { + return; + } + + LineEdit *line_edit = Object::cast_to<LineEdit>(p_line_edit); + ERR_FAIL_COND(!line_edit); + + String validated_name = visual_shader->validate_port_name(p_text, node.ptr(), p_port_id, false); + if (validated_name == String() || prev_name == validated_name) { + line_edit->set_text(node->get_input_port_name(p_port_id)); + return; + } + undo_redo->create_action(TTR("Change Input Port Name")); - undo_redo->add_do_method(node.ptr(), "set_input_port_name", p_port_id, p_text); + undo_redo->add_do_method(node.ptr(), "set_input_port_name", p_port_id, validated_name); undo_redo->add_undo_method(node.ptr(), "set_input_port_name", p_port_id, node->get_input_port_name(p_port_id)); undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, p_node_id); undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, p_node_id); undo_redo->commit_action(); } -void VisualShaderEditor::_change_output_port_name(const String &p_text, Object *line_edit, int p_node_id, int p_port_id) { +void VisualShaderEditor::_change_output_port_name(const String &p_text, Object *p_line_edit, int p_node_id, int p_port_id) { VisualShader::Type type = get_current_shader_type(); Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node_id); ERR_FAIL_COND(!node.is_valid()); + String prev_name = node->get_output_port_name(p_port_id); + if (prev_name == p_text) { + return; + } + + LineEdit *line_edit = Object::cast_to<LineEdit>(p_line_edit); + ERR_FAIL_COND(!line_edit); + + String validated_name = visual_shader->validate_port_name(p_text, node.ptr(), p_port_id, true); + if (validated_name == String() || prev_name == validated_name) { + line_edit->set_text(node->get_output_port_name(p_port_id)); + return; + } + undo_redo->create_action(TTR("Change Output Port Name")); - undo_redo->add_do_method(node.ptr(), "set_output_port_name", p_port_id, p_text); - undo_redo->add_undo_method(node.ptr(), "set_output_port_name", p_port_id, node->get_output_port_name(p_port_id)); + undo_redo->add_do_method(node.ptr(), "set_output_port_name", p_port_id, validated_name); + undo_redo->add_undo_method(node.ptr(), "set_output_port_name", p_port_id, prev_name); undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, p_node_id); undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, p_node_id); undo_redo->commit_action(); @@ -1452,6 +1498,17 @@ void VisualShaderEditor::_remove_output_port(int p_node, int p_port) { } } + int preview_port = node->get_output_port_for_preview(); + if (preview_port != -1) { + if (preview_port == p_port) { + undo_redo->add_do_method(node.ptr(), "set_output_port_for_preview", -1); + undo_redo->add_undo_method(node.ptr(), "set_output_port_for_preview", preview_port); + } else if (preview_port > p_port) { + undo_redo->add_do_method(node.ptr(), "set_output_port_for_preview", preview_port - 1); + undo_redo->add_undo_method(node.ptr(), "set_output_port_for_preview", preview_port); + } + } + undo_redo->add_do_method(node.ptr(), "remove_output_port", p_port); undo_redo->add_undo_method(node.ptr(), "add_output_port", p_port, (int)node->get_output_port_type(p_port), node->get_output_port_name(p_port)); @@ -1596,53 +1653,10 @@ void VisualShaderEditor::_uniform_line_edit_focus_out(Object *line_edit, int p_n } void VisualShaderEditor::_port_name_focus_out(Object *line_edit, int p_node_id, int p_port_id, bool p_output) { - VisualShader::Type type = get_current_shader_type(); - - Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node_id); - ERR_FAIL_COND(!node.is_valid()); - - String text = Object::cast_to<LineEdit>(line_edit)->get_text(); - - if (!p_output) { - if (node->get_input_port_name(p_port_id) == text) { - return; - } - } else { - if (node->get_output_port_name(p_port_id) == text) { - return; - } - } - - List<String> input_names; - List<String> output_names; - - for (int i = 0; i < node->get_input_port_count(); i++) { - if (!p_output && i == p_port_id) { - continue; - } - input_names.push_back(node->get_input_port_name(i)); - } - for (int i = 0; i < node->get_output_port_count(); i++) { - if (p_output && i == p_port_id) { - continue; - } - output_names.push_back(node->get_output_port_name(i)); - } - - String validated_name = visual_shader->validate_port_name(text, input_names, output_names); - if (validated_name == "") { - if (!p_output) { - Object::cast_to<LineEdit>(line_edit)->set_text(node->get_input_port_name(p_port_id)); - } else { - Object::cast_to<LineEdit>(line_edit)->set_text(node->get_output_port_name(p_port_id)); - } - return; - } - if (!p_output) { - _change_input_port_name(validated_name, line_edit, p_node_id, p_port_id); + _change_input_port_name(Object::cast_to<LineEdit>(line_edit)->get_text(), line_edit, p_node_id, p_port_id); } else { - _change_output_port_name(validated_name, line_edit, p_node_id, p_port_id); + _change_output_port_name(Object::cast_to<LineEdit>(line_edit)->get_text(), line_edit, p_node_id, p_port_id); } } @@ -3173,7 +3187,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("View", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view"), "view", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Albedo", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "albedo"), "albedo", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Attenuation", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "attenuation"), "attenuation", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Attenuation", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "attenuation"), "attenuation", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Diffuse", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "diffuse"), "diffuse", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("FragCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Light", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light"), "light", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); @@ -3196,33 +3210,38 @@ VisualShaderEditor::VisualShaderEditor() { // CANVASITEM INPUTS + add_options.push_back(AddOption("AtLightPass", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "at_light_pass"), "at_light_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("FragCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightPass", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "light_pass"), "light_pass", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("NormalTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "normal_texture"), "normal_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("PointCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "point_coord"), "point_coord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("ScreenPixelSize", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_pixel_size"), "screen_pixel_size", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("ScreenTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_texture"), "screen_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("ScreenUV", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininess", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess"), "specular_shininess", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininessAlpha", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess_alpha"), "specular_shininess_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininessTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "specular_shininess_texture"), "specular_shininess_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("Texture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "texture"), "texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("FragCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Light", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light"), "light", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("LightAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_alpha"), "light_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("LightColor", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color"), "light_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightHeight", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_height"), "light_height", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightUV", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_uv"), "light_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightVector", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_vec"), "light_vec", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightColorAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color_alpha"), "light_color_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightPosition", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_position"), "light_position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightVertex", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_vertex"), "light_vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("Normal", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "normal"), "normal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("PointCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "point_coord"), "point_coord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("ScreenUV", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("ShadowAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "shadow_alpha"), "shadow_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("ShadowColor", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "shadow_color"), "shadow_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ShadowVec", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "shadow_vec"), "shadow_vec", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininess", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess"), "specular_shininess", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininessAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess_alpha"), "specular_shininess_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("Texture", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "texture"), "texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Extra", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "extra"), "extra", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightPass", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "light_pass"), "light_pass", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("AtLightPass", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "at_light_pass"), "at_light_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Canvas", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "canvas"), "canvas", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("PointSize", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "point_size"), "point_size", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Projection", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "projection"), "projection", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Screen", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "screen"), "screen", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("Vertex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("World", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "world"), "world", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 73bebcd192..2e7e9a8898 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -354,12 +354,12 @@ class VisualShaderEditor : public VBoxContainer { void _add_input_port(int p_node, int p_port, int p_port_type, const String &p_name); void _remove_input_port(int p_node, int p_port); void _change_input_port_type(int p_type, int p_node, int p_port); - void _change_input_port_name(const String &p_text, Object *line_edit, int p_node, int p_port); + void _change_input_port_name(const String &p_text, Object *p_line_edit, int p_node, int p_port); void _add_output_port(int p_node, int p_port, int p_port_type, const String &p_name); void _remove_output_port(int p_node, int p_port); void _change_output_port_type(int p_type, int p_node, int p_port); - void _change_output_port_name(const String &p_text, Object *line_edit, int p_node, int p_port); + void _change_output_port_name(const String &p_text, Object *p_line_edit, int p_node, int p_port); void _expression_focus_out(Object *code_edit, int p_node); diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index 75420a1ef4..1ff73f25c5 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -31,6 +31,7 @@ #include "property_selector.h" #include "core/os/keyboard.h" +#include "editor/doc_tools.h" #include "editor/editor_node.h" #include "editor_scale.h" @@ -349,7 +350,7 @@ void PropertySelector::_item_selected() { class_type = base_type; } - DocData *dd = EditorHelp::get_doc_data(); + DocTools *dd = EditorHelp::get_doc_data(); String text; if (properties) { diff --git a/main/main.cpp b/main/main.cpp index 3bef04b15d..555ae4e88a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -81,8 +81,8 @@ #ifdef TOOLS_ENABLED -#include "editor/doc_data.h" #include "editor/doc_data_class_path.gen.h" +#include "editor/doc_tools.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/progress_dialog.h" @@ -1914,10 +1914,10 @@ bool Main::start() { GLOBAL_DEF("mono/project/auto_update_project", true); #endif - DocData doc; + DocTools doc; doc.generate(doc_base); - DocData docsrc; + DocTools docsrc; Map<String, String> doc_data_classes; Set<String> checked_paths; print_line("Loading docs..."); @@ -1957,7 +1957,7 @@ bool Main::start() { doc.merge_from(docsrc); for (Set<String>::Element *E = checked_paths.front(); E; E = E->next()) { print_line("Erasing old docs at: " + E->get()); - DocData::erase_classes(E->get()); + DocTools::erase_classes(E->get()); } print_line("Generating new docs..."); diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm index e8fa023ac7..6d69f4a2f4 100644 --- a/modules/arkit/arkit_interface.mm +++ b/modules/arkit/arkit_interface.mm @@ -712,8 +712,8 @@ void ARKitInterface::_add_or_update_anchor(GodotARAnchor *p_anchor) { int16_t index = planeAnchor.geometry.triangleIndices[j]; simd_float3 vrtx = planeAnchor.geometry.vertices[index]; simd_float2 textcoord = planeAnchor.geometry.textureCoordinates[index]; - surftool->add_uv(Vector2(textcoord[0], textcoord[1])); - surftool->add_color(Color(0.8, 0.8, 0.8)); + surftool->set_uv(Vector2(textcoord[0], textcoord[1])); + surftool->set_color(Color(0.8, 0.8, 0.8)); surftool->add_vertex(Vector3(vrtx[0], vrtx[1], vrtx[2])); } diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp index e5becfd559..21ba67ecd3 100644 --- a/modules/assimp/editor_scene_importer_assimp.cpp +++ b/modules/assimp/editor_scene_importer_assimp.cpp @@ -827,8 +827,7 @@ EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &stat Ref<ArrayMesh> mesh; mesh.instance(); bool has_uvs = false; - bool compress_vert_data = state.import_flags & IMPORT_USE_COMPRESSION; - uint32_t mesh_flags = compress_vert_data ? Mesh::ARRAY_COMPRESS_DEFAULT : 0; + uint32_t mesh_flags = 0; Map<String, uint32_t> morph_mesh_string_lookup; @@ -904,33 +903,33 @@ EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &stat // Get the texture coordinates if they exist if (ai_mesh->HasTextureCoords(0)) { has_uvs = true; - st->add_uv(Vector2(ai_mesh->mTextureCoords[0][j].x, 1.0f - ai_mesh->mTextureCoords[0][j].y)); + st->set_uv(Vector2(ai_mesh->mTextureCoords[0][j].x, 1.0f - ai_mesh->mTextureCoords[0][j].y)); } if (ai_mesh->HasTextureCoords(1)) { has_uvs = true; - st->add_uv2(Vector2(ai_mesh->mTextureCoords[1][j].x, 1.0f - ai_mesh->mTextureCoords[1][j].y)); + st->set_uv2(Vector2(ai_mesh->mTextureCoords[1][j].x, 1.0f - ai_mesh->mTextureCoords[1][j].y)); } // Assign vertex colors if (ai_mesh->HasVertexColors(0)) { Color color = Color(ai_mesh->mColors[0]->r, ai_mesh->mColors[0]->g, ai_mesh->mColors[0]->b, ai_mesh->mColors[0]->a); - st->add_color(color); + st->set_color(color); } // Work out normal calculations? - this needs work it doesn't work properly on huestos if (ai_mesh->mNormals != nullptr) { const aiVector3D normals = ai_mesh->mNormals[j]; const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z); - st->add_normal(godot_normal); + st->set_normal(godot_normal); if (ai_mesh->HasTangentsAndBitangents()) { const aiVector3D tangents = ai_mesh->mTangents[j]; const Vector3 godot_tangent = Vector3(tangents.x, tangents.y, tangents.z); const aiVector3D bitangent = ai_mesh->mBitangents[j]; const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z); float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f; - st->add_tangent(Plane(tangents.x, tangents.y, tangents.z, d)); + st->set_tangent(Plane(tangents.x, tangents.y, tangents.z, d)); } } @@ -948,8 +947,8 @@ EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &stat weights.write[k] = bone_info[k].weight; } - st->add_bones(bones); - st->add_weights(weights); + st->set_bones(bones); + st->set_weights(weights); } // Assign vertex diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index eb599df74c..0c64c3640f 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -744,7 +744,7 @@ void RigidBodyBullet::set_continuous_collision_detection(bool p_enable) { } btBody->setCcdSweptSphereRadius(radius * 0.2); } else { - btBody->setCcdMotionThreshold(10000.0); + btBody->setCcdMotionThreshold(0.); btBody->setCcdSweptSphereRadius(0.); } } @@ -824,7 +824,7 @@ void RigidBodyBullet::reload_shapes() { btBody->updateInertiaTensor(); reload_kinematic_shapes(); - set_continuous_collision_detection(btBody->getCcdMotionThreshold() < 9998.0); + set_continuous_collision_detection(is_continuous_collision_detection_enabled()); reload_body(); } diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index 2aaa4be325..e91d9b7bfb 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -31,6 +31,7 @@ #ifndef NATIVE_SCRIPT_H #define NATIVE_SCRIPT_H +#include "core/doc_data.h" #include "core/io/resource.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" @@ -152,6 +153,13 @@ public: virtual void set_source_code(const String &p_code) override; virtual Error reload(bool p_keep_state = false) override; +#ifdef TOOLS_ENABLED + virtual const Vector<DocData::ClassDoc> &get_documentation() const override { + static Vector<DocData::ClassDoc> docs; + return docs; + } +#endif // TOOLS_ENABLED + virtual bool has_method(const StringName &p_method) const override; virtual MethodInfo get_method_info(const StringName &p_method) const override; diff --git a/modules/gdnative/pluginscript/pluginscript_script.h b/modules/gdnative/pluginscript/pluginscript_script.h index 150de05e74..dc1ed6d576 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.h +++ b/modules/gdnative/pluginscript/pluginscript_script.h @@ -32,6 +32,8 @@ #define PLUGINSCRIPT_SCRIPT_H // Godot imports + +#include "core/doc_data.h" #include "core/object/script_language.h" // PluginScript imports #include "pluginscript_language.h" @@ -97,6 +99,13 @@ public: // TODO: load_source_code only allow utf-8 file, should handle bytecode as well ? virtual Error load_source_code(const String &p_path); +#ifdef TOOLS_ENABLED + virtual const Vector<DocData::ClassDoc> &get_documentation() const override { + static Vector<DocData::ClassDoc> docs; + return docs; + } +#endif // TOOLS_ENABLED + virtual bool has_method(const StringName &p_method) const override; virtual MethodInfo get_method_info(const StringName &p_method) const override; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 53602f7a9b..4425b59d62 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -231,7 +231,7 @@ void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { } #endif -void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { +void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const { const GDScript *current = this; while (current) { for (const Map<StringName, GDScriptFunction *>::Element *E = current->member_functions.front(); E; E = E->next()) { @@ -239,18 +239,29 @@ void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { MethodInfo mi; mi.name = E->key(); for (int i = 0; i < func->get_argument_count(); i++) { - mi.arguments.push_back(func->get_argument_type(i)); + PropertyInfo arginfo = func->get_argument_type(i); +#ifdef TOOLS_ENABLED + arginfo.name = func->get_argument_name(i); +#endif + mi.arguments.push_back(arginfo); } mi.return_val = func->get_return_type(); - p_list->push_back(mi); + r_list->push_back(mi); + } + if (!p_include_base) { + return; } current = current->_base; } } -void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const { +void GDScript::get_script_method_list(List<MethodInfo> *r_list) const { + _get_script_method_list(r_list, true); +} + +void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const { const GDScript *sptr = this; List<PropertyInfo> props; @@ -269,15 +280,22 @@ void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const { for (int i = 0; i < msort.size(); i++) { props.push_front(sptr->member_info[msort[i].name]); } + if (!p_include_base) { + break; + } sptr = sptr->_base; } for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - p_list->push_back(E->get()); + r_list->push_back(E->get()); } } +void GDScript::get_script_property_list(List<PropertyInfo> *r_list) const { + _get_script_property_list(r_list, true); +} + bool GDScript::has_method(const StringName &p_method) const { return member_functions.has(p_method); } @@ -383,6 +401,183 @@ void GDScript::_update_exports_values(Map<StringName, Variant> &values, List<Pro propnames.push_back(E->get()); } } + +void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) { + if (_owner) { + _owner->_add_doc(p_inner_class); + } else { + for (int i = 0; i < docs.size(); i++) { + if (docs[i].name == p_inner_class.name) { + docs.remove(i); + break; + } + } + docs.append(p_inner_class); + } +} + +void GDScript::_clear_doc() { + docs.clear(); + doc = DocData::ClassDoc(); +} + +void GDScript::_update_doc() { + _clear_doc(); + + doc.script_path = "\"" + get_path().get_slice("://", 1) + "\""; + if (!name.empty()) { + doc.name = name; + } else { + doc.name = doc.script_path; + } + + if (_owner) { + doc.name = _owner->doc.name + "." + doc.name; + doc.script_path = doc.script_path + "." + doc.name; + } + + doc.is_script_doc = true; + + if (base.is_valid() && base->is_valid()) { + if (base->doc.name != String()) { + doc.inherits = base->doc.name; + } else { + doc.inherits = base->get_instance_base_type(); + } + } else if (native.is_valid()) { + doc.inherits = native->get_name(); + } + + doc.brief_description = doc_brief_description; + doc.description = doc_description; + doc.tutorials = doc_tutorials; + + for (Map<String, DocData::EnumDoc>::Element *E = doc_enums.front(); E; E = E->next()) { + if (E->value().description != "") { + doc.enums[E->key()] = E->value().description; + } + } + + List<MethodInfo> methods; + _get_script_method_list(&methods, false); + for (int i = 0; i < methods.size(); i++) { + // Ignore internal methods. + if (methods[i].name[0] == '@') { + continue; + } + + DocData::MethodDoc method_doc; + const String &class_name = methods[i].name; + if (member_functions.has(class_name)) { + GDScriptFunction *fn = member_functions[class_name]; + + // Change class name if return type is script reference. + GDScriptDataType return_type = fn->get_return_type(); + if (return_type.kind == GDScriptDataType::GDSCRIPT) { + methods[i].return_val.class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(return_type.script_type)); + } + + // Change class name if argumetn is script reference. + for (int j = 0; j < fn->get_argument_count(); j++) { + GDScriptDataType arg_type = fn->get_argument_type(j); + if (arg_type.kind == GDScriptDataType::GDSCRIPT) { + methods[i].arguments[j].class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(arg_type.script_type)); + } + } + } + if (doc_functions.has(methods[i].name)) { + DocData::method_doc_from_methodinfo(method_doc, methods[i], doc_functions[methods[i].name]); + } else { + DocData::method_doc_from_methodinfo(method_doc, methods[i], String()); + } + doc.methods.push_back(method_doc); + } + + List<PropertyInfo> props; + _get_script_property_list(&props, false); + for (int i = 0; i < props.size(); i++) { + ScriptMemberInfo scr_member_info; + scr_member_info.propinfo = props[i]; + scr_member_info.propinfo.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + if (member_indices.has(props[i].name)) { + const MemberInfo &mi = member_indices[props[i].name]; + scr_member_info.setter = mi.setter; + scr_member_info.getter = mi.getter; + if (mi.data_type.kind == GDScriptDataType::GDSCRIPT) { + scr_member_info.propinfo.class_name = _get_gdscript_reference_class_name( + Object::cast_to<GDScript>(mi.data_type.script_type)); + } + } + if (member_default_values.has(props[i].name)) { + scr_member_info.has_default_value = true; + scr_member_info.default_value = member_default_values[props[i].name]; + } + if (doc_variables.has(props[i].name)) { + scr_member_info.doc_string = doc_variables[props[i].name]; + } + + DocData::PropertyDoc prop_doc; + DocData::property_doc_from_scriptmemberinfo(prop_doc, scr_member_info); + doc.properties.push_back(prop_doc); + } + + List<MethodInfo> signals; + _get_script_signal_list(&signals, false); + for (int i = 0; i < signals.size(); i++) { + DocData::MethodDoc signal_doc; + if (doc_signals.has(signals[i].name)) { + DocData::signal_doc_from_methodinfo(signal_doc, signals[i], signals[i].name); + } else { + DocData::signal_doc_from_methodinfo(signal_doc, signals[i], String()); + } + doc.signals.push_back(signal_doc); + } + + for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { + if (subclasses.has(E->key())) { + continue; + } + + // Enums. + bool is_enum = false; + if (E->value().get_type() == Variant::DICTIONARY) { + if (doc_enums.has(E->key())) { + is_enum = true; + for (int i = 0; i < doc_enums[E->key()].values.size(); i++) { + doc_enums[E->key()].values.write[i].enumeration = E->key(); + doc.constants.push_back(doc_enums[E->key()].values[i]); + } + } + } + if (!is_enum && doc_enums.has("@unnamed_enums")) { + for (int i = 0; i < doc_enums["@unnamed_enums"].values.size(); i++) { + if (E->key() == doc_enums["@unnamed_enums"].values[i].name) { + is_enum = true; + DocData::ConstantDoc constant_doc; + constant_doc.enumeration = "@unnamed_enums"; + DocData::constant_doc_from_variant(constant_doc, E->key(), E->value(), doc_enums["@unnamed_enums"].values[i].description); + doc.constants.push_back(constant_doc); + break; + } + } + } + if (!is_enum) { + DocData::ConstantDoc constant_doc; + String doc_description; + if (doc_constants.has(E->key())) { + doc_description = doc_constants[E->key()]; + } + DocData::constant_doc_from_variant(constant_doc, E->key(), E->value(), doc_description); + doc.constants.push_back(constant_doc); + } + } + + for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) { + E->get()->_update_doc(); + } + + _add_doc(doc); +} #endif bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) { @@ -628,8 +823,12 @@ Error GDScript::reload(bool p_keep_state) { if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); } - // TODO: Show all error messages. - _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + + const List<GDScriptParser::ParserError>::Element *e = parser.get_errors().front(); + while (e != nullptr) { + _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + e = e->next(); + } ERR_FAIL_V(ERR_PARSE_ERROR); } @@ -638,6 +837,10 @@ Error GDScript::reload(bool p_keep_state) { GDScriptCompiler compiler; err = compiler.compile(&parser, this, p_keep_state); +#ifdef TOOLS_ENABLED + _update_doc(); +#endif + if (err) { if (can_run) { if (EngineDebugger::is_active()) { @@ -911,7 +1114,7 @@ bool GDScript::has_script_signal(const StringName &p_signal) const { return false; } -void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { +void GDScript::_get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const { for (const Map<StringName, Vector<StringName>>::Element *E = _signals.front(); E; E = E->next()) { MethodInfo mi; mi.name = E->key(); @@ -920,20 +1123,43 @@ void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { arg.name = E->get()[i]; mi.arguments.push_back(arg); } - r_signals->push_back(mi); + r_list->push_back(mi); + } + + if (!p_include_base) { + return; } if (base.is_valid()) { - base->get_script_signal_list(r_signals); + base->get_script_signal_list(r_list); } #ifdef TOOLS_ENABLED else if (base_cache.is_valid()) { - base_cache->get_script_signal_list(r_signals); + base_cache->get_script_signal_list(r_list); } #endif } +void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { + _get_script_signal_list(r_signals, true); +} + +String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript) { + ERR_FAIL_NULL_V(p_gdscript, String()); + + String class_name; + while (p_gdscript) { + if (class_name == "") { + class_name = p_gdscript->get_script_class_name(); + } else { + class_name = p_gdscript->get_script_class_name() + "." + class_name; + } + p_gdscript = p_gdscript->_owner; + } + return class_name; +} + GDScript::GDScript() : script_list(this) { valid = false; @@ -1061,6 +1287,13 @@ GDScript::~GDScript() { _save_orphaned_subclasses(); +#ifdef TOOLS_ENABLED + // Clearing inner class doc, script doc only cleared when the script source deleted. + if (_owner) { + _clear_doc(); + } +#endif + #ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->lock); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index b69a6e39c0..3eb260f95f 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -33,6 +33,7 @@ #include "core/debugger/engine_debugger.h" #include "core/debugger/script_debugger.h" +#include "core/doc_data.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/object/script_language.h" @@ -91,9 +92,7 @@ class GDScript : public Script { #ifdef TOOLS_ENABLED Map<StringName, int> member_lines; - Map<StringName, Variant> member_default_values; - List<PropertyInfo> members_cache; Map<StringName, Variant> member_default_values_cache; Ref<GDScript> base_cache; @@ -102,6 +101,20 @@ class GDScript : public Script { bool placeholder_fallback_enabled; void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames); + DocData::ClassDoc doc; + Vector<DocData::ClassDoc> docs; + String doc_brief_description; + String doc_description; + Vector<DocData::TutorialDoc> doc_tutorials; + Map<String, String> doc_functions; + Map<String, String> doc_variables; + Map<String, String> doc_constants; + Map<String, String> doc_signals; + Map<String, DocData::EnumDoc> doc_enums; + void _clear_doc(); + void _update_doc(); + void _add_doc(const DocData::ClassDoc &p_inner_class); + #endif Map<StringName, PropertyInfo> member_info; @@ -141,6 +154,13 @@ class GDScript : public Script { void _save_orphaned_subclasses(); void _init_rpc_methods_properties(); + void _get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const; + void _get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const; + void _get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const; + + // This method will map the class name from "Reference" to "MyClass.InnerClass". + static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); + protected: bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); @@ -191,6 +211,12 @@ public: virtual void set_source_code(const String &p_code) override; virtual void update_exports() override; +#ifdef TOOLS_ENABLED + virtual const Vector<DocData::ClassDoc> &get_documentation() const override { + return docs; + } +#endif // TOOLS_ENABLED + virtual Error reload(bool p_keep_state = false) override; void set_script_path(const String &p_path) { path = p_path; } //because subclasses need a path too... @@ -444,6 +470,7 @@ public: virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; + virtual bool supports_documentation() const; virtual bool can_inherit_from_file() { return true; } virtual int find_function(const String &p_function, const String &p_code) const; virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 3f2fdc04a5..5a6240f31a 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -860,7 +860,12 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name); } is_shadowing(p_function->parameters[i]->identifier, "function parameter"); -#endif +#endif // DEBUG_ENABLED +#ifdef TOOLS_ENABLED + if (p_function->parameters[i]->default_value && p_function->parameters[i]->default_value->is_constant) { + p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value); + } +#endif // TOOLS_ENABLED } if (p_function->identifier->name == "_init") { @@ -1709,7 +1714,27 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa call_type.native_type = function_name; // "Object". } - if (all_is_constant) { + bool safe_to_fold = true; + switch (builtin_type) { + // Those are stored by reference so not suited for compile-time construction. + // Because in this case they would be the same reference in all constructed values. + case Variant::OBJECT: + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::PACKED_COLOR_ARRAY: + safe_to_fold = false; + break; + default: + break; + } + + if (all_is_constant && safe_to_fold) { // Construct here. Vector<const Variant *> args; for (int i = 0; i < p_call->arguments.size(); i++) { diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 7c20cda39c..d89b89c8b9 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -40,10 +40,6 @@ uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool function->_argument_count++; function->argument_types.push_back(p_type); if (p_is_optional) { - if (function->_default_arg_count == 0) { - append(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT); - } - function->default_arguments.push_back(opcodes.size()); function->_default_arg_count++; } @@ -96,7 +92,12 @@ void GDScriptByteCodeGenerator::pop_temporary() { current_temporaries--; } -void GDScriptByteCodeGenerator::start_parameters() {} +void GDScriptByteCodeGenerator::start_parameters() { + if (function->_default_arg_count > 0) { + append(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT); + function->default_arguments.push_back(opcodes.size()); + } +} void GDScriptByteCodeGenerator::end_parameters() { function->default_arguments.invert(); @@ -167,7 +168,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { } if (function->default_arguments.size()) { - function->_default_arg_count = function->default_arguments.size(); + function->_default_arg_count = function->default_arguments.size() - 1; function->_default_arg_ptr = &function->default_arguments[0]; } else { function->_default_arg_count = 0; @@ -633,6 +634,11 @@ void GDScriptByteCodeGenerator::write_assign_false(const Address &p_target) { append(p_target); } +void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_dst, const Address &p_src) { + write_assign(p_dst, p_src); + function->default_arguments.push_back(opcodes.size()); +} + void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) { int index = 0; diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 34d2bb6098..5cbd12a0ba 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -401,6 +401,7 @@ public: virtual void write_assign(const Address &p_target, const Address &p_source) override; virtual void write_assign_true(const Address &p_target) override; virtual void write_assign_false(const Address &p_target) override; + virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 35e326c61f..559f9b8406 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -122,6 +122,7 @@ public: virtual void write_assign(const Address &p_target, const Address &p_source) = 0; virtual void write_assign_true(const Address &p_target) = 0; virtual void write_assign_false(const Address &p_target) = 0; + virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index b41dc15324..f2d3b1fb18 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1838,7 +1838,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser return error; } GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name]; - codegen.generator->write_assign(dst_addr, src_addr); + codegen.generator->write_assign_default_parameter(dst_addr, src_addr); if (src_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } @@ -1883,6 +1883,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser codegen.generator->set_initial_line(p_func->start_line); #ifdef TOOLS_ENABLED p_script->member_lines[func_name] = p_func->start_line; + p_script->doc_functions[func_name] = p_func->doc_description; #endif } else { codegen.generator->set_initial_line(0); @@ -1896,6 +1897,21 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser p_script->implicit_initializer = gd_function; } + if (p_func) { + // if no return statement -> return type is void not unresolved Variant + if (p_func->body->has_return) { + gd_function->return_type = _gdtype_from_datatype(p_func->get_datatype()); + } else { + gd_function->return_type = GDScriptDataType(); + gd_function->return_type.has_type = true; + gd_function->return_type.kind = GDScriptDataType::BUILTIN; + gd_function->return_type.builtin_type = Variant::NIL; + } +#ifdef TOOLS_ENABLED + gd_function->default_arg_values = p_func->default_arg_values; +#endif + } + p_script->member_functions[func_name] = gd_function; memdelete(codegen.generator); @@ -1993,6 +2009,24 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } } +#ifdef TOOLS_ENABLED + p_script->doc_functions.clear(); + p_script->doc_variables.clear(); + p_script->doc_constants.clear(); + p_script->doc_enums.clear(); + p_script->doc_signals.clear(); + p_script->doc_tutorials.clear(); + + p_script->doc_brief_description = p_class->doc_brief_description; + p_script->doc_description = p_class->doc_description; + for (int i = 0; i < p_class->doc_tutorials.size(); i++) { + DocData::TutorialDoc td; + td.title = p_class->doc_tutorials[i].first; + td.link = p_class->doc_tutorials[i].second; + p_script->doc_tutorials.append(td); + } +#endif + p_script->native = Ref<GDScriptNativeClass>(); p_script->base = Ref<GDScript>(); p_script->_base = nullptr; @@ -2105,20 +2139,23 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar prop_info.hint = export_info.hint; prop_info.hint_string = export_info.hint_string; prop_info.usage = export_info.usage; -#ifdef TOOLS_ENABLED - if (variable->initializer != nullptr && variable->initializer->type == GDScriptParser::Node::LITERAL) { - p_script->member_default_values[name] = static_cast<const GDScriptParser::LiteralNode *>(variable->initializer)->value; - } -#endif } else { prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; } +#ifdef TOOLS_ENABLED + p_script->doc_variables[name] = variable->doc_description; +#endif p_script->member_info[name] = prop_info; p_script->member_indices[name] = minfo; p_script->members.insert(name); #ifdef TOOLS_ENABLED + if (variable->initializer != nullptr && variable->initializer->is_constant) { + p_script->member_default_values[name] = variable->initializer->reduced_value; + } else { + p_script->member_default_values.erase(name); + } p_script->member_lines[name] = variable->start_line; #endif } break; @@ -2129,8 +2166,10 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->constants.insert(name, constant->initializer->reduced_value); #ifdef TOOLS_ENABLED - p_script->member_lines[name] = constant->start_line; + if (constant->doc_description != String()) { + p_script->doc_constants[name] = constant->doc_description; + } #endif } break; @@ -2141,6 +2180,15 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->constants.insert(name, enum_value.value); #ifdef TOOLS_ENABLED p_script->member_lines[name] = enum_value.identifier->start_line; + if (!p_script->doc_enums.has("@unnamed_enums")) { + p_script->doc_enums["@unnamed_enums"] = DocData::EnumDoc(); + p_script->doc_enums["@unnamed_enums"].name = "@unnamed_enums"; + } + DocData::ConstantDoc const_doc; + const_doc.name = enum_value.identifier->name; + const_doc.value = Variant(enum_value.value).operator String(); // TODO-DOC: enum value currently is int. + const_doc.description = enum_value.doc_description; + p_script->doc_enums["@unnamed_enums"].values.push_back(const_doc); #endif } break; @@ -2176,6 +2224,11 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar parameters_names.write[j] = signal->parameters[j]->identifier->name; } p_script->_signals[name] = parameters_names; +#ifdef TOOLS_ENABLED + if (!signal->doc_description.empty()) { + p_script->doc_signals[name] = signal->doc_description; + } +#endif } break; case GDScriptParser::ClassNode::Member::ENUM: { @@ -2192,6 +2245,16 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->constants.insert(enum_n->identifier->name, new_enum); #ifdef TOOLS_ENABLED p_script->member_lines[enum_n->identifier->name] = enum_n->start_line; + p_script->doc_enums[enum_n->identifier->name] = DocData::EnumDoc(); + p_script->doc_enums[enum_n->identifier->name].name = enum_n->identifier->name; + p_script->doc_enums[enum_n->identifier->name].description = enum_n->doc_description; + for (int j = 0; j < enum_n->values.size(); j++) { + DocData::ConstantDoc const_doc; + const_doc.name = enum_n->values[j].identifier->name; + const_doc.value = Variant(enum_n->values[j].value).operator String(); + const_doc.description = enum_n->values[j].doc_description; + p_script->doc_enums[enum_n->identifier->name].values.push_back(const_doc); + } #endif } break; default: diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index a426046797..4f847923a4 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -193,6 +193,10 @@ bool GDScriptLanguage::supports_builtin_mode() const { return true; } +bool GDScriptLanguage::supports_documentation() const { + return true; +} + int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const { GDScriptTokenizer tokenizer; tokenizer.set_source_code(p_code); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index bb5cc1284d..7bc20672d5 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -379,6 +379,7 @@ private: #ifdef TOOLS_ENABLED Vector<StringName> arg_names; + Vector<Variant> default_arg_values; #endif List<StackDebug> stack_debug; @@ -458,6 +459,11 @@ public: ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), Variant()); return default_arguments[p_idx]; } +#ifdef TOOLS_ENABLED + const Vector<Variant> &get_default_arg_values() const { + return default_arg_values; + } +#endif // TOOLS_ENABLED Variant call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state = nullptr); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 372af204f5..48fca16ab1 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -558,6 +558,17 @@ void GDScriptParser::parse_program() { parse_class_body(); +#ifdef TOOLS_ENABLED + for (Map<int, GDScriptTokenizer::CommentData>::Element *E = tokenizer.get_comments().front(); E; E = E->next()) { + if (E->get().new_line && E->get().comment.begins_with("##")) { + class_doc_line = MIN(class_doc_line, E->key()); + } + } + if (has_comment(class_doc_line)) { + get_class_doc_comment(class_doc_line, head->doc_brief_description, head->doc_description, head->doc_tutorials, false); + } +#endif // TOOLS_ENABLED + if (!check(GDScriptTokenizer::Token::TK_EOF)) { push_error("Expected end of file."); } @@ -668,6 +679,10 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() if (member == nullptr) { return; } +#ifdef TOOLS_ENABLED + int doc_comment_line = member->start_line - 1; +#endif // TOOLS_ENABLED + // Consume annotations. while (!annotation_stack.empty()) { AnnotationNode *last_annotation = annotation_stack.back()->get(); @@ -680,7 +695,25 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() clear_unused_annotations(); return; } +#ifdef TOOLS_ENABLED + if (last_annotation->start_line == doc_comment_line) { + doc_comment_line--; + } +#endif // TOOLS_ENABLED + } + +#ifdef TOOLS_ENABLED + // Consume doc comments. + class_doc_line = MIN(class_doc_line, doc_comment_line - 1); + if (has_comment(doc_comment_line)) { + if constexpr (std::is_same_v<T, ClassNode>) { + get_class_doc_comment(doc_comment_line, member->doc_brief_description, member->doc_description, member->doc_tutorials, true); + } else { + member->doc_description = get_doc_comment(doc_comment_line); + } } +#endif // TOOLS_ENABLED + if (member->identifier != nullptr) { // Enums may be unnamed. // TODO: Consider names in outer scope too, for constants and classes (and static functions?) @@ -1050,6 +1083,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { item.parent_enum = enum_node; item.line = previous.start_line; item.leftmost_column = previous.leftmost_column; + item.rightmost_column = previous.rightmost_column; if (elements.has(item.identifier->name)) { push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier); @@ -1088,6 +1122,31 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { pop_multiline(); consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); +#ifdef TOOLS_ENABLED + // Enum values documentaion. + for (int i = 0; i < enum_node->values.size(); i++) { + if (i == enum_node->values.size() - 1) { + // If close bracket is same line as last value. + if (enum_node->values[i].line != previous.start_line && has_comment(enum_node->values[i].line)) { + if (named) { + enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true); + } else { + current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true)); + } + } + } else { + // If two values are same line. + if (enum_node->values[i].line != enum_node->values[i + 1].line && has_comment(enum_node->values[i].line)) { + if (named) { + enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true); + } else { + current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true)); + } + } + } + } +#endif // TOOLS_ENABLED + end_statement("enum"); return enum_node; @@ -2624,6 +2683,218 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { return type; } +#ifdef TOOLS_ENABLED +static bool _in_codeblock(String p_line, bool p_already_in, int *r_block_begins = nullptr) { + int start_block = p_line.rfind("[codeblock]"); + int end_block = p_line.rfind("[/codeblock]"); + + if (start_block != -1 && r_block_begins) { + *r_block_begins = start_block; + } + + if (p_already_in) { + if (end_block == -1) { + return true; + } else if (start_block == -1) { + return false; + } else { + return start_block > end_block; + } + } else { + if (start_block == -1) { + return false; + } else if (end_block == -1) { + return true; + } else { + return start_block > end_block; + } + } +} + +bool GDScriptParser::has_comment(int p_line) { + return tokenizer.get_comments().has(p_line); +} + +String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { + const Map<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + ERR_FAIL_COND_V(!comments.has(p_line), String()); + + if (p_single_line) { + if (comments[p_line].comment.begins_with("##")) { + return comments[p_line].comment.trim_prefix("##").strip_edges(); + } + return ""; + } + + String doc; + + int line = p_line; + bool in_codeblock = false; + + while (comments.has(line - 1)) { + if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { + break; + } + line--; + } + + int codeblock_begins = 0; + while (comments.has(line)) { + if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { + break; + } + String doc_line = comments[line].comment.trim_prefix("##"); + + in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins); + + if (in_codeblock) { + int i = 0; + for (; i < codeblock_begins; i++) { + if (doc_line[i] != ' ') { + break; + } + } + doc_line = doc_line.substr(i); + } else { + doc_line = doc_line.strip_edges(); + } + String line_join = (in_codeblock) ? "\n" : " "; + + doc = (doc.empty()) ? doc_line : doc + line_join + doc_line; + line++; + } + + return doc; +} + +void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class) { + const Map<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + if (!comments.has(p_line)) { + return; + } + ERR_FAIL_COND(p_brief != "" || p_desc != "" || p_tutorials.size() != 0); + + int line = p_line; + bool in_codeblock = false; + enum Mode { + BRIEF, + DESC, + TUTORIALS, + DONE, + }; + Mode mode = BRIEF; + + if (p_inner_class) { + while (comments.has(line - 1)) { + if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { + break; + } + line--; + } + } + + int codeblock_begins = 0; + while (comments.has(line)) { + if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { + break; + } + + String title, link; // For tutorials. + String doc_line = comments[line++].comment.trim_prefix("##"); + String striped_line = doc_line.strip_edges(); + + // Set the read mode. + if (striped_line.begins_with("@desc:") && p_desc == "") { + mode = DESC; + striped_line = striped_line.trim_prefix("@desc:"); + in_codeblock = _in_codeblock(doc_line, in_codeblock); + + } else if (striped_line.begins_with("@tutorial")) { + int begin_scan = String("@tutorial").length(); + if (begin_scan >= striped_line.length()) { + continue; // invalid syntax. + } + + if (striped_line[begin_scan] == ':') { // No title. + // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional. + title = ""; + link = striped_line.trim_prefix("@tutorial:").strip_edges(); + + } else { + /* Syntax: + @tutorial ( The Title Here ) : http://the.url/ + ^ open ^ close ^ colon ^ url + */ + int open_bracket_pos = begin_scan, close_bracket_pos = 0; + while (open_bracket_pos < striped_line.length() && (striped_line[open_bracket_pos] == ' ' || striped_line[open_bracket_pos] == '\t')) { + open_bracket_pos++; + } + if (open_bracket_pos == striped_line.length() || striped_line[open_bracket_pos++] != '(') { + continue; // invalid syntax. + } + close_bracket_pos = open_bracket_pos; + while (close_bracket_pos < striped_line.length() && striped_line[close_bracket_pos] != ')') { + close_bracket_pos++; + } + if (close_bracket_pos == striped_line.length()) { + continue; // invalid syntax. + } + + int colon_pos = close_bracket_pos + 1; + while (colon_pos < striped_line.length() && (striped_line[colon_pos] == ' ' || striped_line[colon_pos] == '\t')) { + colon_pos++; + } + if (colon_pos == striped_line.length() || striped_line[colon_pos++] != ':') { + continue; // invalid syntax. + } + + title = striped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges(); + link = striped_line.substr(colon_pos).strip_edges(); + } + + mode = TUTORIALS; + in_codeblock = false; + } else if (striped_line.empty()) { + continue; + } else { + // Tutorial docs are single line, we need a @tag after it. + if (mode == TUTORIALS) { + mode = DONE; + } + + in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins); + } + + if (in_codeblock) { + int i = 0; + for (; i < codeblock_begins; i++) { + if (doc_line[i] != ' ') { + break; + } + } + doc_line = doc_line.substr(i); + } else { + doc_line = striped_line; + } + String line_join = (in_codeblock) ? "\n" : " "; + + switch (mode) { + case BRIEF: + p_brief = (p_brief.length() == 0) ? doc_line : p_brief + line_join + doc_line; + break; + case DESC: + p_desc = (p_desc.length() == 0) ? doc_line : p_desc + line_join + doc_line; + break; + case TUTORIALS: + p_tutorials.append(Pair<String, String>(title, link)); + break; + case DONE: + return; + } + } +} +#endif // TOOLS_ENABLED + GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Type p_token_type) { // Function table for expression parsing. // clang-format destroys the alignment here, so turn off for the table. diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index b24acc4778..44605bc20f 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -413,9 +413,16 @@ public: int line = 0; int leftmost_column = 0; int rightmost_column = 0; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED }; + IdentifierNode *identifier = nullptr; Vector<Value> values; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED EnumNode() { type = ENUM; @@ -568,6 +575,17 @@ public: Vector<StringName> extends; // List for indexing: extends A.B.C DataType base_type; String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project. +#ifdef TOOLS_ENABLED + String doc_description; + String doc_brief_description; + Vector<Pair<String, String>> doc_tutorials; + + // EnumValue docs are parsed after itself, so we need a method to add/modify the doc property later. + void set_enum_value_doc(const StringName &p_name, const String &p_doc_description) { + ERR_FAIL_INDEX(members_indices[p_name], members.size()); + members.write[members_indices[p_name]].enum_value.doc_description = p_doc_description; + } +#endif // TOOLS_ENABLED bool resolved_interface = false; bool resolved_body = false; @@ -602,6 +620,9 @@ public: TypeNode *datatype_specifier = nullptr; bool infer_datatype = false; int usages = 0; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED ConstantNode() { type = CONSTANT; @@ -653,6 +674,10 @@ public: bool is_coroutine = false; MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; MethodInfo info; +#ifdef TOOLS_ENABLED + Vector<Variant> default_arg_values; + String doc_description; +#endif // TOOLS_ENABLED bool resolved_signature = false; bool resolved_body = false; @@ -820,6 +845,9 @@ public: IdentifierNode *identifier = nullptr; Vector<ParameterNode *> parameters; HashMap<StringName, int> parameters_indices; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED SignalNode() { type = SIGNAL; @@ -1012,6 +1040,9 @@ public: MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; int assignments = 0; int usages = 0; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED VariableNode() { type = VARIABLE; @@ -1270,6 +1301,13 @@ private: ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign); TypeNode *parse_type(bool p_allow_void = false); +#ifdef TOOLS_ENABLED + // Doc comments. + int class_doc_line = 0x7FFFFFFF; + bool has_comment(int p_line); + String get_doc_comment(int p_line, bool p_single_line = false); + void get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class); +#endif // TOOLS_ENABLED public: Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index b91777ede1..ac43105254 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -1014,9 +1014,17 @@ void GDScriptTokenizer::check_indent() { } if (_peek() == '#') { // Comment. Advance to the next line. +#ifdef TOOLS_ENABLED + String comment; + while (_peek() != '\n' && !_is_at_end()) { + comment += _advance(); + } + comments[line] = CommentData(comment, true); +#else while (_peek() != '\n' && !_is_at_end()) { _advance(); } +#endif // TOOLS_ENABLED if (_is_at_end()) { // Reached the end with an empty line, so just dedent as much as needed. pending_indents -= indent_level(); @@ -1125,18 +1133,26 @@ void GDScriptTokenizer::_skip_whitespace() { newline(!is_bol); // Don't create new line token if line is empty. check_indent(); break; - case '#': + case '#': { // Comment. +#ifdef TOOLS_ENABLED + String comment; + while (_peek() != '\n' && !_is_at_end()) { + comment += _advance(); + } + comments[line] = CommentData(comment, is_bol); +#else while (_peek() != '\n' && !_is_at_end()) { _advance(); } +#endif // TOOLS_ENABLED if (_is_at_end()) { return; } _advance(); // Consume '\n' newline(!is_bol); check_indent(); - break; + } break; default: return; } diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index d51f1f250f..f236c86f9f 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -32,6 +32,7 @@ #define GDSCRIPT_TOKENIZER_H #include "core/templates/list.h" +#include "core/templates/map.h" #include "core/templates/set.h" #include "core/templates/vector.h" #include "core/variant/variant.h" @@ -181,6 +182,21 @@ public: } }; +#ifdef TOOLS_ENABLED + struct CommentData { + String comment; + bool new_line = false; + CommentData() {} + CommentData(const String &p_comment, bool p_new_line) { + comment = p_comment; + new_line = p_new_line; + } + }; + const Map<int, CommentData> &get_comments() const { + return comments; + } +#endif // TOOLS_ENABLED + private: String source; const char32_t *_source = nullptr; @@ -207,6 +223,10 @@ private: int position = 0; int length = 0; +#ifdef TOOLS_ENABLED + Map<int, CommentData> comments; +#endif // TOOLS_ENABLED + _FORCE_INLINE_ bool _is_at_end() { return position >= length; } _FORCE_INLINE_ char32_t _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; } int indent_level() const { return indent_stack.size(); } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 7c8bfcd944..7942ee8d97 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -1653,7 +1653,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a VariantInternal::initialize(ret, Variant::OBJECT); Object **ret_opaque = VariantInternal::get_object(ret); method->ptrcall(base_obj, argptrs, ret_opaque); - VariantInternal::set_object(ret, *ret_opaque); + VariantInternal::object_assign(ret, *ret_opaque); // Set so ID is correct too. #ifdef DEBUG_ENABLED if (GDScriptLanguage::get_singleton()->profiling) { diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 6ddb0d149e..729be237ec 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/io/json.h" #include "core/os/copymem.h" +#include "editor/doc_tools.h" #include "editor/editor_log.h" #include "editor/editor_node.h" @@ -212,7 +213,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { void GDScriptLanguageProtocol::initialized(const Variant &p_params) { lsp::GodotCapabilities capabilities; - DocData *doc = EditorHelp::get_doc_data(); + DocTools *doc = EditorHelp::get_doc_data(); for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { lsp::GodotNativeClassInfo gdclass; gdclass.name = E->get().name; diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index f6643d07f9..60668e7b31 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -34,6 +34,7 @@ #include "../gdscript_parser.h" #include "core/config/project_settings.h" #include "core/object/script_language.h" +#include "editor/doc_tools.h" #include "editor/editor_file_system.h" #include "editor/editor_help.h" #include "editor/editor_node.h" @@ -189,7 +190,7 @@ Error GDScriptWorkspace::initialize() { return OK; } - DocData *doc = EditorHelp::get_doc_data(); + DocTools *doc = EditorHelp::get_doc_data(); for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { const DocData::ClassDoc &class_data = E->value(); lsp::DocumentSymbol class_symbol; diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 288fd41c87..1029c53bbf 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -31,9 +31,9 @@ #ifndef GODOT_LSP_H #define GODOT_LSP_H +#include "core/doc_data.h" #include "core/object/class_db.h" #include "core/templates/list.h" -#include "editor/doc_data.h" namespace lsp { diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 0e6ec7f520..cda217acf0 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -1030,11 +1030,15 @@ void GridMapEditor::_notification(int p_what) { for (int i = 0; i < 3; i++) { grid[i] = RS::get_singleton()->mesh_create(); grid_instance[i] = RS::get_singleton()->instance_create2(grid[i], get_tree()->get_root()->get_world_3d()->get_scenario()); + RenderingServer::get_singleton()->instance_set_layer_mask(grid_instance[i], 1 << Node3DEditorViewport::MISC_TOOL_LAYER); selection_level_instance[i] = RenderingServer::get_singleton()->instance_create2(selection_level_mesh[i], get_tree()->get_root()->get_world_3d()->get_scenario()); + RenderingServer::get_singleton()->instance_set_layer_mask(selection_level_instance[i], 1 << Node3DEditorViewport::MISC_TOOL_LAYER); } selection_instance = RenderingServer::get_singleton()->instance_create2(selection_mesh, get_tree()->get_root()->get_world_3d()->get_scenario()); + RenderingServer::get_singleton()->instance_set_layer_mask(selection_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER); paste_instance = RenderingServer::get_singleton()->instance_create2(paste_mesh, get_tree()->get_root()->get_world_3d()->get_scenario()); + RenderingServer::get_singleton()->instance_set_layer_mask(paste_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER); _update_selection_transform(); _update_paste_indicator(); diff --git a/modules/mbedtls/SCsub b/modules/mbedtls/SCsub index 5f5d25a3ee..3b1739c6ee 100755 --- a/modules/mbedtls/SCsub +++ b/modules/mbedtls/SCsub @@ -100,3 +100,7 @@ if env["builtin_mbedtls"]: # Module sources env_mbed_tls.add_source_files(env.modules_sources, "*.cpp") + +if env["tests"]: + env_mbed_tls.Append(CPPDEFINES=["TESTS_ENABLED"]) + env_mbed_tls.add_source_files(env.modules_sources, "./tests/*.cpp") diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index bec792450a..4ea38ebd60 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -44,6 +44,7 @@ #define PEM_END_CRT "-----END CERTIFICATE-----\n" #include <mbedtls/debug.h> +#include <mbedtls/md.h> #include <mbedtls/pem.h> CryptoKey *CryptoKeyMbedTLS::create() { @@ -186,6 +187,68 @@ Error X509CertificateMbedTLS::save(String p_path) { return OK; } +bool HMACContextMbedTLS::is_md_type_allowed(mbedtls_md_type_t p_md_type) { + switch (p_md_type) { + case MBEDTLS_MD_SHA1: + case MBEDTLS_MD_SHA256: + return true; + default: + return false; + } +} + +HMACContext *HMACContextMbedTLS::create() { + return memnew(HMACContextMbedTLS); +} + +Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, PackedByteArray p_key) { + ERR_FAIL_COND_V_MSG(ctx != nullptr, ERR_FILE_ALREADY_IN_USE, "HMACContext already started."); + + // HMAC keys can be any size. + ERR_FAIL_COND_V_MSG(p_key.empty(), ERR_INVALID_PARAMETER, "Key must not be empty."); + + hash_type = p_hash_type; + mbedtls_md_type_t ht = CryptoMbedTLS::md_type_from_hashtype(p_hash_type, hash_len); + + bool allowed = HMACContextMbedTLS::is_md_type_allowed(ht); + ERR_FAIL_COND_V_MSG(!allowed, ERR_INVALID_PARAMETER, "Unsupported hash type."); + + ctx = memalloc(sizeof(mbedtls_md_context_t)); + mbedtls_md_init((mbedtls_md_context_t *)ctx); + + mbedtls_md_setup((mbedtls_md_context_t *)ctx, mbedtls_md_info_from_type((mbedtls_md_type_t)ht), 1); + int ret = mbedtls_md_hmac_starts((mbedtls_md_context_t *)ctx, (const uint8_t *)p_key.ptr(), (size_t)p_key.size()); + return ret ? FAILED : OK; +} + +Error HMACContextMbedTLS::update(PackedByteArray p_data) { + ERR_FAIL_COND_V_MSG(ctx == nullptr, ERR_INVALID_DATA, "Start must be called before update."); + + ERR_FAIL_COND_V_MSG(p_data.empty(), ERR_INVALID_PARAMETER, "Src must not be empty."); + + int ret = mbedtls_md_hmac_update((mbedtls_md_context_t *)ctx, (const uint8_t *)p_data.ptr(), (size_t)p_data.size()); + return ret ? FAILED : OK; +} + +PackedByteArray HMACContextMbedTLS::finish() { + ERR_FAIL_COND_V_MSG(ctx == nullptr, PackedByteArray(), "Start must be called before finish."); + ERR_FAIL_COND_V_MSG(hash_len == 0, PackedByteArray(), "Unsupported hash type."); + + PackedByteArray out; + out.resize(hash_len); + + unsigned char *out_ptr = (unsigned char *)out.ptrw(); + int ret = mbedtls_md_hmac_finish((mbedtls_md_context_t *)ctx, out_ptr); + + mbedtls_md_free((mbedtls_md_context_t *)ctx); + memfree((mbedtls_md_context_t *)ctx); + ctx = nullptr; + hash_len = 0; + + ERR_FAIL_COND_V_MSG(ret, PackedByteArray(), "Error received while finishing HMAC"); + return out; +} + Crypto *CryptoMbedTLS::create() { return memnew(CryptoMbedTLS); } @@ -199,6 +262,7 @@ void CryptoMbedTLS::initialize_crypto() { Crypto::_load_default_certificates = load_default_certificates; X509CertificateMbedTLS::make_default(); CryptoKeyMbedTLS::make_default(); + HMACContextMbedTLS::make_default(); } void CryptoMbedTLS::finalize_crypto() { @@ -210,6 +274,7 @@ void CryptoMbedTLS::finalize_crypto() { } X509CertificateMbedTLS::finalize(); CryptoKeyMbedTLS::finalize(); + HMACContextMbedTLS::finalize(); } CryptoMbedTLS::CryptoMbedTLS() { @@ -313,7 +378,7 @@ PackedByteArray CryptoMbedTLS::generate_random_bytes(int p_bytes) { return out; } -mbedtls_md_type_t CryptoMbedTLS::_md_type_from_hashtype(HashingContext::HashType p_hash_type, int &r_size) { +mbedtls_md_type_t CryptoMbedTLS::md_type_from_hashtype(HashingContext::HashType p_hash_type, int &r_size) { switch (p_hash_type) { case HashingContext::HASH_MD5: r_size = 16; @@ -332,7 +397,7 @@ mbedtls_md_type_t CryptoMbedTLS::_md_type_from_hashtype(HashingContext::HashType Vector<uint8_t> CryptoMbedTLS::sign(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Ref<CryptoKey> p_key) { int size; - mbedtls_md_type_t type = _md_type_from_hashtype(p_hash_type, size); + mbedtls_md_type_t type = CryptoMbedTLS::md_type_from_hashtype(p_hash_type, size); ERR_FAIL_COND_V_MSG(type == MBEDTLS_MD_NONE, Vector<uint8_t>(), "Invalid hash type."); ERR_FAIL_COND_V_MSG(p_hash.size() != size, Vector<uint8_t>(), "Invalid hash provided. Size must be " + itos(size)); Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS>>(p_key); @@ -350,7 +415,7 @@ Vector<uint8_t> CryptoMbedTLS::sign(HashingContext::HashType p_hash_type, Vector bool CryptoMbedTLS::verify(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Vector<uint8_t> p_signature, Ref<CryptoKey> p_key) { int size; - mbedtls_md_type_t type = _md_type_from_hashtype(p_hash_type, size); + mbedtls_md_type_t type = CryptoMbedTLS::md_type_from_hashtype(p_hash_type, size); ERR_FAIL_COND_V_MSG(type == MBEDTLS_MD_NONE, false, "Invalid hash type."); ERR_FAIL_COND_V_MSG(p_hash.size() != size, false, "Invalid hash provided. Size must be " + itos(size)); Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS>>(p_key); diff --git a/modules/mbedtls/crypto_mbedtls.h b/modules/mbedtls/crypto_mbedtls.h index e40ca08643..990f8ae578 100644 --- a/modules/mbedtls/crypto_mbedtls.h +++ b/modules/mbedtls/crypto_mbedtls.h @@ -101,12 +101,31 @@ public: friend class SSLContextMbedTLS; }; +class HMACContextMbedTLS : public HMACContext { +private: + HashingContext::HashType hash_type; + int hash_len = 0; + void *ctx = nullptr; + +public: + static HMACContext *create(); + static void make_default() { HMACContext::_create = create; } + static void finalize() { HMACContext::_create = nullptr; } + + static bool is_md_type_allowed(mbedtls_md_type_t p_md_type); + + virtual Error start(HashingContext::HashType p_hash_type, PackedByteArray p_key); + virtual Error update(PackedByteArray p_data); + virtual PackedByteArray finish(); + + HMACContextMbedTLS() {} +}; + class CryptoMbedTLS : public Crypto { private: mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; static X509CertificateMbedTLS *default_certs; - mbedtls_md_type_t _md_type_from_hashtype(HashingContext::HashType p_hash_type, int &r_size); public: static Crypto *create(); @@ -114,6 +133,7 @@ public: static void finalize_crypto(); static X509CertificateMbedTLS *get_default_certificates(); static void load_default_certificates(String p_path); + static mbedtls_md_type_t md_type_from_hashtype(HashingContext::HashType p_hash_type, int &r_size); virtual PackedByteArray generate_random_bytes(int p_bytes); virtual Ref<CryptoKey> generate_rsa(int p_bytes); diff --git a/modules/mbedtls/register_types.cpp b/modules/mbedtls/register_types.cpp index 84a27c29bd..59abbac8ec 100644 --- a/modules/mbedtls/register_types.cpp +++ b/modules/mbedtls/register_types.cpp @@ -35,6 +35,10 @@ #include "packet_peer_mbed_dtls.h" #include "stream_peer_mbedtls.h" +#ifdef TESTS_ENABLED +#include "tests/test_crypto_mbedtls.h" +#endif + void register_mbedtls_types() { CryptoMbedTLS::initialize_crypto(); StreamPeerMbedTLS::initialize_ssl(); diff --git a/modules/mbedtls/tests/test_crypto_mbedtls.cpp b/modules/mbedtls/tests/test_crypto_mbedtls.cpp new file mode 100644 index 0000000000..c5a27aa794 --- /dev/null +++ b/modules/mbedtls/tests/test_crypto_mbedtls.cpp @@ -0,0 +1,62 @@ +/*************************************************************************/ +/* test_crypto_mbedtls.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "modules/mbedtls/tests/test_crypto_mbedtls.h" + +#include "modules/mbedtls/crypto_mbedtls.h" +#include "tests/test_macros.h" + +namespace TestCryptoMbedTLS { + +void hmac_digest_test(HashingContext::HashType ht, String expected_hex) { + CryptoMbedTLS crypto; + PackedByteArray key = String("supersecretkey").to_utf8_buffer(); + PackedByteArray msg = String("Return of the MAC!").to_utf8_buffer(); + PackedByteArray digest = crypto.hmac_digest(ht, key, msg); + String hex = String::hex_encode_buffer(digest.ptr(), digest.size()); + CHECK(hex == expected_hex); +} + +void hmac_context_digest_test(HashingContext::HashType ht, String expected_hex) { + HMACContextMbedTLS ctx; + PackedByteArray key = String("supersecretkey").to_utf8_buffer(); + PackedByteArray msg1 = String("Return of ").to_utf8_buffer(); + PackedByteArray msg2 = String("the MAC!").to_utf8_buffer(); + Error err = ctx.start(ht, key); + CHECK(err == OK); + err = ctx.update(msg1); + CHECK(err == OK); + err = ctx.update(msg2); + CHECK(err == OK); + PackedByteArray digest = ctx.finish(); + String hex = String::hex_encode_buffer(digest.ptr(), digest.size()); + CHECK(hex == expected_hex); +} +} // namespace TestCryptoMbedTLS diff --git a/modules/mbedtls/tests/test_crypto_mbedtls.h b/modules/mbedtls/tests/test_crypto_mbedtls.h new file mode 100644 index 0000000000..7b1e062239 --- /dev/null +++ b/modules/mbedtls/tests/test_crypto_mbedtls.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* test_crypto_mbedtls.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 TEST_CRYPTO_MBEDTLS_H +#define TEST_CRYPTO_MBEDTLS_H + +#include "core/crypto/hashing_context.h" + +#include "tests/test_macros.h" + +namespace TestCryptoMbedTLS { + +void hmac_digest_test(HashingContext::HashType ht, String expected_hex); + +TEST_CASE("[CryptoMbedTLS] HMAC digest") { + // SHA-256 + hmac_digest_test(HashingContext::HashType::HASH_SHA256, "fe442023f8a7d36a810e1e7cd8a8e2816457f350a008fbf638296afa12085e59"); + + // SHA-1 + hmac_digest_test(HashingContext::HashType::HASH_SHA1, "a0ac4cd68a2f4812c355983d94e8d025afe7dddf"); +} + +void hmac_context_digest_test(HashingContext::HashType ht, String expected_hex); + +TEST_CASE("[HMACContext] HMAC digest") { + // SHA-256 + hmac_context_digest_test(HashingContext::HashType::HASH_SHA256, "fe442023f8a7d36a810e1e7cd8a8e2816457f350a008fbf638296afa12085e59"); + + // SHA-1 + hmac_context_digest_test(HashingContext::HashType::HASH_SHA1, "a0ac4cd68a2f4812c355983d94e8d025afe7dddf"); +} +} // namespace TestCryptoMbedTLS + +#endif // TEST_CRYPTO_MBEDTLS_H diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py index 04f8c80243..28494bff6e 100644 --- a/modules/mono/build_scripts/make_android_mono_config.py +++ b/modules/mono/build_scripts/make_android_mono_config.py @@ -38,10 +38,10 @@ String get_godot_android_mono_config() { Vector<uint8_t> data; data.resize(config_uncompressed_size); uint8_t* w = data.ptrw(); - Compression::decompress(w.ptr(), config_uncompressed_size, config_compressed_data, + Compression::decompress(w, config_uncompressed_size, config_compressed_data, config_compressed_size, Compression::MODE_DEFLATE); String s; - if (s.parse_utf8((const char *)w.ptr(), data.size())) { + if (s.parse_utf8((const char *)w, data.size())) { ERR_FAIL_V(String()); } return s; diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index b4537f531d..63ac0956f4 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -2984,7 +2984,7 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); } -#if TOOLS_ENABLED +#ifdef TOOLS_ENABLED if (!p_script->tool) { p_script->tool = p_script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly(); } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 3e4e6c3f86..f482cc21f0 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -31,6 +31,7 @@ #ifndef CSHARP_SCRIPT_H #define CSHARP_SCRIPT_H +#include "core/doc_data.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/object/script_language.h" @@ -189,6 +190,14 @@ public: String get_source_code() const override; void set_source_code(const String &p_code) override; +#ifdef TOOLS_ENABLED + virtual const Vector<DocData::ClassDoc> &get_documentation() const override { + // TODO + static Vector<DocData::ClassDoc> docs; + return docs; + } +#endif // TOOLS_ENABLED + Error reload(bool p_keep_state = false) override; bool has_script_signal(const StringName &p_signal) const override; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index ff3122a77f..968f9f29c7 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -185,7 +185,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf return String(); } - DocData *doc = EditorHelp::get_doc_data(); + DocTools *doc = EditorHelp::get_doc_data(); String bbcode = p_bbcode; diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index eeab518954..6fefcd48a4 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -31,9 +31,10 @@ #ifndef BINDINGS_GENERATOR_H #define BINDINGS_GENERATOR_H +#include "core/doc_data.h" #include "core/object/class_db.h" #include "core/string/string_builder.h" -#include "editor/doc_data.h" +#include "editor/doc_tools.h" #include "editor/editor_help.h" #if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index 85dab4e6cf..59bdfb2fc3 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -33,6 +33,7 @@ #include "core/debugger/engine_debugger.h" #include "core/debugger/script_debugger.h" +#include "core/doc_data.h" #include "core/object/script_language.h" #include "core/os/thread.h" @@ -342,6 +343,13 @@ public: virtual void set_source_code(const String &p_code) override; virtual Error reload(bool p_keep_state = false) override; +#ifdef TOOLS_ENABLED + virtual const Vector<DocData::ClassDoc> &get_documentation() const override { + static Vector<DocData::ClassDoc> docs; + return docs; + } +#endif // TOOLS_ENABLED + virtual bool is_tool() const override; virtual bool is_valid() const override; diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp index 875270e74f..54d86d5a9c 100644 --- a/modules/visual_script/visual_script_property_selector.cpp +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -31,6 +31,7 @@ #include "visual_script_property_selector.h" #include "core/os/keyboard.h" +#include "editor/doc_tools.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "modules/visual_script/visual_script.h" @@ -437,7 +438,7 @@ void VisualScriptPropertySelector::_item_selected() { class_type = base_type; } - DocData *dd = EditorHelp::get_doc_data(); + DocTools *dd = EditorHelp::get_doc_data(); String text; String at_class = class_type; diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp index 65350518c3..224038d604 100644 --- a/modules/xatlas_unwrap/register_types.cpp +++ b/modules/xatlas_unwrap/register_types.cpp @@ -141,11 +141,11 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver xatlas::Atlas *atlas = xatlas::Create(); printf("Adding mesh..\n"); - xatlas::AddMeshError::Enum err = xatlas::AddMesh(atlas, input_mesh, 1); - ERR_FAIL_COND_V_MSG(err != xatlas::AddMeshError::Enum::Success, false, xatlas::StringForEnum(err)); + xatlas::AddMeshError err = xatlas::AddMesh(atlas, input_mesh, 1); + ERR_FAIL_COND_V_MSG(err != xatlas::AddMeshError::Success, false, xatlas::StringForEnum(err)); printf("Generate..\n"); - xatlas::Generate(atlas, chart_options, xatlas::ParameterizeOptions(), pack_options); + xatlas::Generate(atlas, chart_options, pack_options); *r_size_hint_x = atlas->width; *r_size_hint_y = atlas->height; diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index af8800d565..92e13553fc 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -948,8 +948,8 @@ void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_wi last_width = p_size.x; last_height = p_size.y; double scale = godot_js_display_pixel_ratio_get(); - emscripten_set_canvas_element_size(canvas_id, p_size.x * scale, p_size.y * scale); - emscripten_set_element_css_size(canvas_id, p_size.x, p_size.y); + emscripten_set_canvas_element_size(canvas_id, p_size.x, p_size.y); + emscripten_set_element_css_size(canvas_id, p_size.x / scale, p_size.y / scale); } Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const { diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 488753d704..44b104922e 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -200,7 +200,7 @@ const GodotFS = { } FS.mkdirTree(dir); } - FS.writeFile(path, new Uint8Array(buffer), { 'flags': 'wx+' }); + FS.writeFile(path, new Uint8Array(buffer)); }, }, }; diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 356668a375..c7559e4a40 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -43,6 +43,7 @@ #include "servers/rendering/rasterizer_rd/rasterizer_rd.h" #endif +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index e988e51e72..1dfe614b49 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -42,7 +42,7 @@ #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "platform/osx/logo.gen.h" -#include "string.h" + #include <sys/stat.h> class EditorExportPlatformOSX : public EditorExportPlatform { @@ -572,36 +572,36 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p pkg_name = "Unnamed"; } - String pkg_name_safe = OS::get_singleton()->get_safe_dir_name(pkg_name); - - Error err = OK; - String tmp_app_path_name = ""; + pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); - DirAccess *tmp_app_path = nullptr; String export_format = use_dmg() && p_path.ends_with("dmg") ? "dmg" : "zip"; // Create our application bundle. - tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".app"); + String tmp_app_dir_name = pkg_name; + String tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name); print_line("Exporting to " + tmp_app_path_name); - tmp_app_path = DirAccess::create_for_path(tmp_app_path_name); - if (!tmp_app_path) { + + Error err = OK; + + DirAccessRef tmp_app_dir = DirAccess::create_for_path(tmp_app_path_name); + if (!tmp_app_dir) { err = ERR_CANT_CREATE; } // Create our folder structure. if (err == OK) { print_line("Creating " + tmp_app_path_name + "/Contents/MacOS"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); } if (err == OK) { print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); } if (err == OK) { print_line("Creating " + tmp_app_path_name + "/Contents/Resources"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); } // Now process our template. @@ -678,14 +678,14 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p ret = unzGoToNextFile(src_pkg_zip); continue; // skip } - file = file.replace("/data.mono.osx.64.release_debug/", "/data_" + pkg_name_safe + "/"); + file = file.replace("/data.mono.osx.64.release_debug/", "/data_" + pkg_name + "/"); } if (file.find("/data.mono.osx.64.release/") != -1) { if (p_debug) { ret = unzGoToNextFile(src_pkg_zip); continue; // skip } - file = file.replace("/data.mono.osx.64.release/", "/data_" + pkg_name_safe + "/"); + file = file.replace("/data.mono.osx.64.release/", "/data_" + pkg_name + "/"); } print_line("ADDING: " + file + " size: " + itos(data.size())); @@ -694,7 +694,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p // Write it into our application bundle. file = tmp_app_path_name.plus_file(file); if (err == OK) { - err = tmp_app_path->make_dir_recursive(file.get_base_dir()); + err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); } if (err == OK) { FileAccess *f = FileAccess::open(file, FileAccess::WRITE); @@ -797,7 +797,10 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } // Clean up temporary .app dir. - OS::get_singleton()->move_to_trash(tmp_app_path_name); + tmp_app_dir->change_dir(tmp_app_path_name); + tmp_app_dir->erase_contents_recursive(); + tmp_app_dir->change_dir(".."); + tmp_app_dir->remove(tmp_app_dir_name); } return err; diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index e633b1eb4a..d811b2e852 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -480,7 +480,6 @@ void SoftBody3D::become_mesh_owner() { Dictionary surface_lods = mesh->surface_get_lods(0); uint32_t surface_format = mesh->surface_get_format(0); - surface_format &= ~(Mesh::ARRAY_COMPRESS_NORMAL); surface_format |= Mesh::ARRAY_FLAG_USE_DYNAMIC_UPDATE; Ref<ArrayMesh> soft_mesh; diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index fab0dea804..dadb1bea31 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -435,6 +435,7 @@ void BaseButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_RESOURCE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); BIND_ENUM_CONSTANT(DRAW_NORMAL); BIND_ENUM_CONSTANT(DRAW_PRESSED); diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index d85c8d2112..b66ee514dc 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -295,5 +295,6 @@ void LinkButton::_bind_methods() { LinkButton::LinkButton() { text_buf.instance(); underline_mode = UNDERLINE_MODE_ALWAYS; + set_focus_mode(FOCUS_NONE); set_default_cursor_shape(CURSOR_POINTING_HAND); } diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 62ccd55e89..3a54ac7443 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -431,6 +431,7 @@ void ScrollContainer::update_scrollbars() { v_scroll->set_max(min.height); if (hide_scroll_v) { + v_scroll->set_page(size.height); v_scroll->hide(); scroll.y = 0; } else { @@ -446,6 +447,7 @@ void ScrollContainer::update_scrollbars() { h_scroll->set_max(min.width); if (hide_scroll_h) { + h_scroll->set_page(size.width); h_scroll->hide(); scroll.x = 0; } else { diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index d38af68935..d7ca2f66ea 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -418,37 +418,39 @@ void TabContainer::_notification(int p_what) { // Draw unselected tabs in back int x = 0; int x_current = 0; + int index = 0; for (int i = 0; i < tab_widths.size(); i++) { - if (get_tab_hidden(i)) { + index = i + first_tab_cache; + if (get_tab_hidden(index)) { continue; } int tab_width = tab_widths[i]; - if (get_tab_disabled(i + first_tab_cache)) { + if (get_tab_disabled(index)) { if (rtl) { - _draw_tab(tab_disabled, font_color_disabled, i, size.width - (tabs_ofs_cache + x) - tab_width); + _draw_tab(tab_disabled, font_color_disabled, index, size.width - (tabs_ofs_cache + x) - tab_width); } else { - _draw_tab(tab_disabled, font_color_disabled, i, tabs_ofs_cache + x); + _draw_tab(tab_disabled, font_color_disabled, index, tabs_ofs_cache + x); } - } else if (i + first_tab_cache == current) { + } else if (index == current) { x_current = x; } else { if (rtl) { - _draw_tab(tab_bg, font_color_bg, i, size.width - (tabs_ofs_cache + x) - tab_width); + _draw_tab(tab_bg, font_color_bg, index, size.width - (tabs_ofs_cache + x) - tab_width); } else { - _draw_tab(tab_bg, font_color_bg, i, tabs_ofs_cache + x); + _draw_tab(tab_bg, font_color_bg, index, tabs_ofs_cache + x); } } x += tab_width; - last_tab_cache = i + first_tab_cache; + last_tab_cache = index; } // Draw the tab area. panel->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height)); - // Draw selected tab in front - if (tabs.size() > 0) { + // Draw selected tab in front. only draw selected tab when it's in visible range. + if (tabs.size() > 0 && current - first_tab_cache < tab_widths.size() && current >= first_tab_cache) { if (rtl) { _draw_tab(tab_fg, font_color_fg, current, size.width - (tabs_ofs_cache + x_current) - tab_widths[current]); } else { @@ -535,7 +537,7 @@ void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, in p_tab_style->draw(canvas, tab_rect); // Draw the tab contents. - Control *control = Object::cast_to<Control>(tabs[p_index + first_tab_cache]); + Control *control = Object::cast_to<Control>(tabs[p_index]); String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(tr(control->get_name())); int x_content = tab_rect.position.x + p_tab_style->get_margin(MARGIN_LEFT); @@ -555,8 +557,8 @@ void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, in } // Draw the tab text. - Point2i text_pos(x_content, y_center - text_buf[p_index + first_tab_cache]->get_size().y / 2); - text_buf[p_index + first_tab_cache]->draw(canvas, text_pos, p_font_color); + Point2i text_pos(x_content, y_center - text_buf[p_index]->get_size().y / 2); + text_buf[p_index]->draw(canvas, text_pos, p_font_color); } void TabContainer::_on_theme_changed() { diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 97c0c7a81d..64579e1e37 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -157,7 +157,7 @@ void Mesh::generate_debug_mesh_indices(Vector<Vector3> &r_points) { bool Mesh::surface_is_softbody_friendly(int p_idx) const { const uint32_t surface_format = surface_get_format(p_idx); - return (surface_format & Mesh::ARRAY_FLAG_USE_DYNAMIC_UPDATE && (!(surface_format & Mesh::ARRAY_COMPRESS_NORMAL))); + return (surface_format & Mesh::ARRAY_FLAG_USE_DYNAMIC_UPDATE); } Vector<Face3> Mesh::get_faces() const { @@ -483,37 +483,59 @@ void Mesh::_bind_methods() { BIND_ENUM_CONSTANT(BLEND_SHAPE_MODE_NORMALIZED); BIND_ENUM_CONSTANT(BLEND_SHAPE_MODE_RELATIVE); + BIND_ENUM_CONSTANT(ARRAY_VERTEX); + BIND_ENUM_CONSTANT(ARRAY_NORMAL); + BIND_ENUM_CONSTANT(ARRAY_TANGENT); + BIND_ENUM_CONSTANT(ARRAY_COLOR); + BIND_ENUM_CONSTANT(ARRAY_TEX_UV); + BIND_ENUM_CONSTANT(ARRAY_TEX_UV2); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM0); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM1); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM2); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM3); + BIND_ENUM_CONSTANT(ARRAY_BONES); + BIND_ENUM_CONSTANT(ARRAY_WEIGHTS); + BIND_ENUM_CONSTANT(ARRAY_INDEX); + BIND_ENUM_CONSTANT(ARRAY_MAX); + + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RGBA8_UNORM); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RGBA8_SNORM); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RG_HALF); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RGBA_HALF); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_R_FLOAT); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RG_FLOAT); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RGB_FLOAT); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RGBA_FLOAT); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_MAX); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_VERTEX); BIND_ENUM_CONSTANT(ARRAY_FORMAT_NORMAL); BIND_ENUM_CONSTANT(ARRAY_FORMAT_TANGENT); BIND_ENUM_CONSTANT(ARRAY_FORMAT_COLOR); BIND_ENUM_CONSTANT(ARRAY_FORMAT_TEX_UV); BIND_ENUM_CONSTANT(ARRAY_FORMAT_TEX_UV2); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM0); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM1); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM2); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM3); BIND_ENUM_CONSTANT(ARRAY_FORMAT_BONES); BIND_ENUM_CONSTANT(ARRAY_FORMAT_WEIGHTS); BIND_ENUM_CONSTANT(ARRAY_FORMAT_INDEX); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_NORMAL); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_TANGENT); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_COLOR); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_TEX_UV); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_TEX_UV2); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_INDEX); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_BLEND_SHAPE_MASK); - BIND_ENUM_CONSTANT(ARRAY_FLAG_USE_2D_VERTICES); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM_BASE); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM0_SHIFT); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM1_SHIFT); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM2_SHIFT); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM3_SHIFT); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_DEFAULT); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM_MASK); + BIND_ENUM_CONSTANT(ARRAY_COMPRESS_FLAGS_BASE); - BIND_ENUM_CONSTANT(ARRAY_VERTEX); - BIND_ENUM_CONSTANT(ARRAY_NORMAL); - BIND_ENUM_CONSTANT(ARRAY_TANGENT); - BIND_ENUM_CONSTANT(ARRAY_COLOR); - BIND_ENUM_CONSTANT(ARRAY_TEX_UV); - BIND_ENUM_CONSTANT(ARRAY_TEX_UV2); - BIND_ENUM_CONSTANT(ARRAY_BONES); - BIND_ENUM_CONSTANT(ARRAY_WEIGHTS); - BIND_ENUM_CONSTANT(ARRAY_INDEX); - BIND_ENUM_CONSTANT(ARRAY_MAX); + BIND_ENUM_CONSTANT(ARRAY_FLAG_USE_2D_VERTICES); + BIND_ENUM_CONSTANT(ARRAY_FLAG_USE_DYNAMIC_UPDATE); + BIND_ENUM_CONSTANT(ARRAY_FLAG_USE_8_BONE_WEIGHTS); } void Mesh::clear_cache() const { @@ -559,12 +581,50 @@ Vector<Ref<Shape3D>> Mesh::convex_decompose() const { Mesh::Mesh() { } - +#if 0 static Vector<uint8_t> _fix_array_compatibility(const Vector<uint8_t> &p_src, uint32_t p_format, uint32_t p_elements) { - bool vertex_16bit = p_format & ((1 << (Mesh::ARRAY_VERTEX + Mesh::ARRAY_COMPRESS_BASE))); - bool has_bones = (p_format & Mesh::ARRAY_FORMAT_BONES); - bool bone_8 = has_bones && !(p_format & (Mesh::ARRAY_COMPRESS_INDEX << 2)); - bool weight_32 = has_bones && !(p_format & (Mesh::ARRAY_COMPRESS_TEX_UV2 << 2)); + enum ArrayType { + OLD_ARRAY_VERTEX = 0, + OLD_ARRAY_NORMAL = 1, + OLD_ARRAY_TANGENT = 2, + OLD_ARRAY_COLOR = 3, + OLD_ARRAY_TEX_UV = 4, + OLD_ARRAY_TEX_UV2 = 5, + OLD_ARRAY_BONES = 6, + OLD_ARRAY_WEIGHTS = 7, + OLD_ARRAY_INDEX = 8, + OLD_ARRAY_MAX = 9 + }; + + enum ArrayFormat { + /* OLD_ARRAY FORMAT FLAGS */ + OLD_ARRAY_FORMAT_VERTEX = 1 << OLD_ARRAY_VERTEX, // mandatory + OLD_ARRAY_FORMAT_NORMAL = 1 << OLD_ARRAY_NORMAL, + OLD_ARRAY_FORMAT_TANGENT = 1 << OLD_ARRAY_TANGENT, + OLD_ARRAY_FORMAT_COLOR = 1 << OLD_ARRAY_COLOR, + OLD_ARRAY_FORMAT_TEX_UV = 1 << OLD_ARRAY_TEX_UV, + OLD_ARRAY_FORMAT_TEX_UV2 = 1 << OLD_ARRAY_TEX_UV2, + OLD_ARRAY_FORMAT_BONES = 1 << OLD_ARRAY_BONES, + OLD_ARRAY_FORMAT_WEIGHTS = 1 << OLD_ARRAY_WEIGHTS, + OLD_ARRAY_FORMAT_INDEX = 1 << OLD_ARRAY_INDEX, + + OLD_ARRAY_COMPRESS_BASE = (OLD_ARRAY_INDEX + 1), + OLD_ARRAY_COMPRESS_NORMAL = 1 << (OLD_ARRAY_NORMAL + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_TANGENT = 1 << (OLD_ARRAY_TANGENT + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_COLOR = 1 << (OLD_ARRAY_COLOR + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_TEX_UV = 1 << (OLD_ARRAY_TEX_UV + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_TEX_UV2 = 1 << (OLD_ARRAY_TEX_UV2 + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_INDEX = 1 << (OLD_ARRAY_INDEX + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_DEFAULT = OLD_ARRAY_COMPRESS_NORMAL | OLD_ARRAY_COMPRESS_TANGENT | OLD_ARRAY_COMPRESS_COLOR | OLD_ARRAY_COMPRESS_TEX_UV | OLD_ARRAY_COMPRESS_TEX_UV2, + + OLD_ARRAY_FLAG_USE_2D_VERTICES = OLD_ARRAY_COMPRESS_INDEX << 1, + OLD_ARRAY_FLAG_USE_DYNAMIC_UPDATE = OLD_ARRAY_COMPRESS_INDEX << 3, + }; + + bool vertex_16bit = p_format & ((1 << (OLD_ARRAY_VERTEX + OLD_ARRAY_COMPRESS_BASE))); + bool has_bones = (p_format & OLD_ARRAY_FORMAT_BONES); + bool bone_8 = has_bones && !(p_format & (OLD_ARRAY_COMPRESS_INDEX << 2)); + bool weight_32 = has_bones && !(p_format & (OLD_ARRAY_COMPRESS_TEX_UV2 << 2)); print_line("convert vertex16: " + itos(vertex_16bit) + " convert bone 8 " + itos(bone_8) + " convert weight 32 " + itos(weight_32)); @@ -572,7 +632,7 @@ static Vector<uint8_t> _fix_array_compatibility(const Vector<uint8_t> &p_src, ui return p_src; } - bool vertex_2d = (p_format & (Mesh::ARRAY_COMPRESS_INDEX << 1)); + bool vertex_2d = (p_format & (OLD_ARRAY_COMPRESS_INDEX << 1)); uint32_t src_stride = p_src.size() / p_elements; uint32_t dst_stride = src_stride + (vertex_16bit ? 4 : 0) + (bone_8 ? 4 : 0) - (weight_32 ? 8 : 0); @@ -671,7 +731,7 @@ static Vector<uint8_t> _fix_array_compatibility(const Vector<uint8_t> &p_src, ui return ret; } - +#endif bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { String sname = p_name; @@ -727,6 +787,7 @@ bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { add_surface_from_arrays(PrimitiveType(int(d["primitive"])), d["arrays"], d["morph_arrays"]); } else if (d.has("array_data")) { +#if 0 //print_line("array data (old style"); //older format (3.x) Vector<uint8_t> array_data = d["array_data"]; @@ -793,6 +854,7 @@ bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { } add_surface(format, PrimitiveType(primitive), array_data, vertex_count, array_index_data, index_count, aabb, blend_shapes, bone_aabb); +#endif } else { ERR_FAIL_V(false); } @@ -824,6 +886,12 @@ Array ArrayMesh::_get_surfaces() const { data["primitive"] = surface.primitive; data["vertex_data"] = surface.vertex_data; data["vertex_count"] = surface.vertex_count; + if (surface.skin_data.size()) { + data["skin_data"] = surface.skin_data; + } + if (surface.attribute_data.size()) { + data["attribute_data"] = surface.attribute_data; + } data["aabb"] = surface.aabb; if (surface.index_count) { data["index_data"] = surface.index_data; @@ -848,9 +916,9 @@ Array ArrayMesh::_get_surfaces() const { data["bone_aabbs"] = bone_aabbs; } - Array blend_shapes; - for (int j = 0; j < surface.blend_shapes.size(); j++) { - blend_shapes.push_back(surface.blend_shapes[j]); + if (surface.blend_shape_data.size()) { + data["blend_shapes"] = surface.blend_shape_data; + data["blend_shapes_count"] = surface.blend_shape_count; } if (surfaces[i].material.is_valid()) { @@ -896,6 +964,12 @@ void ArrayMesh::_set_surfaces(const Array &p_surfaces) { surface.primitive = RS::PrimitiveType(int(d["primitive"])); surface.vertex_data = d["vertex_data"]; surface.vertex_count = d["vertex_count"]; + if (d.has("attribute_data")) { + surface.attribute_data = d["attribute_data"]; + } + if (d.has("skin_data")) { + surface.skin_data = d["skin_data"]; + } surface.aabb = d["aabb"]; if (d.has("index_data")) { @@ -922,11 +996,9 @@ void ArrayMesh::_set_surfaces(const Array &p_surfaces) { } } - if (d.has("blend_shapes")) { - Array blend_shapes; - for (int j = 0; j < blend_shapes.size(); j++) { - surface.blend_shapes.push_back(blend_shapes[j]); - } + if (d.has("blend_shapes") && d.has("blend_shape_count")) { + surface.blend_shape_data = d["blend_shapes"]; + surface.blend_shape_count = d["blend_shape_count"]; } Ref<Material> material; @@ -982,7 +1054,7 @@ void ArrayMesh::_set_surfaces(const Array &p_surfaces) { s.aabb = surface_data[i].aabb; if (i == 0) { aabb = s.aabb; - blend_shapes.resize(surface_data[i].blend_shapes.size()); + blend_shapes.resize(surface_data[i].blend_shape_count); } else { aabb.merge_with(s.aabb); } @@ -1070,7 +1142,7 @@ void ArrayMesh::_recompute_aabb() { #ifndef _MSC_VER #warning need to add binding to add_surface using future MeshSurfaceData object #endif -void ArrayMesh::add_surface(uint32_t p_format, PrimitiveType p_primitive, const Vector<uint8_t> &p_array, int p_vertex_count, const Vector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<Vector<uint8_t>> &p_blend_shapes, const Vector<AABB> &p_bone_aabb, const Vector<RS::SurfaceData::LOD> &p_lods) { +void ArrayMesh::add_surface(uint32_t p_format, PrimitiveType p_primitive, const Vector<uint8_t> &p_array, const Vector<uint8_t> &p_attribute_array, const Vector<uint8_t> &p_skin_array, int p_vertex_count, const Vector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<uint8_t> &p_blend_shape_data, uint32_t p_blend_shape_count, const Vector<AABB> &p_bone_aabbs, const Vector<RS::SurfaceData::LOD> &p_lods) { _create_if_empty(); Surface s; @@ -1090,10 +1162,13 @@ void ArrayMesh::add_surface(uint32_t p_format, PrimitiveType p_primitive, const sd.aabb = p_aabb; sd.vertex_count = p_vertex_count; sd.vertex_data = p_array; + sd.attribute_data = p_attribute_array; + sd.skin_data = p_skin_array; sd.index_count = p_index_count; sd.index_data = p_index_array; - sd.blend_shapes = p_blend_shapes; - sd.bone_aabbs = p_bone_aabb; + sd.blend_shape_data = p_blend_shape_data; + sd.blend_shape_count = p_blend_shape_count; + sd.bone_aabbs = p_bone_aabbs; sd.lods = p_lods; RenderingServer::get_singleton()->mesh_add_surface(mesh, sd); @@ -1119,7 +1194,7 @@ void ArrayMesh::add_surface_from_arrays(PrimitiveType p_primitive, const Array & print_line("index count: " + itos(surface.index_count)); print_line("primitive: " + itos(surface.primitive)); */ - add_surface(surface.format, PrimitiveType(surface.primitive), surface.vertex_data, surface.vertex_count, surface.index_data, surface.index_count, surface.aabb, surface.blend_shapes, surface.bone_aabbs, surface.lods); + add_surface(surface.format, PrimitiveType(surface.primitive), surface.vertex_data, surface.attribute_data, surface.skin_data, surface.vertex_count, surface.index_data, surface.index_count, surface.aabb, surface.blend_shape_data, surface.blend_shape_count, surface.bone_aabbs, surface.lods); } Array ArrayMesh::surface_get_arrays(int p_surface) const { @@ -1452,29 +1527,29 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second]; if (lightmap_surfaces[surface].format & ARRAY_FORMAT_COLOR) { - surfaces_tools.write[surface]->add_color(v.color); + surfaces_tools.write[surface]->set_color(v.color); } if (lightmap_surfaces[surface].format & ARRAY_FORMAT_TEX_UV) { - surfaces_tools.write[surface]->add_uv(v.uv); + surfaces_tools.write[surface]->set_uv(v.uv); } if (lightmap_surfaces[surface].format & ARRAY_FORMAT_NORMAL) { - surfaces_tools.write[surface]->add_normal(v.normal); + surfaces_tools.write[surface]->set_normal(v.normal); } if (lightmap_surfaces[surface].format & ARRAY_FORMAT_TANGENT) { Plane t; t.normal = v.tangent; t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1; - surfaces_tools.write[surface]->add_tangent(t); + surfaces_tools.write[surface]->set_tangent(t); } if (lightmap_surfaces[surface].format & ARRAY_FORMAT_BONES) { - surfaces_tools.write[surface]->add_bones(v.bones); + surfaces_tools.write[surface]->set_bones(v.bones); } if (lightmap_surfaces[surface].format & ARRAY_FORMAT_WEIGHTS) { - surfaces_tools.write[surface]->add_weights(v.weights); + surfaces_tools.write[surface]->set_weights(v.weights); } Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]); - surfaces_tools.write[surface]->add_uv2(uv2); + surfaces_tools.write[surface]->set_uv2(uv2); surfaces_tools.write[surface]->add_vertex(v.vertex); } @@ -1507,7 +1582,7 @@ void ArrayMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_blend_shape_mode", "mode"), &ArrayMesh::set_blend_shape_mode); ClassDB::bind_method(D_METHOD("get_blend_shape_mode"), &ArrayMesh::get_blend_shape_mode); - ClassDB::bind_method(D_METHOD("add_surface_from_arrays", "primitive", "arrays", "blend_shapes", "lods", "compress_flags"), &ArrayMesh::add_surface_from_arrays, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(ARRAY_COMPRESS_DEFAULT)); + ClassDB::bind_method(D_METHOD("add_surface_from_arrays", "primitive", "arrays", "blend_shapes", "lods", "compress_flags"), &ArrayMesh::add_surface_from_arrays, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(0)); ClassDB::bind_method(D_METHOD("clear_surfaces"), &ArrayMesh::clear_surfaces); ClassDB::bind_method(D_METHOD("surface_update_region", "surf_idx", "offset", "data"), &ArrayMesh::surface_update_region); ClassDB::bind_method(D_METHOD("surface_get_array_len", "surf_idx"), &ArrayMesh::surface_get_array_len); @@ -1546,20 +1621,53 @@ void ArrayMesh::_bind_methods() { BIND_ENUM_CONSTANT(ARRAY_COLOR); BIND_ENUM_CONSTANT(ARRAY_TEX_UV); BIND_ENUM_CONSTANT(ARRAY_TEX_UV2); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM0); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM1); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM2); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM3); BIND_ENUM_CONSTANT(ARRAY_BONES); BIND_ENUM_CONSTANT(ARRAY_WEIGHTS); BIND_ENUM_CONSTANT(ARRAY_INDEX); BIND_ENUM_CONSTANT(ARRAY_MAX); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RGBA8_UNORM); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RGBA8_SNORM); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RG_HALF); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RGBA_HALF); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_R_FLOAT); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RG_FLOAT); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RGB_FLOAT); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_RGBA_FLOAT); + BIND_ENUM_CONSTANT(ARRAY_CUSTOM_MAX); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_VERTEX); BIND_ENUM_CONSTANT(ARRAY_FORMAT_NORMAL); BIND_ENUM_CONSTANT(ARRAY_FORMAT_TANGENT); BIND_ENUM_CONSTANT(ARRAY_FORMAT_COLOR); BIND_ENUM_CONSTANT(ARRAY_FORMAT_TEX_UV); BIND_ENUM_CONSTANT(ARRAY_FORMAT_TEX_UV2); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM0); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM1); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM2); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM3); BIND_ENUM_CONSTANT(ARRAY_FORMAT_BONES); BIND_ENUM_CONSTANT(ARRAY_FORMAT_WEIGHTS); BIND_ENUM_CONSTANT(ARRAY_FORMAT_INDEX); + + BIND_ENUM_CONSTANT(ARRAY_FORMAT_BLEND_SHAPE_MASK); + + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM_BASE); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM0_SHIFT); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM1_SHIFT); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM2_SHIFT); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM3_SHIFT); + + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM_MASK); + BIND_ENUM_CONSTANT(ARRAY_COMPRESS_FLAGS_BASE); + + BIND_ENUM_CONSTANT(ARRAY_FLAG_USE_2D_VERTICES); + BIND_ENUM_CONSTANT(ARRAY_FLAG_USE_DYNAMIC_UPDATE); + BIND_ENUM_CONSTANT(ARRAY_FLAG_USE_8_BONE_WEIGHTS); } void ArrayMesh::reload_from_file() { diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 642ae7e1b0..659574c2c4 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -61,6 +61,10 @@ public: ARRAY_COLOR = RenderingServer::ARRAY_COLOR, ARRAY_TEX_UV = RenderingServer::ARRAY_TEX_UV, ARRAY_TEX_UV2 = RenderingServer::ARRAY_TEX_UV2, + ARRAY_CUSTOM0 = RenderingServer::ARRAY_CUSTOM0, + ARRAY_CUSTOM1 = RenderingServer::ARRAY_CUSTOM1, + ARRAY_CUSTOM2 = RenderingServer::ARRAY_CUSTOM2, + ARRAY_CUSTOM3 = RenderingServer::ARRAY_CUSTOM3, ARRAY_BONES = RenderingServer::ARRAY_BONES, ARRAY_WEIGHTS = RenderingServer::ARRAY_WEIGHTS, ARRAY_INDEX = RenderingServer::ARRAY_INDEX, @@ -68,30 +72,47 @@ public: }; + enum ArrayCustomFormat { + ARRAY_CUSTOM_RGBA8_UNORM, + ARRAY_CUSTOM_RGBA8_SNORM, + ARRAY_CUSTOM_RG_HALF, + ARRAY_CUSTOM_RGBA_HALF, + ARRAY_CUSTOM_R_FLOAT, + ARRAY_CUSTOM_RG_FLOAT, + ARRAY_CUSTOM_RGB_FLOAT, + ARRAY_CUSTOM_RGBA_FLOAT, + ARRAY_CUSTOM_MAX + }; + enum ArrayFormat { - /* ARRAY FORMAT FLAGS */ - ARRAY_FORMAT_VERTEX = 1 << ARRAY_VERTEX, // mandatory - ARRAY_FORMAT_NORMAL = 1 << ARRAY_NORMAL, - ARRAY_FORMAT_TANGENT = 1 << ARRAY_TANGENT, - ARRAY_FORMAT_COLOR = 1 << ARRAY_COLOR, - ARRAY_FORMAT_TEX_UV = 1 << ARRAY_TEX_UV, - ARRAY_FORMAT_TEX_UV2 = 1 << ARRAY_TEX_UV2, - ARRAY_FORMAT_BONES = 1 << ARRAY_BONES, - ARRAY_FORMAT_WEIGHTS = 1 << ARRAY_WEIGHTS, - ARRAY_FORMAT_INDEX = 1 << ARRAY_INDEX, - - ARRAY_COMPRESS_BASE = (ARRAY_INDEX + 1), - ARRAY_COMPRESS_NORMAL = 1 << (ARRAY_NORMAL + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_TANGENT = 1 << (ARRAY_TANGENT + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_COLOR = 1 << (ARRAY_COLOR + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_TEX_UV = 1 << (ARRAY_TEX_UV + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_TEX_UV2 = 1 << (ARRAY_TEX_UV2 + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_INDEX = 1 << (ARRAY_INDEX + ARRAY_COMPRESS_BASE), - - ARRAY_FLAG_USE_2D_VERTICES = ARRAY_COMPRESS_INDEX << 1, - ARRAY_FLAG_USE_DYNAMIC_UPDATE = ARRAY_COMPRESS_INDEX << 3, - - ARRAY_COMPRESS_DEFAULT = ARRAY_COMPRESS_NORMAL | ARRAY_COMPRESS_TANGENT | ARRAY_COMPRESS_COLOR | ARRAY_COMPRESS_TEX_UV | ARRAY_COMPRESS_TEX_UV2 + ARRAY_FORMAT_VERTEX = RS::ARRAY_FORMAT_VERTEX, + ARRAY_FORMAT_NORMAL = RS::ARRAY_FORMAT_NORMAL, + ARRAY_FORMAT_TANGENT = RS::ARRAY_FORMAT_TANGENT, + ARRAY_FORMAT_COLOR = RS::ARRAY_FORMAT_COLOR, + ARRAY_FORMAT_TEX_UV = RS::ARRAY_FORMAT_TEX_UV, + ARRAY_FORMAT_TEX_UV2 = RS::ARRAY_FORMAT_TEX_UV2, + ARRAY_FORMAT_CUSTOM0 = RS::ARRAY_FORMAT_CUSTOM0, + ARRAY_FORMAT_CUSTOM1 = RS::ARRAY_FORMAT_CUSTOM1, + ARRAY_FORMAT_CUSTOM2 = RS::ARRAY_FORMAT_CUSTOM2, + ARRAY_FORMAT_CUSTOM3 = RS::ARRAY_FORMAT_CUSTOM3, + ARRAY_FORMAT_BONES = RS::ARRAY_FORMAT_BONES, + ARRAY_FORMAT_WEIGHTS = RS::ARRAY_FORMAT_WEIGHTS, + ARRAY_FORMAT_INDEX = RS::ARRAY_FORMAT_INDEX, + + ARRAY_FORMAT_BLEND_SHAPE_MASK = RS::ARRAY_FORMAT_BLEND_SHAPE_MASK, + + ARRAY_FORMAT_CUSTOM_BASE = RS::ARRAY_FORMAT_CUSTOM_BASE, + ARRAY_FORMAT_CUSTOM0_SHIFT = RS::ARRAY_FORMAT_CUSTOM0_SHIFT, + ARRAY_FORMAT_CUSTOM1_SHIFT = RS::ARRAY_FORMAT_CUSTOM1_SHIFT, + ARRAY_FORMAT_CUSTOM2_SHIFT = RS::ARRAY_FORMAT_CUSTOM2_SHIFT, + ARRAY_FORMAT_CUSTOM3_SHIFT = RS::ARRAY_FORMAT_CUSTOM3_SHIFT, + + ARRAY_FORMAT_CUSTOM_MASK = RS::ARRAY_FORMAT_CUSTOM_MASK, + ARRAY_COMPRESS_FLAGS_BASE = RS::ARRAY_COMPRESS_FLAGS_BASE, + + ARRAY_FLAG_USE_2D_VERTICES = RS::ARRAY_FLAG_USE_2D_VERTICES, + ARRAY_FLAG_USE_DYNAMIC_UPDATE = RS::ARRAY_FLAG_USE_DYNAMIC_UPDATE, + ARRAY_FLAG_USE_8_BONE_WEIGHTS = RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS, }; @@ -187,9 +208,9 @@ protected: static void _bind_methods(); public: - void add_surface_from_arrays(PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), uint32_t p_flags = ARRAY_COMPRESS_DEFAULT); + void add_surface_from_arrays(PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), uint32_t p_flags = 0); - void add_surface(uint32_t p_format, PrimitiveType p_primitive, const Vector<uint8_t> &p_array, int p_vertex_count, const Vector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<Vector<uint8_t>> &p_blend_shapes = Vector<Vector<uint8_t>>(), const Vector<AABB> &p_bone_aabbs = Vector<AABB>(), const Vector<RS::SurfaceData::LOD> &p_lods = Vector<RS::SurfaceData::LOD>()); + void add_surface(uint32_t p_format, PrimitiveType p_primitive, const Vector<uint8_t> &p_array, const Vector<uint8_t> &p_attribute_array, const Vector<uint8_t> &p_skin_array, int p_vertex_count, const Vector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<uint8_t> &p_blend_shape_data = Vector<uint8_t>(), uint32_t p_blend_shape_count = 0, const Vector<AABB> &p_bone_aabbs = Vector<AABB>(), const Vector<RS::SurfaceData::LOD> &p_lods = Vector<RS::SurfaceData::LOD>()); Array surface_get_arrays(int p_surface) const override; Array surface_get_blend_shape_arrays(int p_surface) const override; @@ -244,6 +265,7 @@ public: VARIANT_ENUM_CAST(Mesh::ArrayType); VARIANT_ENUM_CAST(Mesh::ArrayFormat); +VARIANT_ENUM_CAST(Mesh::ArrayCustomFormat); VARIANT_ENUM_CAST(Mesh::PrimitiveType); VARIANT_ENUM_CAST(Mesh::BlendShapeMode); diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 8d9c5f07b2..64322f333c 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -148,7 +148,7 @@ Array PrimitiveMesh::surface_get_blend_shape_arrays(int p_surface) const { uint32_t PrimitiveMesh::surface_get_format(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, 1, 0); - return RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL | RS::ARRAY_FORMAT_TANGENT | RS::ARRAY_FORMAT_TEX_UV | RS::ARRAY_FORMAT_INDEX | RS::ARRAY_COMPRESS_DEFAULT; + return RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL | RS::ARRAY_FORMAT_TANGENT | RS::ARRAY_FORMAT_TEX_UV | RS::ARRAY_FORMAT_INDEX; } Mesh::PrimitiveType PrimitiveMesh::surface_get_primitive_type(int p_idx) const { diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index ff14a5a292..3166067573 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -74,6 +74,12 @@ bool SurfaceTool::Vertex::operator==(const Vertex &p_vertex) const { } } + for (int i = 0; i < RS::ARRAY_CUSTOM_MAX; i++) { + if (custom[i] != p_vertex.custom[i]) { + return false; + } + } + return true; } @@ -87,6 +93,7 @@ uint32_t SurfaceTool::VertexHasher::hash(const Vertex &p_vtx) { 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); + h = hash_djb2_buffer((const uint8_t *)&p_vtx.custom[0], sizeof(Color) * RS::ARRAY_CUSTOM_COUNT, h); return h; } @@ -111,8 +118,11 @@ void SurfaceTool::add_vertex(const Vector3 &p_vertex) { vtx.bones = last_bones; vtx.tangent = last_tangent.normal; vtx.binormal = last_normal.cross(last_tangent.normal).normalized() * last_tangent.d; + for (int i = 0; i < RS::ARRAY_CUSTOM_COUNT; i++) { + vtx.custom[i] = last_custom[i]; + } - const int expected_vertices = 4; + const int expected_vertices = skin_weights == SKIN_8_WEIGHTS ? 8 : 4; if ((format & Mesh::ARRAY_FORMAT_WEIGHTS || format & Mesh::ARRAY_FORMAT_BONES) && (vtx.weights.size() != expected_vertices || vtx.bones.size() != expected_vertices)) { //ensure vertices are the expected amount @@ -163,7 +173,7 @@ void SurfaceTool::add_vertex(const Vector3 &p_vertex) { format |= Mesh::ARRAY_FORMAT_VERTEX; } -void SurfaceTool::add_color(Color p_color) { +void SurfaceTool::set_color(Color p_color) { ERR_FAIL_COND(!begun); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_COLOR)); @@ -172,7 +182,7 @@ void SurfaceTool::add_color(Color p_color) { last_color = p_color; } -void SurfaceTool::add_normal(const Vector3 &p_normal) { +void SurfaceTool::set_normal(const Vector3 &p_normal) { ERR_FAIL_COND(!begun); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_NORMAL)); @@ -181,7 +191,7 @@ void SurfaceTool::add_normal(const Vector3 &p_normal) { last_normal = p_normal; } -void SurfaceTool::add_tangent(const Plane &p_tangent) { +void SurfaceTool::set_tangent(const Plane &p_tangent) { ERR_FAIL_COND(!begun); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_TANGENT)); @@ -189,7 +199,7 @@ void SurfaceTool::add_tangent(const Plane &p_tangent) { last_tangent = p_tangent; } -void SurfaceTool::add_uv(const Vector2 &p_uv) { +void SurfaceTool::set_uv(const Vector2 &p_uv) { ERR_FAIL_COND(!begun); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_TEX_UV)); @@ -197,7 +207,7 @@ void SurfaceTool::add_uv(const Vector2 &p_uv) { last_uv = p_uv; } -void SurfaceTool::add_uv2(const Vector2 &p_uv2) { +void SurfaceTool::set_uv2(const Vector2 &p_uv2) { ERR_FAIL_COND(!begun); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_TEX_UV2)); @@ -205,19 +215,40 @@ void SurfaceTool::add_uv2(const Vector2 &p_uv2) { last_uv2 = p_uv2; } -void SurfaceTool::add_bones(const Vector<int> &p_bones) { +void SurfaceTool::set_custom(int p_index, const Color &p_custom) { + ERR_FAIL_INDEX(p_index, RS::ARRAY_CUSTOM_COUNT); + ERR_FAIL_COND(!begun); + ERR_FAIL_COND(last_custom_format[p_index] == CUSTOM_MAX); + static const uint32_t mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 }; + static const uint32_t shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; + ERR_FAIL_COND(!first && !(format & mask[p_index])); + + if (first) { + format |= mask[p_index]; + format |= last_custom_format[p_index] << shift[p_index]; + } + last_custom[p_index] = p_custom; +} + +void SurfaceTool::set_bones(const Vector<int> &p_bones) { ERR_FAIL_COND(!begun); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_BONES)); format |= Mesh::ARRAY_FORMAT_BONES; + if (skin_weights == SKIN_8_WEIGHTS) { + format |= Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS; + } last_bones = p_bones; } -void SurfaceTool::add_weights(const Vector<float> &p_weights) { +void SurfaceTool::set_weights(const Vector<float> &p_weights) { ERR_FAIL_COND(!begun); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_WEIGHTS)); format |= Mesh::ARRAY_FORMAT_WEIGHTS; + if (skin_weights == SKIN_8_WEIGHTS) { + format |= Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS; + } last_weights = p_weights; } @@ -238,15 +269,15 @@ void SurfaceTool::add_triangle_fan(const Vector<Vector3> &p_vertices, const Vect #define ADD_POINT(n) \ { \ if (p_colors.size() > n) \ - add_color(p_colors[n]); \ + set_color(p_colors[n]); \ if (p_uvs.size() > n) \ - add_uv(p_uvs[n]); \ + set_uv(p_uvs[n]); \ if (p_uv2s.size() > n) \ - add_uv2(p_uv2s[n]); \ + set_uv2(p_uv2s[n]); \ if (p_normals.size() > n) \ - add_normal(p_normals[n]); \ + set_normal(p_normals[n]); \ if (p_tangents.size() > n) \ - add_tangent(p_tangents[n]); \ + set_tangent(p_tangents[n]); \ add_vertex(p_vertices[n]); \ } @@ -358,18 +389,157 @@ Array SurfaceTool::commit_to_arrays() { a[i] = array; } break; + case Mesh::ARRAY_CUSTOM0: + case Mesh::ARRAY_CUSTOM1: + case Mesh::ARRAY_CUSTOM2: + case Mesh::ARRAY_CUSTOM3: { + int fmt = i - Mesh::ARRAY_CUSTOM0; + switch (last_custom_format[fmt]) { + case CUSTOM_RGBA8_UNORM: { + Vector<uint8_t> array; + array.resize(varr_len * 4); + uint8_t *w = array.ptrw(); + + int idx = 0; + for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx++) { + const Vertex &v = E->get(); + const Color &c = v.custom[idx]; + w[idx * 4 + 0] = CLAMP(int32_t(c.r * 255.0), 0, 255); + w[idx * 4 + 1] = CLAMP(int32_t(c.g * 255.0), 0, 255); + w[idx * 4 + 2] = CLAMP(int32_t(c.b * 255.0), 0, 255); + w[idx * 4 + 3] = CLAMP(int32_t(c.a * 255.0), 0, 255); + } + + a[i] = array; + } break; + case CUSTOM_RGBA8_SNORM: { + Vector<uint8_t> array; + array.resize(varr_len * 4); + uint8_t *w = array.ptrw(); + + int idx = 0; + for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx++) { + const Vertex &v = E->get(); + const Color &c = v.custom[idx]; + w[idx * 4 + 0] = uint8_t(int8_t(CLAMP(int32_t(c.r * 127.0), -128, 127))); + w[idx * 4 + 1] = uint8_t(int8_t(CLAMP(int32_t(c.g * 127.0), -128, 127))); + w[idx * 4 + 2] = uint8_t(int8_t(CLAMP(int32_t(c.b * 127.0), -128, 127))); + w[idx * 4 + 3] = uint8_t(int8_t(CLAMP(int32_t(c.a * 127.0), -128, 127))); + } + + a[i] = array; + } break; + case CUSTOM_RG_HALF: { + Vector<uint8_t> array; + array.resize(varr_len * 4); + uint16_t *w = (uint16_t *)array.ptrw(); + + int idx = 0; + for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx++) { + const Vertex &v = E->get(); + const Color &c = v.custom[idx]; + w[idx * 2 + 0] = Math::make_half_float(c.r); + w[idx * 2 + 1] = Math::make_half_float(c.g); + } + + a[i] = array; + } break; + case CUSTOM_RGBA_HALF: { + Vector<uint8_t> array; + array.resize(varr_len * 8); + uint16_t *w = (uint16_t *)array.ptrw(); + + int idx = 0; + for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx++) { + const Vertex &v = E->get(); + const Color &c = v.custom[idx]; + w[idx * 4 + 0] = Math::make_half_float(c.r); + w[idx * 4 + 1] = Math::make_half_float(c.g); + w[idx * 4 + 2] = Math::make_half_float(c.b); + w[idx * 4 + 3] = Math::make_half_float(c.a); + } + + a[i] = array; + } break; + case CUSTOM_R_FLOAT: { + Vector<float> array; + array.resize(varr_len); + float *w = (float *)array.ptrw(); + + int idx = 0; + for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx++) { + const Vertex &v = E->get(); + const Color &c = v.custom[idx]; + w[idx] = c.r; + } + + a[i] = array; + } break; + case CUSTOM_RG_FLOAT: { + Vector<float> array; + array.resize(varr_len * 2); + float *w = (float *)array.ptrw(); + + int idx = 0; + for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx++) { + const Vertex &v = E->get(); + const Color &c = v.custom[idx]; + w[idx * 2 + 0] = c.r; + w[idx * 2 + 1] = c.g; + } + + a[i] = array; + } break; + case CUSTOM_RGB_FLOAT: { + Vector<float> array; + array.resize(varr_len * 3); + float *w = (float *)array.ptrw(); + + int idx = 0; + for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx++) { + const Vertex &v = E->get(); + const Color &c = v.custom[idx]; + w[idx * 3 + 0] = c.r; + w[idx * 3 + 1] = c.g; + w[idx * 3 + 2] = c.b; + } + + a[i] = array; + } break; + case CUSTOM_RGBA_FLOAT: { + Vector<float> array; + array.resize(varr_len * 4); + float *w = (float *)array.ptrw(); + + int idx = 0; + for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx++) { + const Vertex &v = E->get(); + const Color &c = v.custom[idx]; + w[idx * 4 + 0] = c.r; + w[idx * 4 + 1] = c.g; + w[idx * 4 + 2] = c.b; + w[idx * 4 + 3] = c.a; + } + + a[i] = array; + } break; + default: { + } //unreachable but compiler warning anyway + } + } break; case Mesh::ARRAY_BONES: { + int count = skin_weights == SKIN_8_WEIGHTS ? 8 : 4; Vector<int> array; - array.resize(varr_len * 4); + array.resize(varr_len * count); int *w = array.ptrw(); int idx = 0; - for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx += 4) { + for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx += count) { const Vertex &v = E->get(); - ERR_CONTINUE(v.bones.size() != 4); + ERR_CONTINUE(v.bones.size() != count); - for (int j = 0; j < 4; j++) { + for (int j = 0; j < count; j++) { w[idx + j] = v.bones[j]; } } @@ -379,15 +549,17 @@ Array SurfaceTool::commit_to_arrays() { } break; case Mesh::ARRAY_WEIGHTS: { Vector<float> array; - array.resize(varr_len * 4); + int count = skin_weights == SKIN_8_WEIGHTS ? 8 : 4; + + array.resize(varr_len * count); float *w = array.ptrw(); int idx = 0; - for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx += 4) { + for (List<Vertex>::Element *E = vertex_array.front(); E; E = E->next(), idx += count) { const Vertex &v = E->get(); - ERR_CONTINUE(v.weights.size() != 4); + ERR_CONTINUE(v.weights.size() != count); - for (int j = 0; j < 4; j++) { + for (int j = 0; j < count; j++) { w[idx + j] = v.weights[j]; } } @@ -492,13 +664,13 @@ void SurfaceTool::deindex() { index_array.clear(); } -void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, List<Vertex> *r_vertex, List<int> *r_index, int &lformat) { +void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, List<Vertex> *r_vertex, List<int> *r_index, uint32_t &lformat) { Array arr = p_existing->surface_get_arrays(p_surface); ERR_FAIL_COND(arr.size() != RS::ARRAY_MAX); _create_list_from_arrays(arr, r_vertex, r_index, lformat); } -Vector<SurfaceTool::Vertex> SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays) { +Vector<SurfaceTool::Vertex> SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays, uint32_t *r_format) { Vector<SurfaceTool::Vertex> ret; Vector<Vector3> varr = p_arrays[RS::ARRAY_VERTEX]; @@ -509,9 +681,13 @@ Vector<SurfaceTool::Vertex> SurfaceTool::create_vertex_array_from_triangle_array Vector<Vector2> uv2arr = p_arrays[RS::ARRAY_TEX_UV2]; Vector<int> barr = p_arrays[RS::ARRAY_BONES]; Vector<float> warr = p_arrays[RS::ARRAY_WEIGHTS]; + Vector<float> custom_float[RS::ARRAY_CUSTOM_COUNT]; int vc = varr.size(); if (vc == 0) { + if (r_format) { + *r_format = 0; + } return ret; } @@ -534,12 +710,40 @@ Vector<SurfaceTool::Vertex> SurfaceTool::create_vertex_array_from_triangle_array if (uv2arr.size()) { lformat |= RS::ARRAY_FORMAT_TEX_UV2; } - if (barr.size()) { + int wcount = 0; + if (barr.size() && warr.size()) { lformat |= RS::ARRAY_FORMAT_BONES; + lformat |= RS::ARRAY_FORMAT_WEIGHTS; + + wcount = barr.size() / varr.size(); + if (wcount == 8) { + lformat |= RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS; + } } + if (warr.size()) { lformat |= RS::ARRAY_FORMAT_WEIGHTS; } + static const uint32_t custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 }; + static const uint32_t custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; + + for (int i = 0; i < RS::ARRAY_CUSTOM_COUNT; i++) { + ERR_CONTINUE_MSG(p_arrays[RS::ARRAY_CUSTOM0 + i].get_type() == Variant::PACKED_BYTE_ARRAY, "Extracting Byte/Half formats is not supported"); + if (p_arrays[RS::ARRAY_CUSTOM0 + i].get_type() == Variant::PACKED_FLOAT32_ARRAY) { + lformat |= custom_mask[i]; + custom_float[i] = p_arrays[RS::ARRAY_CUSTOM0 + i]; + int fmt = custom_float[i].size() / varr.size(); + if (fmt == 1) { + lformat |= CUSTOM_R_FLOAT << custom_shift[i]; + } else if (fmt == 2) { + lformat |= CUSTOM_RG_FLOAT << custom_shift[i]; + } else if (fmt == 3) { + lformat |= CUSTOM_RGB_FLOAT << custom_shift[i]; + } else if (fmt == 4) { + lformat |= CUSTOM_RGBA_FLOAT << custom_shift[i]; + } + } + } for (int i = 0; i < vc; i++) { Vertex v; @@ -565,112 +769,46 @@ Vector<SurfaceTool::Vertex> SurfaceTool::create_vertex_array_from_triangle_array } if (lformat & RS::ARRAY_FORMAT_BONES) { Vector<int> b; - b.resize(4); - b.write[0] = barr[i * 4 + 0]; - b.write[1] = barr[i * 4 + 1]; - b.write[2] = barr[i * 4 + 2]; - b.write[3] = barr[i * 4 + 3]; + b.resize(wcount); + for (int j = 0; j < wcount; j++) { + b.write[j] = barr[i * wcount + j]; + } v.bones = b; } if (lformat & RS::ARRAY_FORMAT_WEIGHTS) { Vector<float> w; - w.resize(4); - w.write[0] = warr[i * 4 + 0]; - w.write[1] = warr[i * 4 + 1]; - w.write[2] = warr[i * 4 + 2]; - w.write[3] = warr[i * 4 + 3]; + w.resize(wcount); + for (int j = 0; j < wcount; j++) { + w.write[j] = warr[i * wcount + j]; + } v.weights = w; } + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (lformat & custom_mask[j]) { + int cc = custom_float[j].size() / varr.size(); + for (int k = 0; k < cc; k++) { + v.custom[j][k] = custom_float[j][i * cc + k]; + } + } + } + ret.push_back(v); } - return ret; -} - -void SurfaceTool::_create_list_from_arrays(Array arr, List<Vertex> *r_vertex, List<int> *r_index, int &lformat) { - Vector<Vector3> varr = arr[RS::ARRAY_VERTEX]; - Vector<Vector3> narr = arr[RS::ARRAY_NORMAL]; - Vector<float> tarr = arr[RS::ARRAY_TANGENT]; - Vector<Color> carr = arr[RS::ARRAY_COLOR]; - Vector<Vector2> uvarr = arr[RS::ARRAY_TEX_UV]; - Vector<Vector2> uv2arr = arr[RS::ARRAY_TEX_UV2]; - Vector<int> barr = arr[RS::ARRAY_BONES]; - Vector<float> warr = arr[RS::ARRAY_WEIGHTS]; - - int vc = varr.size(); - if (vc == 0) { - return; + if (r_format) { + *r_format = lformat; } - lformat = 0; - if (varr.size()) { - lformat |= RS::ARRAY_FORMAT_VERTEX; - } - if (narr.size()) { - lformat |= RS::ARRAY_FORMAT_NORMAL; - } - if (tarr.size()) { - lformat |= RS::ARRAY_FORMAT_TANGENT; - } - if (carr.size()) { - lformat |= RS::ARRAY_FORMAT_COLOR; - } - if (uvarr.size()) { - lformat |= RS::ARRAY_FORMAT_TEX_UV; - } - if (uv2arr.size()) { - lformat |= RS::ARRAY_FORMAT_TEX_UV2; - } - if (barr.size()) { - lformat |= RS::ARRAY_FORMAT_BONES; - } - if (warr.size()) { - lformat |= RS::ARRAY_FORMAT_WEIGHTS; - } + return ret; +} - for (int i = 0; i < vc; i++) { - Vertex v; - if (lformat & RS::ARRAY_FORMAT_VERTEX) { - v.vertex = varr[i]; - } - if (lformat & RS::ARRAY_FORMAT_NORMAL) { - v.normal = narr[i]; - } - if (lformat & RS::ARRAY_FORMAT_TANGENT) { - Plane p(tarr[i * 4 + 0], tarr[i * 4 + 1], tarr[i * 4 + 2], tarr[i * 4 + 3]); - v.tangent = p.normal; - v.binormal = p.normal.cross(v.tangent).normalized() * p.d; - } - if (lformat & RS::ARRAY_FORMAT_COLOR) { - v.color = carr[i]; - } - if (lformat & RS::ARRAY_FORMAT_TEX_UV) { - v.uv = uvarr[i]; - } - if (lformat & RS::ARRAY_FORMAT_TEX_UV2) { - v.uv2 = uv2arr[i]; - } - if (lformat & RS::ARRAY_FORMAT_BONES) { - Vector<int> b; - b.resize(4); - b.write[0] = barr[i * 4 + 0]; - b.write[1] = barr[i * 4 + 1]; - b.write[2] = barr[i * 4 + 2]; - b.write[3] = barr[i * 4 + 3]; - v.bones = b; - } - if (lformat & RS::ARRAY_FORMAT_WEIGHTS) { - Vector<float> w; - w.resize(4); - w.write[0] = warr[i * 4 + 0]; - w.write[1] = warr[i * 4 + 1]; - w.write[2] = warr[i * 4 + 2]; - w.write[3] = warr[i * 4 + 3]; - v.weights = w; - } +void SurfaceTool::_create_list_from_arrays(Array arr, List<Vertex> *r_vertex, List<int> *r_index, uint32_t &lformat) { + Vector<Vertex> arrays = create_vertex_array_from_triangle_arrays(arr, &lformat); + ERR_FAIL_COND(arrays.size() == 0); - r_vertex->push_back(v); + for (int i = 0; i < arrays.size(); i++) { + r_vertex->push_back(arrays[i]); } //indices @@ -725,7 +863,7 @@ void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const format = 0; } - int nformat; + uint32_t nformat; List<Vertex> nvertices; List<int> nindices; _create_list(p_existing, p_surface, &nvertices, &nindices, nformat); @@ -975,19 +1113,48 @@ void SurfaceTool::clear() { vertex_array.clear(); smooth_groups.clear(); material.unref(); + for (int i = 0; i < RS::ARRAY_CUSTOM_COUNT; i++) { + last_custom_format[i] = CUSTOM_MAX; + } + skin_weights = SKIN_4_WEIGHTS; +} + +void SurfaceTool::set_skin_weight_count(SkinWeightCount p_weights) { + ERR_FAIL_COND(begun); + skin_weights = p_weights; +} +SurfaceTool::SkinWeightCount SurfaceTool::get_skin_weight_count() const { + return skin_weights; +} + +void SurfaceTool::set_custom_format(int p_index, CustomFormat p_format) { + ERR_FAIL_INDEX(p_index, RS::ARRAY_CUSTOM_COUNT); + ERR_FAIL_COND(begun); + last_custom_format[p_index] = p_format; +} +SurfaceTool::CustomFormat SurfaceTool::get_custom_format(int p_index) const { + ERR_FAIL_INDEX_V(p_index, RS::ARRAY_CUSTOM_COUNT, CUSTOM_MAX); + return last_custom_format[p_index]; } void SurfaceTool::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_skin_weight_count", "count"), &SurfaceTool::set_skin_weight_count); + ClassDB::bind_method(D_METHOD("get_skin_weight_count"), &SurfaceTool::get_skin_weight_count); + + ClassDB::bind_method(D_METHOD("set_custom_format", "index", "format"), &SurfaceTool::set_custom_format); + ClassDB::bind_method(D_METHOD("get_custom_format", "index"), &SurfaceTool::get_custom_format); + ClassDB::bind_method(D_METHOD("begin", "primitive"), &SurfaceTool::begin); ClassDB::bind_method(D_METHOD("add_vertex", "vertex"), &SurfaceTool::add_vertex); - ClassDB::bind_method(D_METHOD("add_color", "color"), &SurfaceTool::add_color); - ClassDB::bind_method(D_METHOD("add_normal", "normal"), &SurfaceTool::add_normal); - ClassDB::bind_method(D_METHOD("add_tangent", "tangent"), &SurfaceTool::add_tangent); - ClassDB::bind_method(D_METHOD("add_uv", "uv"), &SurfaceTool::add_uv); - ClassDB::bind_method(D_METHOD("add_uv2", "uv2"), &SurfaceTool::add_uv2); - ClassDB::bind_method(D_METHOD("add_bones", "bones"), &SurfaceTool::add_bones); - ClassDB::bind_method(D_METHOD("add_weights", "weights"), &SurfaceTool::add_weights); + ClassDB::bind_method(D_METHOD("set_color", "color"), &SurfaceTool::set_color); + ClassDB::bind_method(D_METHOD("set_normal", "normal"), &SurfaceTool::set_normal); + ClassDB::bind_method(D_METHOD("set_tangent", "tangent"), &SurfaceTool::set_tangent); + ClassDB::bind_method(D_METHOD("set_uv", "uv"), &SurfaceTool::set_uv); + ClassDB::bind_method(D_METHOD("set_uv2", "uv2"), &SurfaceTool::set_uv2); + ClassDB::bind_method(D_METHOD("set_bones", "bones"), &SurfaceTool::set_bones); + ClassDB::bind_method(D_METHOD("set_weights", "weights"), &SurfaceTool::set_weights); + ClassDB::bind_method(D_METHOD("set_custom", "index", "custom"), &SurfaceTool::set_custom); ClassDB::bind_method(D_METHOD("add_smooth_group", "smooth"), &SurfaceTool::add_smooth_group); ClassDB::bind_method(D_METHOD("add_triangle_fan", "vertices", "uvs", "colors", "uv2s", "normals", "tangents"), &SurfaceTool::add_triangle_fan, DEFVAL(Vector<Vector2>()), DEFVAL(Vector<Color>()), DEFVAL(Vector<Vector2>()), DEFVAL(Vector<Vector3>()), DEFVAL(Vector<Plane>())); @@ -1006,13 +1173,29 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("create_from", "existing", "surface"), &SurfaceTool::create_from); ClassDB::bind_method(D_METHOD("create_from_blend_shape", "existing", "surface", "blend_shape"), &SurfaceTool::create_from_blend_shape); ClassDB::bind_method(D_METHOD("append_from", "existing", "surface", "transform"), &SurfaceTool::append_from); - ClassDB::bind_method(D_METHOD("commit", "existing", "flags"), &SurfaceTool::commit, DEFVAL(Variant()), DEFVAL(Mesh::ARRAY_COMPRESS_DEFAULT)); + ClassDB::bind_method(D_METHOD("commit", "existing", "flags"), &SurfaceTool::commit, DEFVAL(Variant()), DEFVAL(0)); ClassDB::bind_method(D_METHOD("commit_to_arrays"), &SurfaceTool::commit_to_arrays); + + BIND_ENUM_CONSTANT(CUSTOM_RGBA8_UNORM); + BIND_ENUM_CONSTANT(CUSTOM_RGBA8_SNORM); + BIND_ENUM_CONSTANT(CUSTOM_RG_HALF); + BIND_ENUM_CONSTANT(CUSTOM_RGBA_HALF); + BIND_ENUM_CONSTANT(CUSTOM_R_FLOAT); + BIND_ENUM_CONSTANT(CUSTOM_RG_FLOAT); + BIND_ENUM_CONSTANT(CUSTOM_RGB_FLOAT); + BIND_ENUM_CONSTANT(CUSTOM_RGBA_FLOAT); + BIND_ENUM_CONSTANT(CUSTOM_MAX); + BIND_ENUM_CONSTANT(SKIN_4_WEIGHTS); + BIND_ENUM_CONSTANT(SKIN_8_WEIGHTS); } SurfaceTool::SurfaceTool() { first = false; begun = false; + for (int i = 0; i < RS::ARRAY_CUSTOM_COUNT; i++) { + last_custom_format[i] = CUSTOM_MAX; + } primitive = Mesh::PRIMITIVE_LINES; + skin_weights = SKIN_4_WEIGHTS; format = 0; } diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index d7b255e695..4a5c7d990c 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -49,12 +49,30 @@ public: Vector2 uv2; Vector<int> bones; Vector<float> weights; + Color custom[RS::ARRAY_CUSTOM_COUNT]; bool operator==(const Vertex &p_vertex) const; Vertex() {} }; + enum CustomFormat { + CUSTOM_RGBA8_UNORM = RS::ARRAY_CUSTOM_RGBA8_UNORM, + CUSTOM_RGBA8_SNORM = RS::ARRAY_CUSTOM_RGBA8_SNORM, + CUSTOM_RG_HALF = RS::ARRAY_CUSTOM_RG_HALF, + CUSTOM_RGBA_HALF = RS::ARRAY_CUSTOM_RGBA_HALF, + CUSTOM_R_FLOAT = RS::ARRAY_CUSTOM_R_FLOAT, + CUSTOM_RG_FLOAT = RS::ARRAY_CUSTOM_RG_FLOAT, + CUSTOM_RGB_FLOAT = RS::ARRAY_CUSTOM_RGB_FLOAT, + CUSTOM_RGBA_FLOAT = RS::ARRAY_CUSTOM_RGBA_FLOAT, + CUSTOM_MAX = RS::ARRAY_CUSTOM_MAX + }; + + enum SkinWeightCount { + SKIN_4_WEIGHTS, + SKIN_8_WEIGHTS + }; + private: struct VertexHasher { static _FORCE_INLINE_ uint32_t hash(const Vertex &p_vtx); @@ -71,7 +89,7 @@ private: bool begun; bool first; Mesh::PrimitiveType primitive; - int format; + uint32_t format; Ref<Material> material; //arrays List<Vertex> vertex_array; @@ -87,8 +105,14 @@ private: Vector<float> last_weights; Plane last_tangent; - void _create_list_from_arrays(Array arr, List<Vertex> *r_vertex, List<int> *r_index, int &lformat); - void _create_list(const Ref<Mesh> &p_existing, int p_surface, List<Vertex> *r_vertex, List<int> *r_index, int &lformat); + SkinWeightCount skin_weights; + + Color last_custom[RS::ARRAY_CUSTOM_COUNT]; + + CustomFormat last_custom_format[RS::ARRAY_CUSTOM_COUNT]; + + void _create_list_from_arrays(Array arr, List<Vertex> *r_vertex, List<int> *r_index, uint32_t &lformat); + void _create_list(const Ref<Mesh> &p_existing, int p_surface, List<Vertex> *r_vertex, List<int> *r_index, uint32_t &lformat); //mikktspace callbacks static int mikktGetNumFaces(const SMikkTSpaceContext *pContext); @@ -103,18 +127,26 @@ protected: static void _bind_methods(); public: + void set_skin_weight_count(SkinWeightCount p_weights); + SkinWeightCount get_skin_weight_count() const; + + void set_custom_format(int p_index, CustomFormat p_format); + CustomFormat get_custom_format(int p_index) const; + void begin(Mesh::PrimitiveType p_primitive); + void set_color(Color p_color); + void set_normal(const Vector3 &p_normal); + void set_tangent(const Plane &p_tangent); + void set_uv(const Vector2 &p_uv); + void set_uv2(const Vector2 &p_uv2); + void set_custom(int p_index, const Color &p_custom); + void set_bones(const Vector<int> &p_bones); + void set_weights(const Vector<float> &p_weights); + void add_vertex(const Vector3 &p_vertex); - void add_color(Color p_color); - void add_normal(const Vector3 &p_normal); - void add_tangent(const Plane &p_tangent); - void add_uv(const Vector2 &p_uv); - void add_uv2(const Vector2 &p_uv2); - void add_bones(const Vector<int> &p_bones); - void add_weights(const Vector<float> &p_weights); - void add_smooth_group(bool p_smooth); + void add_smooth_group(bool p_smooth); void add_triangle_fan(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uvs = Vector<Vector2>(), const Vector<Color> &p_colors = Vector<Color>(), const Vector<Vector2> &p_uv2s = Vector<Vector2>(), const Vector<Vector3> &p_normals = Vector<Vector3>(), const Vector<Plane> &p_tangents = Vector<Plane>()); void add_index(int p_index); @@ -131,14 +163,17 @@ public: List<Vertex> &get_vertex_array() { return vertex_array; } void create_from_triangle_arrays(const Array &p_arrays); - static Vector<Vertex> create_vertex_array_from_triangle_arrays(const Array &p_arrays); + static Vector<Vertex> create_vertex_array_from_triangle_arrays(const Array &p_arrays, uint32_t *r_format = nullptr); Array commit_to_arrays(); void create_from(const Ref<Mesh> &p_existing, int p_surface); void create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name); void append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform &p_xform); - Ref<ArrayMesh> commit(const Ref<ArrayMesh> &p_existing = Ref<ArrayMesh>(), uint32_t p_flags = Mesh::ARRAY_COMPRESS_DEFAULT); + Ref<ArrayMesh> commit(const Ref<ArrayMesh> &p_existing = Ref<ArrayMesh>(), uint32_t p_flags = 0); SurfaceTool(); }; +VARIANT_ENUM_CAST(SurfaceTool::CustomFormat) +VARIANT_ENUM_CAST(SurfaceTool::SkinWeightCount) + #endif diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 785a5ebba6..34129e35da 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -811,8 +811,12 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port #define IS_SYMBOL_CHAR(m_d) (((m_d) >= 'a' && (m_d) <= 'z') || ((m_d) >= 'A' && (m_d) <= 'Z') || ((m_d) >= '0' && (m_d) <= '9') || (m_d) == '_') -String VisualShader::validate_port_name(const String &p_name, const List<String> &p_input_ports, const List<String> &p_output_ports) const { - String name = p_name; +String VisualShader::validate_port_name(const String &p_port_name, VisualShaderNode *p_node, int p_port_id, bool p_output) const { + String name = p_port_name; + + if (name == String()) { + return String(); + } while (name.length() && !IS_INITIAL_CHAR(name[0])) { name = name.substr(1, name.length() - 1); @@ -830,29 +834,28 @@ String VisualShader::validate_port_name(const String &p_name, const List<String> } name = valid_name; + } else { + return String(); } - String valid_name = name; - bool is_equal = false; + List<String> input_names; + List<String> output_names; - for (int i = 0; i < p_input_ports.size(); i++) { - if (name == p_input_ports[i]) { - is_equal = true; - break; + for (int i = 0; i < p_node->get_input_port_count(); i++) { + if (!p_output && i == p_port_id) { + continue; } - } - - if (!is_equal) { - for (int i = 0; i < p_output_ports.size(); i++) { - if (name == p_output_ports[i]) { - is_equal = true; - break; - } + if (name == p_node->get_input_port_name(i)) { + return String(); } } - - if (is_equal) { - name = ""; + for (int i = 0; i < p_node->get_output_port_count(); i++) { + if (p_output && i == p_port_id) { + continue; + } + if (name == p_node->get_output_port_name(i)) { + return String(); + } } return name; @@ -1706,7 +1709,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "view", "VIEW" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_color", "LIGHT_COLOR" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "attenuation", "ATTENUATION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "attenuation", "ATTENUATION" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "albedo", "ALBEDO" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "backlight", "BACKLIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "diffuse", "DIFFUSE_LIGHT" }, @@ -1724,17 +1727,17 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, // Canvas Item, Vertex { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "vec3(VERTEX, 0.0)" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "texture_pixel_size", "vec3(TEXTURE_PIXEL_SIZE, 1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION_MATRIX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "extra", "EXTRA_MATRIX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "canvas", "CANVAS_MATRIX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "screen", "SCREEN_MATRIX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "light_pass", "float(AT_LIGHT_PASS ? 1.0 : 0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_light_pass", "AT_LIGHT_PASS" }, // Canvas Item, Fragment { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" }, @@ -1745,29 +1748,34 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_pixel_size", "vec3(SCREEN_PIXEL_SIZE, 1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec3(POINT_COORD, 0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "light_pass", "float(AT_LIGHT_PASS ? 1.0 : 0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_light_pass", "AT_LIGHT_PASS" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "texture", "TEXTURE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "normal_texture", "NORMAL_TEXTURE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "screen_texture", "SCREEN_TEXTURE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "specular_shininess", "SPECULAR_SHININESS.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "specular_shininess_alpha", "SPECULAR_SHININESS.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "specular_shininess_texture", "SPECULAR_SHININESS_TEXTURE" }, // Canvas Item, Light { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_vec", "vec3(LIGHT_VEC, 0.0)" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_height", "LIGHT_HEIGHT" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_alpha", "LIGHT.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_color", "LIGHT_COLOR.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_alpha", "LIGHT_COLOR.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_uv", "vec3(LIGHT_UV, 0.0)" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "shadow_color", "SHADOW_COLOR.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "shadow_alpha", "SHADOW_COLOR.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "shadow_vec", "vec3(SHADOW_VEC, 0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_color_alpha", "LIGHT_COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_position", "LIGHT_POSITION" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_vertex", "LIGHT_VERTEX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "shadow_color", "SHADOW_MODULATE.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "shadow_alpha", "SHADOW_MODULATE.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "texture_pixel_size", "vec3(TEXTURE_PIXEL_SIZE, 1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec3(POINT_COORD, 0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SAMPLER, "texture", "TEXTURE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "specular_shininess", "SPECULAR_SHININESS.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "specular_shininess_alpha", "SPECULAR_SHININESS.a" }, // Particles, Emit { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, @@ -1862,7 +1870,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "side", "1.0" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(1.0,1.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(1.0, 1.0, 0.0)" }, // Spatial, Light { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0, 0.0, 1.0)" }, @@ -1954,7 +1962,7 @@ String VisualShaderNodeInput::generate_code(Shader::Mode p_mode, VisualShader::T code = "\t" + p_output_vars[0] + " = vec3(0.0);\n"; } break; case PORT_TYPE_TRANSFORM: { - code = "\t" + p_output_vars[0] + " = mat4( vec4(1.0,0.0,0.0,0.0), vec4(0.0,1.0,0.0,0.0), vec4(0.0,0.0,1.0,0.0), vec4(0.0,0.0,0.0,1.0) );\n"; + code = "\t" + p_output_vars[0] + " = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } break; case PORT_TYPE_BOOLEAN: { code = "\t" + p_output_vars[0] + " = false;\n"; @@ -2762,8 +2770,10 @@ void VisualShaderNodeGroupBase::remove_input_port(int p_id) { inputs.erase(index, count); inputs_strings = inputs.split(";", false); + inputs = inputs.substr(0, index); + for (int i = p_id; i < inputs_strings.size(); i++) { - inputs = inputs.replace_first(inputs_strings[i].split(",")[0], itos(i)); + inputs += inputs_strings[i].replace_first(inputs_strings[i].split(",")[0], itos(i)) + ";"; } _apply_port_changes(); @@ -2832,8 +2842,10 @@ void VisualShaderNodeGroupBase::remove_output_port(int p_id) { outputs.erase(index, count); outputs_strings = outputs.split(";", false); + outputs = outputs.substr(0, index); + for (int i = p_id; i < outputs_strings.size(); i++) { - outputs = outputs.replace_first(outputs_strings[i].split(",")[0], itos(i)); + outputs += outputs_strings[i].replace_first(outputs_strings[i].split(",")[0], itos(i)) + ";"; } _apply_port_changes(); diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 41c4642ee3..a38c2886e2 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -175,7 +175,7 @@ public: String generate_preview_shader(Type p_type, int p_node, int p_port, Vector<DefaultTextureParam> &r_default_tex_params) const; - String validate_port_name(const String &p_name, const List<String> &p_input_ports, const List<String> &p_output_ports) const; + String validate_port_name(const String &p_port_name, VisualShaderNode *p_node, int p_port_id, bool p_output) const; String validate_uniform_name(const String &p_name, const Ref<VisualShaderNodeUniform> &p_uniform) const; VisualShader(); diff --git a/servers/rendering/rasterizer_rd/rasterizer_canvas_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_canvas_rd.cpp index 921a7b966e..f5360cbd36 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_canvas_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_canvas_rd.cpp @@ -119,9 +119,9 @@ RasterizerCanvas::PolygonID RasterizerCanvasRD::request_polygon(const Vector<int Vector<uint8_t> polygon_buffer; polygon_buffer.resize(buffer_size * sizeof(float)); Vector<RD::VertexAttribute> descriptions; - descriptions.resize(4); + descriptions.resize(5); Vector<RID> buffers; - buffers.resize(4); + buffers.resize(5); { const uint8_t *r = polygon_buffer.ptr(); @@ -218,7 +218,7 @@ RasterizerCanvas::PolygonID RasterizerCanvasRD::request_polygon(const Vector<int //bones if ((uint32_t)p_indices.size() == vertex_count * 4 && (uint32_t)p_weights.size() == vertex_count * 4) { RD::VertexAttribute vd; - vd.format = RD::DATA_FORMAT_R32G32B32A32_UINT; + vd.format = RD::DATA_FORMAT_R16G16B16A16_UINT; vd.offset = base_offset * sizeof(float); vd.location = RS::ARRAY_BONES; vd.stride = stride * sizeof(float); @@ -226,16 +226,42 @@ RasterizerCanvas::PolygonID RasterizerCanvasRD::request_polygon(const Vector<int descriptions.write[3] = vd; const int *bone_ptr = p_bones.ptr(); - const float *weight_ptr = p_weights.ptr(); for (uint32_t i = 0; i < vertex_count; i++) { uint16_t *bone16w = (uint16_t *)&uptr[base_offset + i * stride]; - uint16_t *weight16w = (uint16_t *)&uptr[base_offset + i * stride + 2]; bone16w[0] = bone_ptr[i * 4 + 0]; bone16w[1] = bone_ptr[i * 4 + 1]; bone16w[2] = bone_ptr[i * 4 + 2]; bone16w[3] = bone_ptr[i * 4 + 3]; + } + + base_offset += 2; + } else { + RD::VertexAttribute vd; + vd.format = RD::DATA_FORMAT_R32G32B32A32_UINT; + vd.offset = 0; + vd.location = RS::ARRAY_BONES; + vd.stride = 0; + + descriptions.write[3] = vd; + buffers.write[3] = storage->mesh_get_default_rd_buffer(RasterizerStorageRD::DEFAULT_RD_BUFFER_BONES); + } + + //weights + if ((uint32_t)p_weights.size() == vertex_count * 4) { + RD::VertexAttribute vd; + vd.format = RD::DATA_FORMAT_R16G16B16A16_UNORM; + vd.offset = base_offset * sizeof(float); + vd.location = RS::ARRAY_WEIGHTS; + vd.stride = stride * sizeof(float); + + descriptions.write[4] = vd; + + const float *weight_ptr = p_weights.ptr(); + + for (uint32_t i = 0; i < vertex_count; i++) { + uint16_t *weight16w = (uint16_t *)&uptr[base_offset + i * stride]; weight16w[0] = CLAMP(weight_ptr[i * 4 + 0] * 65535, 0, 65535); weight16w[1] = CLAMP(weight_ptr[i * 4 + 1] * 65535, 0, 65535); @@ -243,16 +269,16 @@ RasterizerCanvas::PolygonID RasterizerCanvasRD::request_polygon(const Vector<int weight16w[3] = CLAMP(weight_ptr[i * 4 + 3] * 65535, 0, 65535); } - base_offset += 4; + base_offset += 2; } else { RD::VertexAttribute vd; - vd.format = RD::DATA_FORMAT_R32G32B32A32_UINT; + vd.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; vd.offset = 0; - vd.location = RS::ARRAY_BONES; + vd.location = RS::ARRAY_WEIGHTS; vd.stride = 0; - descriptions.write[3] = vd; - buffers.write[3] = storage->mesh_get_default_rd_buffer(RasterizerStorageRD::DEFAULT_RD_BUFFER_BONES); + descriptions.write[4] = vd; + buffers.write[4] = storage->mesh_get_default_rd_buffer(RasterizerStorageRD::DEFAULT_RD_BUFFER_BONES); } //check that everything is as it should be @@ -1796,22 +1822,25 @@ void RasterizerCanvasRD::occluder_polygon_set_shape(RID p_occluder, const Vector ERR_FAIL_COND(!oc); Vector<Vector2> lines; - int lc = p_points.size() * 2; - lines.resize(lc - (p_closed ? 0 : 2)); - { - Vector2 *w = lines.ptrw(); - const Vector2 *r = p_points.ptr(); + if (p_points.size()) { + int lc = p_points.size() * 2; - int max = lc / 2; - if (!p_closed) { - max--; - } - for (int i = 0; i < max; i++) { - Vector2 a = r[i]; - Vector2 b = r[(i + 1) % (lc / 2)]; - w[i * 2 + 0] = a; - w[i * 2 + 1] = b; + lines.resize(lc - (p_closed ? 0 : 2)); + { + Vector2 *w = lines.ptrw(); + const Vector2 *r = p_points.ptr(); + + int max = lc / 2; + if (!p_closed) { + max--; + } + for (int i = 0; i < max; i++) { + Vector2 a = r[i]; + Vector2 b = r[(i + 1) % (lc / 2)]; + w[i * 2 + 0] = a; + w[i * 2 + 1] = b; + } } } @@ -1832,7 +1861,7 @@ void RasterizerCanvasRD::occluder_polygon_set_shape(RID p_occluder, const Vector if (lines.size()) { Vector<uint8_t> geometry; Vector<uint8_t> indices; - lc = lines.size(); + int lc = lines.size(); geometry.resize(lc * 6 * sizeof(float)); indices.resize(lc * 3 * sizeof(uint16_t)); @@ -1902,19 +1931,21 @@ void RasterizerCanvasRD::occluder_polygon_set_shape(RID p_occluder, const Vector Vector<int> sdf_indices; - if (p_closed) { - sdf_indices = Geometry2D::triangulate_polygon(p_points); - oc->sdf_is_lines = false; - } else { - int max = p_points.size(); - sdf_indices.resize(max * 2); + if (p_points.size()) { + if (p_closed) { + sdf_indices = Geometry2D::triangulate_polygon(p_points); + oc->sdf_is_lines = false; + } else { + int max = p_points.size(); + sdf_indices.resize(max * 2); - int *iw = sdf_indices.ptrw(); - for (int i = 0; i < max; i++) { - iw[i * 2 + 0] = i; - iw[i * 2 + 1] = (i + 1) % max; + int *iw = sdf_indices.ptrw(); + for (int i = 0; i < max; i++) { + iw[i * 2 + 0] = i; + iw[i * 2 + 1] = (i + 1) % max; + } + oc->sdf_is_lines = true; } - oc->sdf_is_lines = true; } if (oc->sdf_index_count != sdf_indices.size() && oc->sdf_point_count != p_points.size() && oc->sdf_vertex_array.is_valid()) { diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp index 313188ba87..66561958fc 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp @@ -2795,6 +2795,12 @@ RasterizerSceneHighEndRD::RasterizerSceneHighEndRD(RasterizerStorageRD *p_storag actions.renames["FOG"] = "custom_fog"; actions.renames["RADIANCE"] = "custom_radiance"; actions.renames["IRRADIANCE"] = "custom_irradiance"; + actions.renames["BONE_INDICES"] = "bone_attrib"; + actions.renames["BONE_WEIGHTS"] = "weight_attrib"; + actions.renames["CUSTOM0"] = "custom0_attrib"; + actions.renames["CUSTOM1"] = "custom1_attrib"; + actions.renames["CUSTOM2"] = "custom2_attrib"; + actions.renames["CUSTOM3"] = "custom3_attrib"; //for light actions.renames["VIEW"] = "view"; @@ -2817,6 +2823,12 @@ RasterizerSceneHighEndRD::RasterizerSceneHighEndRD(RasterizerStorageRD *p_storag actions.usage_defines["AO_LIGHT_AFFECT"] = "#define AO_USED\n"; actions.usage_defines["UV"] = "#define UV_USED\n"; actions.usage_defines["UV2"] = "#define UV2_USED\n"; + actions.usage_defines["BONE_INDICES"] = "#define BONES_USED\n"; + actions.usage_defines["BONE_WEIGHTS"] = "#define WEIGHTS_USED\n"; + actions.usage_defines["CUSTOM0"] = "#define CUSTOM0\n"; + actions.usage_defines["CUSTOM1"] = "#define CUSTOM1\n"; + actions.usage_defines["CUSTOM2"] = "#define CUSTOM2\n"; + actions.usage_defines["CUSTOM3"] = "#define CUSTOM3\n"; actions.usage_defines["NORMALMAP"] = "#define NORMALMAP_USED\n"; actions.usage_defines["NORMALMAP_DEPTH"] = "@NORMALMAP"; actions.usage_defines["COLOR"] = "#define COLOR_USED\n"; diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp index 3c4cac7ba9..d6f08370e0 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp @@ -5336,18 +5336,19 @@ void RasterizerSceneRD::_render_buffers_post_process_and_tonemap(RID p_render_bu tonemap.exposure = env->exposure; } + tonemap.use_color_correction = false; + tonemap.use_1d_color_correction = false; + tonemap.color_correction_texture = storage->texture_rd_get_default(RasterizerStorageRD::DEFAULT_RD_TEXTURE_3D_WHITE); + if (can_use_effects && env) { tonemap.use_bcs = env->adjustments_enabled; tonemap.brightness = env->adjustments_brightness; tonemap.contrast = env->adjustments_contrast; tonemap.saturation = env->adjustments_saturation; - tonemap.use_1d_color_correction = env->use_1d_color_correction; if (env->adjustments_enabled && env->color_correction.is_valid()) { tonemap.use_color_correction = true; + tonemap.use_1d_color_correction = env->use_1d_color_correction; tonemap.color_correction_texture = storage->texture_get_rd_texture(env->color_correction); - } else { - tonemap.use_color_correction = false; - tonemap.color_correction_texture = storage->texture_rd_get_default(RasterizerStorageRD::DEFAULT_RD_TEXTURE_WHITE); } } diff --git a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp index a4d79ffc87..819404b316 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp @@ -2398,13 +2398,15 @@ void RasterizerStorageRD::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_ ERR_FAIL_COND(!mesh); //ensure blend shape consistency - ERR_FAIL_COND(mesh->blend_shape_count && p_surface.blend_shapes.size() != (int)mesh->blend_shape_count); + ERR_FAIL_COND(mesh->blend_shape_count && p_surface.blend_shape_count != mesh->blend_shape_count); ERR_FAIL_COND(mesh->blend_shape_count && p_surface.bone_aabbs.size() != mesh->bone_aabbs.size()); #ifdef DEBUG_ENABLED //do a validation, to catch errors first { uint32_t stride = 0; + uint32_t attrib_stride = 0; + uint32_t skin_stride = 0; for (int i = 0; i < RS::ARRAY_WEIGHTS; i++) { if ((p_surface.format & (1 << i))) { @@ -2418,59 +2420,54 @@ void RasterizerStorageRD::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_ } break; case RS::ARRAY_NORMAL: { - if (p_surface.format & RS::ARRAY_COMPRESS_NORMAL) { - stride += sizeof(int8_t) * 4; - } else { - stride += sizeof(float) * 4; - } + stride += sizeof(int32_t); } break; case RS::ARRAY_TANGENT: { - if (p_surface.format & RS::ARRAY_COMPRESS_TANGENT) { - stride += sizeof(int8_t) * 4; - } else { - stride += sizeof(float) * 4; - } + stride += sizeof(int32_t); } break; case RS::ARRAY_COLOR: { - if (p_surface.format & RS::ARRAY_COMPRESS_COLOR) { - stride += sizeof(int8_t) * 4; - } else { - stride += sizeof(float) * 4; - } - + attrib_stride += sizeof(int16_t) * 4; } break; case RS::ARRAY_TEX_UV: { - if (p_surface.format & RS::ARRAY_COMPRESS_TEX_UV) { - stride += sizeof(int16_t) * 2; - } else { - stride += sizeof(float) * 2; - } + attrib_stride += sizeof(float) * 2; } break; case RS::ARRAY_TEX_UV2: { - if (p_surface.format & RS::ARRAY_COMPRESS_TEX_UV2) { - stride += sizeof(int16_t) * 2; - } else { - stride += sizeof(float) * 2; - } + attrib_stride += sizeof(float) * 2; } break; - case RS::ARRAY_BONES: { - //assumed weights too - - //unique format, internally 16 bits, exposed as single array for 32 - - stride += sizeof(int32_t) * 4; + case RS::ARRAY_CUSTOM0: + case RS::ARRAY_CUSTOM1: + case RS::ARRAY_CUSTOM2: + case RS::ARRAY_CUSTOM3: { + int idx = i - RS::ARRAY_CUSTOM0; + uint32_t fmt_shift[RS::ARRAY_CUSTOM_COUNT] = { RS::ARRAY_FORMAT_CUSTOM0_SHIFT, RS::ARRAY_FORMAT_CUSTOM1_SHIFT, RS::ARRAY_FORMAT_CUSTOM2_SHIFT, RS::ARRAY_FORMAT_CUSTOM3_SHIFT }; + uint32_t fmt = (p_surface.format >> fmt_shift[idx]) & RS::ARRAY_FORMAT_CUSTOM_MASK; + uint32_t fmtsize[RS::ARRAY_CUSTOM_MAX] = { 4, 4, 4, 8, 4, 8, 12, 16 }; + attrib_stride += fmtsize[fmt]; } break; + case RS::ARRAY_WEIGHTS: + case RS::ARRAY_BONES: { + //uses a separate array + bool use_8 = p_surface.format & RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS; + skin_stride += sizeof(int16_t) * (use_8 ? 8 : 4); + } break; } } } int expected_size = stride * p_surface.vertex_count; - ERR_FAIL_COND_MSG(expected_size != p_surface.vertex_data.size(), "Size of data provided (" + itos(p_surface.vertex_data.size()) + ") does not match expected (" + itos(expected_size) + ")"); + ERR_FAIL_COND_MSG(expected_size != p_surface.vertex_data.size(), "Size of vertex data provided (" + itos(p_surface.vertex_data.size()) + ") does not match expected (" + itos(expected_size) + ")"); + int expected_attrib_size = attrib_stride * p_surface.vertex_count; + ERR_FAIL_COND_MSG(expected_attrib_size != p_surface.attribute_data.size(), "Size of attribute data provided (" + itos(p_surface.attribute_data.size()) + ") does not match expected (" + itos(expected_attrib_size) + ")"); + + if ((p_surface.format & RS::ARRAY_FORMAT_WEIGHTS) && (p_surface.format & RS::ARRAY_FORMAT_BONES)) { + expected_size = skin_stride * p_surface.vertex_count; + ERR_FAIL_COND_MSG(expected_size != p_surface.skin_data.size(), "Size of skin data provided (" + itos(p_surface.skin_data.size()) + ") does not match expected (" + itos(expected_size) + ")"); + } } #endif @@ -2481,6 +2478,12 @@ void RasterizerStorageRD::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_ s->primitive = p_surface.primitive; s->vertex_buffer = RD::get_singleton()->vertex_buffer_create(p_surface.vertex_data.size(), p_surface.vertex_data); + if (p_surface.attribute_data.size()) { + s->attribute_buffer = RD::get_singleton()->vertex_buffer_create(p_surface.attribute_data.size(), p_surface.attribute_data); + } + if (p_surface.skin_data.size()) { + s->skin_buffer = RD::get_singleton()->vertex_buffer_create(p_surface.skin_data.size(), p_surface.skin_data); + } s->vertex_count = p_surface.vertex_count; if (p_surface.index_count) { @@ -2504,7 +2507,7 @@ void RasterizerStorageRD::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_ s->aabb = p_surface.aabb; s->bone_aabbs = p_surface.bone_aabbs; //only really useful for returning them. - +#if 0 for (int i = 0; i < p_surface.blend_shapes.size(); i++) { if (p_surface.blend_shapes[i].size() != p_surface.vertex_data.size()) { memdelete(s); @@ -2513,8 +2516,8 @@ void RasterizerStorageRD::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_ RID vertex_buffer = RD::get_singleton()->vertex_buffer_create(p_surface.blend_shapes[i].size(), p_surface.blend_shapes[i]); s->blend_shapes.push_back(vertex_buffer); } - - mesh->blend_shape_count = p_surface.blend_shapes.size(); +#endif + mesh->blend_shape_count = p_surface.blend_shape_count; if (mesh->surface_count == 0) { mesh->bone_aabbs = p_surface.bone_aabbs; @@ -2596,6 +2599,12 @@ RS::SurfaceData RasterizerStorageRD::mesh_get_surface(RID p_mesh, int p_surface) RS::SurfaceData sd; sd.format = s.format; sd.vertex_data = RD::get_singleton()->buffer_get_data(s.vertex_buffer); + if (s.attribute_buffer.is_valid()) { + sd.attribute_data = RD::get_singleton()->buffer_get_data(s.attribute_buffer); + } + if (s.skin_buffer.is_valid()) { + sd.skin_data = RD::get_singleton()->buffer_get_data(s.skin_buffer); + } sd.vertex_count = s.vertex_count; sd.index_count = s.index_count; sd.primitive = s.primitive; @@ -2613,9 +2622,8 @@ RS::SurfaceData RasterizerStorageRD::mesh_get_surface(RID p_mesh, int p_surface) sd.bone_aabbs = s.bone_aabbs; - for (int i = 0; i < s.blend_shapes.size(); i++) { - Vector<uint8_t> bs = RD::get_singleton()->buffer_get_data(s.blend_shapes[i]); - sd.blend_shapes.push_back(bs); + if (s.blend_shape_buffer.is_valid()) { + sd.blend_shape_data = RD::get_singleton()->buffer_get_data(s.blend_shape_buffer); } return sd; @@ -2750,6 +2758,12 @@ void RasterizerStorageRD::mesh_clear(RID p_mesh) { for (uint32_t i = 0; i < mesh->surface_count; i++) { Mesh::Surface &s = *mesh->surfaces[i]; RD::get_singleton()->free(s.vertex_buffer); //clears arrays as dependency automatically, including all versions + if (s.attribute_buffer.is_valid()) { + RD::get_singleton()->free(s.attribute_buffer); + } + if (s.skin_buffer.is_valid()) { + RD::get_singleton()->free(s.skin_buffer); + } if (s.versions) { memfree(s.versions); //reallocs, so free with memfree. } @@ -2765,12 +2779,8 @@ void RasterizerStorageRD::mesh_clear(RID p_mesh) { memdelete_arr(s.lods); } - for (int32_t j = 0; j < s.blend_shapes.size(); j++) { - RD::get_singleton()->free(s.blend_shapes[j]); - } - - if (s.blend_shape_base_buffer.is_valid()) { - RD::get_singleton()->free(s.blend_shape_base_buffer); + if (s.blend_shape_buffer.is_valid()) { + RD::get_singleton()->free(s.blend_shape_buffer); } memdelete(mesh->surfaces[i]); @@ -2796,8 +2806,10 @@ void RasterizerStorageRD::_mesh_surface_generate_version_for_input_mask(Mesh::Su Vector<RID> buffers; uint32_t stride = 0; + uint32_t attribute_stride = 0; + uint32_t skin_stride = 0; - for (int i = 0; i < RS::ARRAY_WEIGHTS; i++) { + for (int i = 0; i < RS::ARRAY_INDEX; i++) { RD::VertexAttribute vd; RID buffer; vd.location = i; @@ -2805,6 +2817,7 @@ void RasterizerStorageRD::_mesh_surface_generate_version_for_input_mask(Mesh::Su if (!(s->format & (1 << i))) { // Not supplied by surface, use default value buffer = mesh_default_rd_buffers[i]; + vd.stride = 0; switch (i) { case RS::ARRAY_VERTEX: { vd.format = RD::DATA_FORMAT_R32G32B32_SFLOAT; @@ -2827,20 +2840,31 @@ void RasterizerStorageRD::_mesh_surface_generate_version_for_input_mask(Mesh::Su case RS::ARRAY_TEX_UV2: { vd.format = RD::DATA_FORMAT_R32G32_SFLOAT; } break; + case RS::ARRAY_CUSTOM0: + case RS::ARRAY_CUSTOM1: + case RS::ARRAY_CUSTOM2: + case RS::ARRAY_CUSTOM3: { + //assumed weights too + vd.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; + } break; case RS::ARRAY_BONES: { //assumed weights too vd.format = RD::DATA_FORMAT_R32G32B32A32_UINT; } break; + case RS::ARRAY_WEIGHTS: { + //assumed weights too + vd.format = RD::DATA_FORMAT_R32G32B32A32_UINT; + } break; } } else { //Supplied, use it - vd.offset = stride; - vd.stride = 1; //mark that it needs a stride set - buffer = s->vertex_buffer; + vd.stride = 1; //mark that it needs a stride set (default uses 0) switch (i) { case RS::ARRAY_VERTEX: { + vd.offset = stride; + if (s->format & RS::ARRAY_FLAG_USE_2D_VERTICES) { vd.format = RD::DATA_FORMAT_R32G32_SFLOAT; stride += sizeof(float) * 2; @@ -2849,71 +2873,80 @@ void RasterizerStorageRD::_mesh_surface_generate_version_for_input_mask(Mesh::Su stride += sizeof(float) * 3; } + buffer = s->vertex_buffer; + } break; case RS::ARRAY_NORMAL: { - if (s->format & RS::ARRAY_COMPRESS_NORMAL) { - vd.format = RD::DATA_FORMAT_R8G8B8A8_SNORM; - stride += sizeof(int8_t) * 4; - } else { - vd.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; - stride += sizeof(float) * 4; - } + vd.offset = stride; + vd.format = RD::DATA_FORMAT_A2B10G10R10_UNORM_PACK32; + + stride += sizeof(uint32_t); + buffer = s->vertex_buffer; } break; case RS::ARRAY_TANGENT: { - if (s->format & RS::ARRAY_COMPRESS_TANGENT) { - vd.format = RD::DATA_FORMAT_R8G8B8A8_SNORM; - stride += sizeof(int8_t) * 4; - } else { - vd.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; - stride += sizeof(float) * 4; - } + vd.offset = stride; + vd.format = RD::DATA_FORMAT_A2B10G10R10_UNORM_PACK32; + stride += sizeof(uint32_t); + buffer = s->vertex_buffer; } break; case RS::ARRAY_COLOR: { - if (s->format & RS::ARRAY_COMPRESS_COLOR) { - vd.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; - stride += sizeof(int8_t) * 4; - } else { - vd.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; - stride += sizeof(float) * 4; - } + vd.offset = attribute_stride; + vd.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT; + attribute_stride += sizeof(int16_t) * 4; + buffer = s->attribute_buffer; } break; case RS::ARRAY_TEX_UV: { - if (s->format & RS::ARRAY_COMPRESS_TEX_UV) { - vd.format = RD::DATA_FORMAT_R16G16_SFLOAT; - stride += sizeof(int16_t) * 2; - } else { - vd.format = RD::DATA_FORMAT_R32G32_SFLOAT; - stride += sizeof(float) * 2; - } + vd.offset = attribute_stride; + + vd.format = RD::DATA_FORMAT_R32G32_SFLOAT; + attribute_stride += sizeof(float) * 2; + buffer = s->attribute_buffer; } break; case RS::ARRAY_TEX_UV2: { - if (s->format & RS::ARRAY_COMPRESS_TEX_UV2) { - vd.format = RD::DATA_FORMAT_R16G16_SFLOAT; - stride += sizeof(int16_t) * 2; - } else { - vd.format = RD::DATA_FORMAT_R32G32_SFLOAT; - stride += sizeof(float) * 2; - } + vd.offset = attribute_stride; + vd.format = RD::DATA_FORMAT_R32G32_SFLOAT; + attribute_stride += sizeof(float) * 2; + buffer = s->attribute_buffer; + } break; + case RS::ARRAY_CUSTOM0: + case RS::ARRAY_CUSTOM1: + case RS::ARRAY_CUSTOM2: + case RS::ARRAY_CUSTOM3: { + vd.offset = attribute_stride; + + int idx = i - RS::ARRAY_CUSTOM0; + uint32_t fmt_shift[RS::ARRAY_CUSTOM_COUNT] = { RS::ARRAY_FORMAT_CUSTOM0_SHIFT, RS::ARRAY_FORMAT_CUSTOM1_SHIFT, RS::ARRAY_FORMAT_CUSTOM2_SHIFT, RS::ARRAY_FORMAT_CUSTOM3_SHIFT }; + uint32_t fmt = (s->format >> fmt_shift[idx]) & RS::ARRAY_FORMAT_CUSTOM_MASK; + uint32_t fmtsize[RS::ARRAY_CUSTOM_MAX] = { 4, 4, 4, 8, 4, 8, 12, 16 }; + RD::DataFormat fmtrd[RS::ARRAY_CUSTOM_MAX] = { RD::DATA_FORMAT_R8G8B8A8_UNORM, RD::DATA_FORMAT_R8G8B8A8_SNORM, RD::DATA_FORMAT_R16G16_SFLOAT, RD::DATA_FORMAT_R16G16B16A16_SFLOAT, RD::DATA_FORMAT_R32_SFLOAT, RD::DATA_FORMAT_R32G32_SFLOAT, RD::DATA_FORMAT_R32G32B32_SFLOAT, RD::DATA_FORMAT_R32G32B32A32_SFLOAT }; + vd.format = fmtrd[fmt]; + attribute_stride += fmtsize[fmt]; + buffer = s->attribute_buffer; } break; case RS::ARRAY_BONES: { - //assumed weights too - - //unique format, internally 16 bits, exposed as single array for 32 + vd.offset = skin_stride; - vd.format = RD::DATA_FORMAT_R32G32B32A32_UINT; - stride += sizeof(int32_t) * 4; + vd.format = RD::DATA_FORMAT_R16G16B16A16_UINT; + skin_stride += sizeof(int16_t) * 4; + buffer = s->skin_buffer; + } break; + case RS::ARRAY_WEIGHTS: { + vd.offset = skin_stride; + vd.format = RD::DATA_FORMAT_R16G16B16A16_UNORM; + skin_stride += sizeof(int16_t) * 4; + buffer = s->skin_buffer; } break; } } if (!(p_input_mask & (1 << i))) { - continue; // Shader does not need this, skip it + continue; // Shader does not need this, skip it (but computing stride was important anyway) } attributes.push_back(vd); @@ -2922,8 +2955,17 @@ void RasterizerStorageRD::_mesh_surface_generate_version_for_input_mask(Mesh::Su //update final stride for (int i = 0; i < attributes.size(); i++) { - if (attributes[i].stride == 1) { + if (attributes[i].stride == 0) { + continue; //default location + } + int loc = attributes[i].location; + + if (loc < RS::ARRAY_COLOR) { attributes.write[i].stride = stride; + } else if (loc < RS::ARRAY_BONES) { + attributes.write[i].stride = attribute_stride; + } else { + attributes.write[i].stride = skin_stride; } } @@ -8263,6 +8305,19 @@ RasterizerStorageRD::RasterizerStorageRD() { mesh_default_rd_buffers[DEFAULT_RD_BUFFER_TEX_UV2] = RD::get_singleton()->vertex_buffer_create(buffer.size(), buffer); } + for (int i = 0; i < RS::ARRAY_CUSTOM_COUNT; i++) { + buffer.resize(sizeof(float) * 4); + { + uint8_t *w = buffer.ptrw(); + float *fptr = (float *)w; + fptr[0] = 0.0; + fptr[1] = 0.0; + fptr[2] = 0.0; + fptr[3] = 0.0; + } + mesh_default_rd_buffers[DEFAULT_RD_BUFFER_CUSTOM0 + i] = RD::get_singleton()->vertex_buffer_create(buffer.size(), buffer); + } + { //bones buffer.resize(sizeof(uint32_t) * 4); { diff --git a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h index 42dd0616b0..d887f122c9 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h +++ b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h @@ -170,6 +170,10 @@ public: DEFAULT_RD_BUFFER_COLOR, DEFAULT_RD_BUFFER_TEX_UV, DEFAULT_RD_BUFFER_TEX_UV2, + DEFAULT_RD_BUFFER_CUSTOM0, + DEFAULT_RD_BUFFER_CUSTOM1, + DEFAULT_RD_BUFFER_CUSTOM2, + DEFAULT_RD_BUFFER_CUSTOM3, DEFAULT_RD_BUFFER_BONES, DEFAULT_RD_BUFFER_WEIGHTS, DEFAULT_RD_BUFFER_MAX, @@ -378,6 +382,8 @@ private: uint32_t format = 0; RID vertex_buffer; + RID attribute_buffer; + RID skin_buffer; uint32_t vertex_count = 0; // A different pipeline needs to be allocated @@ -414,8 +420,7 @@ private: Vector<AABB> bone_aabbs; - Vector<RID> blend_shapes; - RID blend_shape_base_buffer; //source buffer goes here when using blend shapes, and main one is uncompressed + RID blend_shape_buffer; RID material; diff --git a/servers/rendering/rasterizer_rd/shaders/canvas.glsl b/servers/rendering/rasterizer_rd/shaders/canvas.glsl index 51d7193a03..7808e7ed52 100644 --- a/servers/rendering/rasterizer_rd/shaders/canvas.glsl +++ b/servers/rendering/rasterizer_rd/shaders/canvas.glsl @@ -9,7 +9,8 @@ layout(location = 0) in vec2 vertex_attrib; layout(location = 3) in vec4 color_attrib; layout(location = 4) in vec2 uv_attrib; -layout(location = 6) in uvec4 bones_attrib; +layout(location = 10) in uvec4 bone_attrib; +layout(location = 11) in vec4 weight_attrib; #endif @@ -61,6 +62,7 @@ void main() { color = vec4(unpackHalf2x16(draw_data.colors[4]), unpackHalf2x16(draw_data.colors[5])); } uvec4 bones = uvec4(0, 0, 0, 0); + vec4 bone_weights = vec4(0.0); #elif defined(USE_ATTRIBUTES) @@ -68,7 +70,8 @@ void main() { vec4 color = color_attrib; vec2 uv = uv_attrib; - uvec4 bones = bones_attrib; + uvec4 bones = bone_attrib; + vec4 bone_weights = weight_attrib; #else vec2 vertex_base_arr[4] = vec2[](vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0)); diff --git a/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl b/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl index 285698f060..5d87dec79f 100644 --- a/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl +++ b/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl @@ -24,7 +24,29 @@ layout(location = 4) in vec2 uv_attrib; layout(location = 5) in vec2 uv2_attrib; #endif -layout(location = 6) in uvec4 bone_attrib; // always bound, even if unused +#if defined(CUSTOM0_USED) +layout(location = 6) in vec4 custom0_attrib; +#endif + +#if defined(CUSTOM1_USED) +layout(location = 7) in vec4 custom1_attrib; +#endif + +#if defined(CUSTOM2_USED) +layout(location = 8) in vec4 custom2_attrib; +#endif + +#if defined(CUSTOM3_USED) +layout(location = 9) in vec4 custom3_attrib; +#endif + +#if defined(BONES_USED) +layout(location = 10) in uvec4 bone_attrib; +#endif + +#if defined(WEIGHTS_USED) +layout(location = 11) in vec4 weight_attrib; +#endif /* Varyings */ @@ -116,14 +138,15 @@ void main() { } vec3 vertex = vertex_attrib; - vec3 normal = normal_attrib; + vec3 normal = normal_attrib * 2.0 - 1.0; #if defined(TANGENT_USED) || defined(NORMALMAP_USED) || defined(LIGHT_ANISOTROPY_USED) - vec3 tangent = tangent_attrib.xyz; - float binormalf = tangent_attrib.a; + vec3 tangent = tangent_attrib.xyz * 2.0 - 1.0; + float binormalf = tangent_attrib.a * 2.0 - 1.0; vec3 binormal = normalize(cross(normal, tangent) * binormalf); #endif +#if 0 if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_SKELETON)) { //multimesh, instances are for it @@ -147,7 +170,7 @@ void main() { binormal = (vec4(binormal, 0.0) * m).xyz; #endif } - +#endif uv_interp = uv_attrib; #if defined(UV2_USED) || defined(USE_LIGHTMAP) diff --git a/servers/rendering/rendering_server_canvas.cpp b/servers/rendering/rendering_server_canvas.cpp index d518d709c5..0e61d53866 100644 --- a/servers/rendering/rendering_server_canvas.cpp +++ b/servers/rendering/rendering_server_canvas.cpp @@ -545,6 +545,8 @@ void RenderingServerCanvas::canvas_item_add_polyline(RID p_item, const Vector<Po Item *canvas_item = canvas_item_owner.getornull(p_item); ERR_FAIL_COND(!canvas_item); + Color color = Color(1, 1, 1, 1); + Vector<int> indices; int pc = p_points.size(); int pc2 = pc * 2; @@ -565,6 +567,8 @@ void RenderingServerCanvas::canvas_item_add_polyline(RID p_item, const Vector<Po Color *colors_ptr = colors.ptrw(); if (p_antialiased) { + Color color2 = Color(1, 1, 1, 0); + PackedColorArray colors_top; PackedVector2Array points_top; @@ -616,8 +620,10 @@ void RenderingServerCanvas::canvas_item_add_polyline(RID p_item, const Vector<Po points_bottom_ptr[j] = pos - tangent; points_bottom_ptr[j2] = pos - tangent - tangent; - Color color = p_colors[i]; - Color color2 = Color(color.r, color.g, color.b, 0); + if (i < p_colors.size()) { + color = p_colors[i]; + color2 = Color(color.r, color.g, color.b, 0); + } colors_ptr[j] = color; colors_ptr[j2] = color; @@ -653,13 +659,15 @@ void RenderingServerCanvas::canvas_item_add_polyline(RID p_item, const Vector<Po j2 = j + 1; Vector2 tangent = ((t + prev_t).normalized()) * p_width * 0.5; - - Vector2 pos = p_points[j]; - Color color = p_colors[j2]; + Vector2 pos = p_points[i]; points_ptr[j] = pos + tangent; points_ptr[j2] = pos - tangent; + if (i < p_colors.size()) { + color = p_colors[i]; + } + colors_ptr[j] = color; colors_ptr[j2] = color; diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp index bd61f2a549..0c9b2ddf2f 100644 --- a/servers/rendering/shader_types.cpp +++ b/servers/rendering/shader_types.cpp @@ -67,6 +67,12 @@ ShaderTypes::ShaderTypes() { shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["INSTANCE_ID"] = constt(ShaderLanguage::TYPE_INT); shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["INSTANCE_CUSTOM"] = constt(ShaderLanguage::TYPE_VEC4); shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["ROUGHNESS"] = ShaderLanguage::TYPE_FLOAT; + shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["BONE_INDICES"] = ShaderLanguage::TYPE_UVEC4; + shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["BONE_WEIGHTS"] = ShaderLanguage::TYPE_VEC4; + shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["CUSTOM0"] = ShaderLanguage::TYPE_VEC4; + shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["CUSTOM1"] = ShaderLanguage::TYPE_VEC4; + shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["CUSTOM2"] = ShaderLanguage::TYPE_VEC4; + shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["CUSTOM3"] = ShaderLanguage::TYPE_VEC4; shader_modes[RS::SHADER_SPATIAL].functions["vertex"].can_discard = false; //builtins diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 9c8342b34d..599b9e09f2 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -315,8 +315,10 @@ RID RenderingServer::get_white_texture() { #define SMALL_VEC2 Vector2(0.00001, 0.00001) #define SMALL_VEC3 Vector3(0.00001, 0.00001, 0.00001) -Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint32_t *p_offsets, uint32_t p_stride, Vector<uint8_t> &r_vertex_array, int p_vertex_array_len, Vector<uint8_t> &r_index_array, int p_index_array_len, AABB &r_aabb, Vector<AABB> &r_bone_aabb) { +Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint32_t *p_offsets, uint32_t p_vertex_stride, uint32_t p_attrib_stride, uint32_t p_skin_stride, Vector<uint8_t> &r_vertex_array, Vector<uint8_t> &r_attrib_array, Vector<uint8_t> &r_skin_array, int p_vertex_array_len, Vector<uint8_t> &r_index_array, int p_index_array_len, AABB &r_aabb, Vector<AABB> &r_bone_aabb) { uint8_t *vw = r_vertex_array.ptrw(); + uint8_t *aw = r_attrib_array.ptrw(); + uint8_t *sw = r_skin_array.ptrw(); uint8_t *iw = nullptr; if (r_index_array.size()) { @@ -345,7 +347,7 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint for (int i = 0; i < p_vertex_array_len; i++) { float vector[2] = { src[i].x, src[i].y }; - copymem(&vw[p_offsets[ai] + i * p_stride], vector, sizeof(float) * 2); + copymem(&vw[p_offsets[ai] + i * p_vertex_stride], vector, sizeof(float) * 2); if (i == 0) { aabb = Rect2(src[i], SMALL_VEC2); //must have a bit of size @@ -370,7 +372,7 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint for (int i = 0; i < p_vertex_array_len; i++) { float vector[3] = { src[i].x, src[i].y, src[i].z }; - copymem(&vw[p_offsets[ai] + i * p_stride], vector, sizeof(float) * 3); + copymem(&vw[p_offsets[ai] + i * p_vertex_stride], vector, sizeof(float) * 3); if (i == 0) { aabb = AABB(src[i], SMALL_VEC3); @@ -391,26 +393,15 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint ERR_FAIL_COND_V(array.size() != p_vertex_array_len, ERR_INVALID_PARAMETER); const Vector3 *src = array.ptr(); + for (int i = 0; i < p_vertex_array_len; i++) { + Vector3 n = src[i] * Vector3(0.5, 0.5, 0.5) + Vector3(0.5, 0.5, 0.5); - // setting vertices means regenerating the AABB + uint32_t value = 0; + value |= CLAMP(int(n.x * 1023.0), 0, 1023); + value |= CLAMP(int(n.y * 1023.0), 0, 1023) << 10; + value |= CLAMP(int(n.z * 1023.0), 0, 1023) << 20; - if (p_format & ARRAY_COMPRESS_NORMAL) { - for (int i = 0; i < p_vertex_array_len; i++) { - int8_t vector[4] = { - (int8_t)CLAMP(src[i].x * 127, -128, 127), - (int8_t)CLAMP(src[i].y * 127, -128, 127), - (int8_t)CLAMP(src[i].z * 127, -128, 127), - 0, - }; - - copymem(&vw[p_offsets[ai] + i * p_stride], vector, 4); - } - - } else { - for (int i = 0; i < p_vertex_array_len; i++) { - float vector[3] = { src[i].x, src[i].y, src[i].z }; - copymem(&vw[p_offsets[ai] + i * p_stride], vector, 3 * 4); - } + copymem(&vw[p_offsets[ai] + i * p_vertex_stride], &value, 4); } } break; @@ -424,29 +415,14 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint const real_t *src = array.ptr(); - if (p_format & ARRAY_COMPRESS_TANGENT) { - for (int i = 0; i < p_vertex_array_len; i++) { - int8_t xyzw[4] = { - (int8_t)CLAMP(src[i * 4 + 0] * 127, -128, 127), - (int8_t)CLAMP(src[i * 4 + 1] * 127, -128, 127), - (int8_t)CLAMP(src[i * 4 + 2] * 127, -128, 127), - (int8_t)CLAMP(src[i * 4 + 3] * 127, -128, 127) - }; - - copymem(&vw[p_offsets[ai] + i * p_stride], xyzw, 4); - } + for (int i = 0; i < p_vertex_array_len; i++) { + uint32_t value = 0; + value |= CLAMP(int((src[i * 4 + 0] * 0.5 + 0.5) * 1023.0), 0, 1023); + value |= CLAMP(int((src[i * 4 + 1] * 0.5 + 0.5) * 1023.0), 0, 1023) << 10; + value |= CLAMP(int((src[i * 4 + 2] * 0.5 + 0.5) * 1023.0), 0, 1023) << 20; + value |= CLAMP(int((src[i * 4 + 3] * 0.5 + 0.5) * 3.0), 0, 3) << 30; - } else { - for (int i = 0; i < p_vertex_array_len; i++) { - float xyzw[4] = { - src[i * 4 + 0], - src[i * 4 + 1], - src[i * 4 + 2], - src[i * 4 + 3] - }; - - copymem(&vw[p_offsets[ai] + i * p_stride], xyzw, 4 * 4); - } + copymem(&vw[p_offsets[ai] + i * p_vertex_stride], &value, 4); } } break; @@ -458,23 +434,14 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint ERR_FAIL_COND_V(array.size() != p_vertex_array_len, ERR_INVALID_PARAMETER); const Color *src = array.ptr(); - - if (p_format & ARRAY_COMPRESS_COLOR) { - for (int i = 0; i < p_vertex_array_len; i++) { - uint8_t colors[4]; - - for (int j = 0; j < 4; j++) { - colors[j] = CLAMP(int((src[i][j]) * 255.0), 0, 255); - } - - copymem(&vw[p_offsets[ai] + i * p_stride], colors, 4); - } - } else { - for (int i = 0; i < p_vertex_array_len; i++) { - copymem(&vw[p_offsets[ai] + i * p_stride], &src[i], 4 * 4); - } + uint16_t color16[4]; + for (int i = 0; i < p_vertex_array_len; i++) { + color16[0] = Math::make_half_float(src[i].r); + color16[1] = Math::make_half_float(src[i].g); + color16[2] = Math::make_half_float(src[i].b); + color16[3] = Math::make_half_float(src[i].a); + copymem(&aw[p_offsets[ai] + i * p_attrib_stride], color16, 8); } - } break; case RS::ARRAY_TEX_UV: { ERR_FAIL_COND_V(p_arrays[ai].get_type() != Variant::PACKED_VECTOR3_ARRAY && p_arrays[ai].get_type() != Variant::PACKED_VECTOR2_ARRAY, ERR_INVALID_PARAMETER); @@ -485,18 +452,10 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint const Vector2 *src = array.ptr(); - if (p_format & ARRAY_COMPRESS_TEX_UV) { - for (int i = 0; i < p_vertex_array_len; i++) { - uint16_t uv[2] = { Math::make_half_float(src[i].x), Math::make_half_float(src[i].y) }; - copymem(&vw[p_offsets[ai] + i * p_stride], uv, 2 * 2); - } - - } else { - for (int i = 0; i < p_vertex_array_len; i++) { - float uv[2] = { src[i].x, src[i].y }; + for (int i = 0; i < p_vertex_array_len; i++) { + float uv[2] = { src[i].x, src[i].y }; - copymem(&vw[p_offsets[ai] + i * p_stride], uv, 2 * 4); - } + copymem(&aw[p_offsets[ai] + i * p_attrib_stride], uv, 2 * 4); } } break; @@ -510,37 +469,90 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint const Vector2 *src = array.ptr(); - if (p_format & ARRAY_COMPRESS_TEX_UV2) { - for (int i = 0; i < p_vertex_array_len; i++) { - uint16_t uv[2] = { Math::make_half_float(src[i].x), Math::make_half_float(src[i].y) }; - copymem(&vw[p_offsets[ai] + i * p_stride], uv, 2 * 2); - } + for (int i = 0; i < p_vertex_array_len; i++) { + uint16_t uv[2] = { Math::make_half_float(src[i].x), Math::make_half_float(src[i].y) }; + copymem(&aw[p_offsets[ai] + i * p_attrib_stride], uv, 2 * 2); + } + } break; + case RS::ARRAY_CUSTOM0: + case RS::ARRAY_CUSTOM1: + case RS::ARRAY_CUSTOM2: + case RS::ARRAY_CUSTOM3: { + uint32_t type = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * (RS::ARRAY_CUSTOM0 - ai))) & ARRAY_FORMAT_CUSTOM_MASK; + switch (type) { + case ARRAY_CUSTOM_RGBA8_UNORM: + case ARRAY_CUSTOM_RGBA8_SNORM: + case ARRAY_CUSTOM_RG_HALF: { + //size 4 + ERR_FAIL_COND_V(p_arrays[ai].get_type() != Variant::PACKED_BYTE_ARRAY, ERR_INVALID_PARAMETER); - } else { - for (int i = 0; i < p_vertex_array_len; i++) { - float uv[2] = { src[i].x, src[i].y }; + Vector<uint8_t> array = p_arrays[ai]; + + ERR_FAIL_COND_V(array.size() != p_vertex_array_len * 4, ERR_INVALID_PARAMETER); + + const uint8_t *src = array.ptr(); + + for (int i = 0; i < p_vertex_array_len; i++) { + copymem(&aw[p_offsets[ai] + i * p_attrib_stride], &src[i * 4], 4); + } + + } break; + case ARRAY_CUSTOM_RGBA_HALF: { + //size 8 + ERR_FAIL_COND_V(p_arrays[ai].get_type() != Variant::PACKED_BYTE_ARRAY, ERR_INVALID_PARAMETER); + + Vector<uint8_t> array = p_arrays[ai]; + + ERR_FAIL_COND_V(array.size() != p_vertex_array_len * 8, ERR_INVALID_PARAMETER); + + const uint8_t *src = array.ptr(); - copymem(&vw[p_offsets[ai] + i * p_stride], uv, 2 * 4); + for (int i = 0; i < p_vertex_array_len; i++) { + copymem(&aw[p_offsets[ai] + i * p_attrib_stride], &src[i * 8], 8); + } + } break; + case ARRAY_CUSTOM_R_FLOAT: + case ARRAY_CUSTOM_RG_FLOAT: + case ARRAY_CUSTOM_RGB_FLOAT: + case ARRAY_CUSTOM_RGBA_FLOAT: { + //RF + ERR_FAIL_COND_V(p_arrays[ai].get_type() != Variant::PACKED_FLOAT32_ARRAY, ERR_INVALID_PARAMETER); + + Vector<float> array = p_arrays[ai]; + int32_t s = ARRAY_CUSTOM_R_FLOAT - ai + 1; + + ERR_FAIL_COND_V(array.size() != p_vertex_array_len * s, ERR_INVALID_PARAMETER); + + const float *src = array.ptr(); + + for (int i = 0; i < p_vertex_array_len; i++) { + copymem(&aw[p_offsets[ai] + i * p_attrib_stride], &src[i * s], 4 * s); + } + } break; + default: { } } + } break; case RS::ARRAY_WEIGHTS: { ERR_FAIL_COND_V(p_arrays[ai].get_type() != Variant::PACKED_FLOAT32_ARRAY, ERR_INVALID_PARAMETER); + uint32_t bone_count = (p_format & ARRAY_FLAG_USE_8_BONE_WEIGHTS) ? 8 : 4; + Vector<real_t> array = p_arrays[ai]; - ERR_FAIL_COND_V(array.size() != p_vertex_array_len * RS::ARRAY_WEIGHTS_SIZE, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(array.size() != (int32_t)(p_vertex_array_len * bone_count), ERR_INVALID_PARAMETER); const real_t *src = array.ptr(); { + uint16_t data[8]; for (int i = 0; i < p_vertex_array_len; i++) { - uint16_t data[RS::ARRAY_WEIGHTS_SIZE]; - for (int j = 0; j < RS::ARRAY_WEIGHTS_SIZE; j++) { - data[j] = CLAMP(src[i * RS::ARRAY_WEIGHTS_SIZE + j] * 65535, 0, 65535); + for (uint32_t j = 0; j < bone_count; j++) { + data[j] = CLAMP(src[i * bone_count + j] * 65535, 0, 65535); } - copymem(&vw[p_offsets[ai] + i * p_stride], data, 2 * 4); + copymem(&sw[p_offsets[ai] + i * p_skin_stride], data, 2 * bone_count); } } @@ -550,21 +562,25 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint Vector<int> array = p_arrays[ai]; - ERR_FAIL_COND_V(array.size() != p_vertex_array_len * RS::ARRAY_WEIGHTS_SIZE, ERR_INVALID_PARAMETER); + uint32_t bone_count = (p_format & ARRAY_FLAG_USE_8_BONE_WEIGHTS) ? 8 : 4; + + ERR_FAIL_COND_V(array.size() != (int32_t)(p_vertex_array_len * bone_count), ERR_INVALID_PARAMETER); const int *src = array.ptr(); + uint16_t data[8]; + for (int i = 0; i < p_vertex_array_len; i++) { - uint16_t data[RS::ARRAY_WEIGHTS_SIZE]; - for (int j = 0; j < RS::ARRAY_WEIGHTS_SIZE; j++) { - data[j] = src[i * RS::ARRAY_WEIGHTS_SIZE + j]; + for (uint32_t j = 0; j < bone_count; j++) { + data[j] = src[i * bone_count + j]; max_bone = MAX(data[j], max_bone); } - copymem(&vw[p_offsets[ai] + i * p_stride], data, 2 * 4); + copymem(&sw[p_offsets[ai] + i * p_skin_stride], data, 2 * bone_count); } } break; + case RS::ARRAY_INDEX: { ERR_FAIL_NULL_V(iw, ERR_INVALID_DATA); ERR_FAIL_COND_V(p_index_array_len <= 0, ERR_INVALID_DATA); @@ -652,23 +668,62 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint return OK; } -uint32_t RenderingServer::mesh_surface_get_format_offset(uint32_t p_format, int p_vertex_len, int p_index_len, int p_array_index) const { +uint32_t RenderingServer::mesh_surface_get_format_offset(uint32_t p_format, int p_vertex_len, int p_array_index) const { + p_format &= ~ARRAY_FORMAT_INDEX; uint32_t offsets[ARRAY_MAX]; - mesh_surface_make_offsets_from_format(p_format, p_vertex_len, p_index_len, offsets); + uint32_t vstr; + uint32_t astr; + uint32_t sstr; + mesh_surface_make_offsets_from_format(p_format, p_vertex_len, 0, offsets, vstr, astr, sstr); return offsets[p_array_index]; } -uint32_t RenderingServer::mesh_surface_get_format_stride(uint32_t p_format, int p_vertex_len, int p_index_len) const { +uint32_t RenderingServer::mesh_surface_get_format_vertex_stride(uint32_t p_format, int p_vertex_len) const { + p_format &= ~ARRAY_FORMAT_INDEX; uint32_t offsets[ARRAY_MAX]; - return mesh_surface_make_offsets_from_format(p_format, p_vertex_len, p_index_len, offsets); + uint32_t vstr; + uint32_t astr; + uint32_t sstr; + mesh_surface_make_offsets_from_format(p_format, p_vertex_len, 0, offsets, vstr, astr, sstr); + return vstr; } +uint32_t RenderingServer::mesh_surface_get_format_attribute_stride(uint32_t p_format, int p_vertex_len) const { + p_format &= ~ARRAY_FORMAT_INDEX; + uint32_t offsets[ARRAY_MAX]; + uint32_t vstr; + uint32_t astr; + uint32_t sstr; + mesh_surface_make_offsets_from_format(p_format, p_vertex_len, 0, offsets, vstr, astr, sstr); + return astr; +} +uint32_t RenderingServer::mesh_surface_get_format_skin_stride(uint32_t p_format, int p_vertex_len) const { + p_format &= ~ARRAY_FORMAT_INDEX; + uint32_t offsets[ARRAY_MAX]; + uint32_t vstr; + uint32_t astr; + uint32_t sstr; + mesh_surface_make_offsets_from_format(p_format, p_vertex_len, 0, offsets, vstr, astr, sstr); + return sstr; +} + +void RenderingServer::mesh_surface_make_offsets_from_format(uint32_t p_format, int p_vertex_len, int p_index_len, uint32_t *r_offsets, uint32_t &r_vertex_element_size, uint32_t &r_attrib_element_size, uint32_t &r_skin_element_size) const { + r_vertex_element_size = 0; + r_attrib_element_size = 0; + r_skin_element_size = 0; -uint32_t RenderingServer::mesh_surface_make_offsets_from_format(uint32_t p_format, int p_vertex_len, int p_index_len, uint32_t *r_offsets) const { - int total_elem_size = 0; + uint32_t *size_accum; for (int i = 0; i < RS::ARRAY_MAX; i++) { r_offsets[i] = 0; //reset + if (i == RS::ARRAY_VERTEX) { + size_accum = &r_vertex_element_size; + } else if (i == RS::ARRAY_COLOR) { + size_accum = &r_attrib_element_size; + } else if (i == RS::ARRAY_BONES) { + size_accum = &r_skin_element_size; + } + if (!(p_format & (1 << i))) { // no array continue; } @@ -693,53 +748,64 @@ uint32_t RenderingServer::mesh_surface_make_offsets_from_format(uint32_t p_forma } break; case RS::ARRAY_NORMAL: { - if (p_format & ARRAY_COMPRESS_NORMAL) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 3; - } - + elem_size = 4; } break; case RS::ARRAY_TANGENT: { - if (p_format & ARRAY_COMPRESS_TANGENT) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 4; - } - + elem_size = 4; } break; case RS::ARRAY_COLOR: { - if (p_format & ARRAY_COMPRESS_COLOR) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 4; - } + elem_size = 8; } break; case RS::ARRAY_TEX_UV: { - if (p_format & ARRAY_COMPRESS_TEX_UV) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 2; - } + elem_size = 8; } break; case RS::ARRAY_TEX_UV2: { - if (p_format & ARRAY_COMPRESS_TEX_UV2) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 2; - } + elem_size = 8; } break; + case RS::ARRAY_CUSTOM0: + case RS::ARRAY_CUSTOM1: + case RS::ARRAY_CUSTOM2: + case RS::ARRAY_CUSTOM3: { + uint32_t format = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + (ARRAY_FORMAT_CUSTOM_BITS * (i - ARRAY_CUSTOM0)))) & ARRAY_FORMAT_CUSTOM_MASK; + switch (format) { + case ARRAY_CUSTOM_RGBA8_UNORM: { + elem_size = 4; + } break; + case ARRAY_CUSTOM_RGBA8_SNORM: { + elem_size = 4; + } break; + case ARRAY_CUSTOM_RG_HALF: { + elem_size = 4; + } break; + case ARRAY_CUSTOM_RGBA_HALF: { + elem_size = 8; + } break; + case ARRAY_CUSTOM_R_FLOAT: { + elem_size = 4; + } break; + case ARRAY_CUSTOM_RG_FLOAT: { + elem_size = 8; + } break; + case ARRAY_CUSTOM_RGB_FLOAT: { + elem_size = 12; + } break; + case ARRAY_CUSTOM_RGBA_FLOAT: { + elem_size = 16; + } break; + } + } break; case RS::ARRAY_WEIGHTS: { - elem_size = sizeof(uint16_t) * 4; + uint32_t bone_count = (p_format & ARRAY_FLAG_USE_8_BONE_WEIGHTS) ? 8 : 4; + elem_size = sizeof(uint16_t) * bone_count; } break; case RS::ARRAY_BONES: { - elem_size = sizeof(uint16_t) * 4; - + uint32_t bone_count = (p_format & ARRAY_FLAG_USE_8_BONE_WEIGHTS) ? 8 : 4; + elem_size = sizeof(uint16_t) * bone_count; } break; case RS::ARRAY_INDEX: { if (p_index_len <= 0) { @@ -757,14 +823,13 @@ uint32_t RenderingServer::mesh_surface_make_offsets_from_format(uint32_t p_forma continue; } default: { - ERR_FAIL_V(0); + ERR_FAIL(); } } - r_offsets[i] = total_elem_size; - total_elem_size += elem_size; + r_offsets[i] = (*size_accum); + (*size_accum) += elem_size; } - return total_elem_size; } Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surface_data, PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, const Dictionary &p_lods, uint32_t p_compress_format) { @@ -785,20 +850,20 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa format |= (1 << i); if (i == RS::ARRAY_VERTEX) { - Variant var = p_arrays[i]; - switch (var.get_type()) { + switch (p_arrays[i].get_type()) { case Variant::PACKED_VECTOR2_ARRAY: { - Vector<Vector2> v2 = var; + Vector<Vector2> v2 = p_arrays[i]; + array_len = v2.size(); } break; case Variant::PACKED_VECTOR3_ARRAY: { - Vector<Vector3> v3 = var; + Vector<Vector3> v3 = p_arrays[i]; + array_len = v3.size(); } break; default: { - Array v = var; + ERR_FAIL_V(ERR_INVALID_DATA); } break; } - array_len = PackedVector3Array(p_arrays[i]).size(); ERR_FAIL_COND_V(array_len == 0, ERR_INVALID_DATA); } else if (i == RS::ARRAY_INDEX) { index_array_len = PackedInt32Array(p_arrays[i]).size(); @@ -824,117 +889,28 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa uint32_t offsets[RS::ARRAY_MAX]; - int total_elem_size = 0; + uint32_t vertex_element_size; + uint32_t attrib_element_size; + uint32_t skin_element_size; - for (int i = 0; i < RS::ARRAY_MAX; i++) { - offsets[i] = 0; //reset - - if (!(format & (1 << i))) { // no array - continue; - } - - int elem_size = 0; - - switch (i) { - case RS::ARRAY_VERTEX: { - Variant arr = p_arrays[0]; - if (arr.get_type() == Variant::PACKED_VECTOR2_ARRAY) { - elem_size = 2; - p_compress_format |= ARRAY_FLAG_USE_2D_VERTICES; - } else if (arr.get_type() == Variant::PACKED_VECTOR3_ARRAY) { - p_compress_format &= ~ARRAY_FLAG_USE_2D_VERTICES; - elem_size = 3; - } else { - elem_size = (p_compress_format & ARRAY_FLAG_USE_2D_VERTICES) ? 2 : 3; - } - - { - elem_size *= sizeof(float); - } - - } break; - case RS::ARRAY_NORMAL: { - if (p_compress_format & ARRAY_COMPRESS_NORMAL) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 3; - } - - } break; - - case RS::ARRAY_TANGENT: { - if (p_compress_format & ARRAY_COMPRESS_TANGENT) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 4; - } - - } break; - case RS::ARRAY_COLOR: { - if (p_compress_format & ARRAY_COMPRESS_COLOR) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 4; - } - } break; - case RS::ARRAY_TEX_UV: { - if (p_compress_format & ARRAY_COMPRESS_TEX_UV) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 2; - } - - } break; - - case RS::ARRAY_TEX_UV2: { - if (p_compress_format & ARRAY_COMPRESS_TEX_UV2) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 2; - } - - } break; - case RS::ARRAY_WEIGHTS: { - elem_size = sizeof(uint16_t) * 4; - - } break; - case RS::ARRAY_BONES: { - elem_size = sizeof(uint16_t) * 4; - - } break; - case RS::ARRAY_INDEX: { - if (index_array_len <= 0) { - ERR_PRINT("index_array_len==NO_INDEX_ARRAY"); - break; - } - /* determine whether using 16 or 32 bits indices */ - if (array_len >= (1 << 16)) { - elem_size = 4; - - } else { - elem_size = 2; - } - offsets[i] = elem_size; - continue; - } - default: { - ERR_FAIL_V(ERR_BUG); - } - } - - offsets[i] = total_elem_size; - total_elem_size += elem_size; - } + mesh_surface_make_offsets_from_format(format, array_len, index_array_len, offsets, vertex_element_size, attrib_element_size, skin_element_size); uint32_t mask = (1 << ARRAY_MAX) - 1; format |= (~mask) & p_compress_format; //make the full format - int array_size = total_elem_size * array_len; + int vertex_array_size = vertex_element_size * array_len; + int attrib_array_size = attrib_element_size * array_len; + int skin_array_size = skin_element_size * array_len; + int index_array_size = offsets[RS::ARRAY_INDEX] * index_array_len; Vector<uint8_t> vertex_array; - vertex_array.resize(array_size); + vertex_array.resize(vertex_array_size); - int index_array_size = offsets[RS::ARRAY_INDEX] * index_array_len; + Vector<uint8_t> attrib_array; + attrib_array.resize(attrib_array_size); + + Vector<uint8_t> skin_array; + skin_array.resize(skin_array_size); Vector<uint8_t> index_array; index_array.resize(index_array_size); @@ -942,22 +918,29 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa AABB aabb; Vector<AABB> bone_aabb; - Error err = _surface_set_data(p_arrays, format, offsets, total_elem_size, vertex_array, array_len, index_array, index_array_len, aabb, bone_aabb); + Error err = _surface_set_data(p_arrays, format, offsets, vertex_element_size, attrib_element_size, skin_element_size, vertex_array, attrib_array, skin_array, array_len, index_array, index_array_len, aabb, bone_aabb); ERR_FAIL_COND_V_MSG(err != OK, ERR_INVALID_DATA, "Invalid array format for surface."); - Vector<Vector<uint8_t>> blend_shape_data; - - for (int i = 0; i < p_blend_shapes.size(); i++) { - Vector<uint8_t> vertex_array_shape; - vertex_array_shape.resize(array_size); - Vector<uint8_t> noindex; + Vector<uint8_t> blend_shape_data; + uint32_t blend_shape_count = 0; - AABB laabb; - Error err2 = _surface_set_data(p_blend_shapes[i], format & ~ARRAY_FORMAT_INDEX, offsets, total_elem_size, vertex_array_shape, array_len, noindex, 0, laabb, bone_aabb); - aabb.merge_with(laabb); - ERR_FAIL_COND_V_MSG(err2 != OK, ERR_INVALID_DATA, "Invalid blend shape array format for surface."); - - blend_shape_data.push_back(vertex_array_shape); + if (p_blend_shapes.size()) { + uint32_t bs_format = format & RS::ARRAY_FORMAT_BLEND_SHAPE_MASK; + for (int i = 0; i < p_blend_shapes.size(); i++) { + Vector<uint8_t> vertex_array_shape; + vertex_array_shape.resize(vertex_array_size); + Vector<uint8_t> noindex; + Vector<uint8_t> noattrib; + Vector<uint8_t> noskin; + + AABB laabb; + Error err2 = _surface_set_data(p_blend_shapes[i], bs_format, offsets, vertex_element_size, 0, 0, vertex_array_shape, noattrib, noskin, array_len, noindex, 0, laabb, bone_aabb); + aabb.merge_with(laabb); + ERR_FAIL_COND_V_MSG(err2 != OK, ERR_INVALID_DATA, "Invalid blend shape array format for surface."); + + blend_shape_data.append_array(vertex_array_shape); + blend_shape_count++; + } } Vector<SurfaceData::LOD> lods; if (index_array_len) { @@ -1004,10 +987,13 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa surface_data.primitive = p_primitive; surface_data.aabb = aabb; surface_data.vertex_data = vertex_array; + surface_data.attribute_data = attrib_array; + surface_data.skin_data = skin_array; surface_data.vertex_count = array_len; surface_data.index_data = index_array; surface_data.index_count = index_array_len; - surface_data.blend_shapes = blend_shape_data; + surface_data.blend_shape_count = blend_shape_count; + surface_data.blend_shape_data = blend_shape_data; surface_data.bone_aabbs = bone_aabb; surface_data.lods = lods; @@ -1023,110 +1009,20 @@ void RenderingServer::mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_p mesh_add_surface(p_mesh, sd); } -Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t> p_vertex_data, int p_vertex_len, Vector<uint8_t> p_index_data, int p_index_len) const { - uint32_t offsets[ARRAY_MAX]; - - int total_elem_size = 0; - - for (int i = 0; i < RS::ARRAY_MAX; i++) { - offsets[i] = 0; //reset - - if (!(p_format & (1 << i))) { // no array - continue; - } - - int elem_size = 0; - - switch (i) { - case RS::ARRAY_VERTEX: { - if (p_format & ARRAY_FLAG_USE_2D_VERTICES) { - elem_size = 2; - } else { - elem_size = 3; - } - - { - elem_size *= sizeof(float); - } - - } break; - case RS::ARRAY_NORMAL: { - if (p_format & ARRAY_COMPRESS_NORMAL) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 3; - } - - } break; - - case RS::ARRAY_TANGENT: { - if (p_format & ARRAY_COMPRESS_TANGENT) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 4; - } - - } break; - case RS::ARRAY_COLOR: { - if (p_format & ARRAY_COMPRESS_COLOR) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 4; - } - } break; - case RS::ARRAY_TEX_UV: { - if (p_format & ARRAY_COMPRESS_TEX_UV) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 2; - } - - } break; - - case RS::ARRAY_TEX_UV2: { - if (p_format & ARRAY_COMPRESS_TEX_UV2) { - elem_size = sizeof(uint32_t); - } else { - elem_size = sizeof(float) * 2; - } - - } break; - case RS::ARRAY_WEIGHTS: { - elem_size = sizeof(uint16_t) * 4; - - } break; - case RS::ARRAY_BONES: { - elem_size = sizeof(uint16_t) * 4; - - } break; - case RS::ARRAY_INDEX: { - if (p_index_len <= 0) { - ERR_PRINT("index_array_len==NO_INDEX_ARRAY"); - break; - } - /* determine whether using 16 or 32 bits indices */ - if (p_vertex_len >= (1 << 16)) { - elem_size = 4; - - } else { - elem_size = 2; - } - offsets[i] = elem_size; - continue; - } - default: { - ERR_FAIL_V(Array()); - } - } +Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t> p_vertex_data, Vector<uint8_t> p_attrib_data, Vector<uint8_t> p_skin_data, int p_vertex_len, Vector<uint8_t> p_index_data, int p_index_len) const { + uint32_t offsets[RS::ARRAY_MAX]; - offsets[i] = total_elem_size; - total_elem_size += elem_size; - } + uint32_t vertex_elem_size; + uint32_t attrib_elem_size; + uint32_t skin_elem_size; + mesh_surface_make_offsets_from_format(p_format, p_vertex_len, p_index_len, offsets, vertex_elem_size, attrib_elem_size, skin_elem_size); Array ret; ret.resize(RS::ARRAY_MAX); const uint8_t *r = p_vertex_data.ptr(); + const uint8_t *ar = p_attrib_data.ptr(); + const uint8_t *sr = p_skin_data.ptr(); for (int i = 0; i < RS::ARRAY_MAX; i++) { if (!(p_format & (1 << i))) { @@ -1143,7 +1039,7 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t Vector2 *w = arr_2d.ptrw(); for (int j = 0; j < p_vertex_len; j++) { - const float *v = (const float *)&r[j * total_elem_size + offsets[i]]; + const float *v = (const float *)&r[j * vertex_elem_size + offsets[i]]; w[j] = Vector2(v[0], v[1]); } } @@ -1157,7 +1053,7 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t Vector3 *w = arr_3d.ptrw(); for (int j = 0; j < p_vertex_len; j++) { - const float *v = (const float *)&r[j * total_elem_size + offsets[i]]; + const float *v = (const float *)&r[j * vertex_elem_size + offsets[i]]; w[j] = Vector3(v[0], v[1], v[2]); } } @@ -1170,21 +1066,11 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t Vector<Vector3> arr; arr.resize(p_vertex_len); - if (p_format & ARRAY_COMPRESS_NORMAL) { - Vector3 *w = arr.ptrw(); - const float multiplier = 1.f / 127.f; + Vector3 *w = arr.ptrw(); - for (int j = 0; j < p_vertex_len; j++) { - const int8_t *v = (const int8_t *)&r[j * total_elem_size + offsets[i]]; - w[j] = Vector3(float(v[0]) * multiplier, float(v[1]) * multiplier, float(v[2]) * multiplier); - } - } else { - Vector3 *w = arr.ptrw(); - - for (int j = 0; j < p_vertex_len; j++) { - const float *v = (const float *)&r[j * total_elem_size + offsets[i]]; - w[j] = Vector3(v[0], v[1], v[2]); - } + for (int j = 0; j < p_vertex_len; j++) { + const uint32_t v = *(const uint32_t *)&r[j * vertex_elem_size + offsets[i]]; + w[j] = Vector3((v & 0x3FF) / 1023.0, ((v >> 10) & 0x3FF) / 1023.0, ((v >> 20) & 0x3FF) / 1023.0) * Vector3(2, 2, 2) - Vector3(1, 1, 1); } ret[i] = arr; @@ -1194,24 +1080,16 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t case RS::ARRAY_TANGENT: { Vector<float> arr; arr.resize(p_vertex_len * 4); - if (p_format & ARRAY_COMPRESS_TANGENT) { - float *w = arr.ptrw(); - for (int j = 0; j < p_vertex_len; j++) { - const int8_t *v = (const int8_t *)&r[j * total_elem_size + offsets[i]]; - for (int k = 0; k < 4; k++) { - w[j * 4 + k] = float(v[k] / 127.0); - } - } - } else { - float *w = arr.ptrw(); + float *w = arr.ptrw(); - for (int j = 0; j < p_vertex_len; j++) { - const float *v = (const float *)&r[j * total_elem_size + offsets[i]]; - for (int k = 0; k < 4; k++) { - w[j * 4 + k] = v[k]; - } - } + for (int j = 0; j < p_vertex_len; j++) { + const uint32_t v = *(const uint32_t *)&r[j * vertex_elem_size + offsets[i]]; + + w[j * 4 + 0] = ((v & 0x3FF) / 1023.0) * 2.0 - 1.0; + w[j * 4 + 1] = (((v >> 10) & 0x3FF) / 1023.0) * 2.0 - 1.0; + w[j * 4 + 2] = (((v >> 20) & 0x3FF) / 1023.0) * 2.0 - 1.0; + w[j * 4 + 3] = ((v >> 30) / 3.0) * 2.0 - 1.0; } ret[i] = arr; @@ -1221,20 +1099,11 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t Vector<Color> arr; arr.resize(p_vertex_len); - if (p_format & ARRAY_COMPRESS_COLOR) { - Color *w = arr.ptrw(); + Color *w = arr.ptrw(); - for (int j = 0; j < p_vertex_len; j++) { - const uint8_t *v = (const uint8_t *)&r[j * total_elem_size + offsets[i]]; - w[j] = Color(float(v[0] / 255.0), float(v[1] / 255.0), float(v[2] / 255.0), float(v[3] / 255.0)); - } - } else { - Color *w = arr.ptrw(); - - for (int j = 0; j < p_vertex_len; j++) { - const float *v = (const float *)&r[j * total_elem_size + offsets[i]]; - w[j] = Color(v[0], v[1], v[2], v[3]); - } + for (int32_t j = 0; j < p_vertex_len; j++) { + const uint16_t *v = (const uint16_t *)&ar[j * attrib_elem_size + offsets[i]]; + w[j] = Color(Math::half_to_float(v[0]), Math::half_to_float(v[1]), Math::half_to_float(v[2]), Math::half_to_float(v[3])); } ret[i] = arr; @@ -1243,20 +1112,11 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t Vector<Vector2> arr; arr.resize(p_vertex_len); - if (p_format & ARRAY_COMPRESS_TEX_UV) { - Vector2 *w = arr.ptrw(); - - for (int j = 0; j < p_vertex_len; j++) { - const uint16_t *v = (const uint16_t *)&r[j * total_elem_size + offsets[i]]; - w[j] = Vector2(Math::halfptr_to_float(&v[0]), Math::halfptr_to_float(&v[1])); - } - } else { - Vector2 *w = arr.ptrw(); + Vector2 *w = arr.ptrw(); - for (int j = 0; j < p_vertex_len; j++) { - const float *v = (const float *)&r[j * total_elem_size + offsets[i]]; - w[j] = Vector2(v[0], v[1]); - } + for (int j = 0; j < p_vertex_len; j++) { + const float *v = (const float *)&ar[j * attrib_elem_size + offsets[i]]; + w[j] = Vector2(v[0], v[1]); } ret[i] = arr; @@ -1266,35 +1126,74 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t Vector<Vector2> arr; arr.resize(p_vertex_len); - if (p_format & ARRAY_COMPRESS_TEX_UV2) { - Vector2 *w = arr.ptrw(); - - for (int j = 0; j < p_vertex_len; j++) { - const uint16_t *v = (const uint16_t *)&r[j * total_elem_size + offsets[i]]; - w[j] = Vector2(Math::halfptr_to_float(&v[0]), Math::halfptr_to_float(&v[1])); - } - } else { - Vector2 *w = arr.ptrw(); + Vector2 *w = arr.ptrw(); - for (int j = 0; j < p_vertex_len; j++) { - const float *v = (const float *)&r[j * total_elem_size + offsets[i]]; - w[j] = Vector2(v[0], v[1]); - } + for (int j = 0; j < p_vertex_len; j++) { + const float *v = (const float *)&ar[j * attrib_elem_size + offsets[i]]; + w[j] = Vector2(v[0], v[1]); } ret[i] = arr; } break; + case RS::ARRAY_CUSTOM0: + case RS::ARRAY_CUSTOM1: + case RS::ARRAY_CUSTOM2: + case RS::ARRAY_CUSTOM3: { + uint32_t type = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * (RS::ARRAY_CUSTOM0 - i))) & ARRAY_FORMAT_CUSTOM_MASK; + switch (type) { + case ARRAY_CUSTOM_RGBA8_UNORM: + case ARRAY_CUSTOM_RGBA8_SNORM: + case ARRAY_CUSTOM_RG_HALF: + case ARRAY_CUSTOM_RGBA_HALF: { + //size 4 + int s = type == ARRAY_CUSTOM_RGBA_HALF ? 8 : 4; + Vector<uint8_t> arr; + arr.resize(p_vertex_len * s); + + uint8_t *w = arr.ptrw(); + + for (int j = 0; j < p_vertex_len; j++) { + const uint8_t *v = (const uint8_t *)&ar[j * attrib_elem_size + offsets[i]]; + copymem(&w[j * s], v, s); + } + + ret[i] = arr; + + } break; + case ARRAY_CUSTOM_R_FLOAT: + case ARRAY_CUSTOM_RG_FLOAT: + case ARRAY_CUSTOM_RGB_FLOAT: + case ARRAY_CUSTOM_RGBA_FLOAT: { + uint32_t s = type - ARRAY_CUSTOM_R_FLOAT + 1; + + Vector<float> arr; + float *w = arr.ptrw(); + + for (int j = 0; j < p_vertex_len; j++) { + const float *v = (const float *)&ar[j * attrib_elem_size + offsets[i]]; + copymem(&w[j * s], v, s * sizeof(float)); + } + ret[i] = arr; + + } break; + default: { + } + } + + } break; case RS::ARRAY_WEIGHTS: { + uint32_t bone_count = (p_format & ARRAY_FLAG_USE_8_BONE_WEIGHTS) ? 8 : 4; + Vector<float> arr; - arr.resize(p_vertex_len * 4); + arr.resize(p_vertex_len * bone_count); { float *w = arr.ptrw(); for (int j = 0; j < p_vertex_len; j++) { - const uint16_t *v = (const uint16_t *)&r[j * total_elem_size + offsets[i]]; - for (int k = 0; k < 4; k++) { - w[j * 4 + k] = float(v[k] / 65535.0); + const uint16_t *v = (const uint16_t *)&sr[j * skin_elem_size + offsets[i]]; + for (uint32_t k = 0; k < bone_count; k++) { + w[j * bone_count + k] = float(v[k] / 65535.0); } } } @@ -1303,15 +1202,17 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t } break; case RS::ARRAY_BONES: { + uint32_t bone_count = (p_format & ARRAY_FLAG_USE_8_BONE_WEIGHTS) ? 8 : 4; + Vector<int> arr; - arr.resize(p_vertex_len * 4); + arr.resize(p_vertex_len * bone_count); int *w = arr.ptrw(); for (int j = 0; j < p_vertex_len; j++) { - const uint16_t *v = (const uint16_t *)&r[j * total_elem_size + offsets[i]]; - for (int k = 0; k < 4; k++) { - w[j * 4 + k] = v[k]; + const uint16_t *v = (const uint16_t *)&sr[j * skin_elem_size + offsets[i]]; + for (uint32_t k = 0; k < bone_count; k++) { + w[j * bone_count + k] = v[k]; } } @@ -1394,20 +1295,30 @@ Array RenderingServer::mesh_surface_get_blend_shape_arrays(RID p_mesh, int p_sur SurfaceData sd = mesh_get_surface(p_mesh, p_surface); ERR_FAIL_COND_V(sd.vertex_count == 0, Array()); - Vector<Vector<uint8_t>> blend_shape_data = sd.blend_shapes; + Vector<uint8_t> blend_shape_data = sd.blend_shape_data; if (blend_shape_data.size() > 0) { - int vertex_len = sd.vertex_count; + uint32_t bs_offsets[RS::ARRAY_MAX]; + uint32_t bs_format = (sd.format & RS::ARRAY_FORMAT_BLEND_SHAPE_MASK); + uint32_t vertex_elem_size; + uint32_t attrib_elem_size; + uint32_t skin_elem_size; + + mesh_surface_make_offsets_from_format(bs_format, sd.vertex_count, 0, bs_offsets, vertex_elem_size, attrib_elem_size, skin_elem_size); - Vector<uint8_t> index_data = sd.index_data; - int index_len = sd.index_count; + int divisor = vertex_elem_size * sd.vertex_count; + ERR_FAIL_COND_V((blend_shape_data.size() % divisor) != 0, Array()); - uint32_t format = sd.format; + uint32_t blend_shape_count = blend_shape_data.size() / divisor; + + ERR_FAIL_COND_V(blend_shape_count != sd.blend_shape_count, Array()); Array blend_shape_array; - blend_shape_array.resize(blend_shape_data.size()); - for (int i = 0; i < blend_shape_data.size(); i++) { - blend_shape_array.set(i, _get_array_from_surface(format, blend_shape_data[i], vertex_len, index_data, index_len)); + blend_shape_array.resize(blend_shape_count); + for (uint32_t i = 0; i < blend_shape_count; i++) { + Vector<uint8_t> bs_data = blend_shape_data.subarray(i * divisor, (i + 1) * divisor - 1); + Vector<uint8_t> unused; + blend_shape_array.set(i, _get_array_from_surface(bs_format, bs_data, unused, unused, sd.vertex_count, unused, 0)); } return blend_shape_array; @@ -1418,6 +1329,8 @@ Array RenderingServer::mesh_surface_get_blend_shape_arrays(RID p_mesh, int p_sur Array RenderingServer::mesh_create_arrays_from_surface_data(const SurfaceData &p_data) const { Vector<uint8_t> vertex_data = p_data.vertex_data; + Vector<uint8_t> attrib_data = p_data.attribute_data; + Vector<uint8_t> skin_data = p_data.skin_data; ERR_FAIL_COND_V(vertex_data.size() == 0, Array()); int vertex_len = p_data.vertex_count; @@ -1427,7 +1340,7 @@ Array RenderingServer::mesh_create_arrays_from_surface_data(const SurfaceData &p uint32_t format = p_data.format; - return _get_array_from_surface(format, vertex_data, vertex_len, index_data, index_len); + return _get_array_from_surface(format, vertex_data, attrib_data, skin_data, vertex_len, index_data, index_len); } #if 0 Array RenderingServer::_mesh_surface_get_skeleton_aabb_bind(RID p_mesh, int p_surface) const { @@ -1540,9 +1453,11 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("material_set_next_pass", "material", "next_material"), &RenderingServer::material_set_next_pass); ClassDB::bind_method(D_METHOD("mesh_create"), &RenderingServer::mesh_create); - ClassDB::bind_method(D_METHOD("mesh_surface_get_format_offset", "format", "vertex_len", "index_len", "array_index"), &RenderingServer::mesh_surface_get_format_offset); - ClassDB::bind_method(D_METHOD("mesh_surface_get_format_stride", "format", "vertex_len", "index_len"), &RenderingServer::mesh_surface_get_format_stride); - ClassDB::bind_method(D_METHOD("mesh_add_surface_from_arrays", "mesh", "primitive", "arrays", "blend_shapes", "lods", "compress_format"), &RenderingServer::mesh_add_surface_from_arrays, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(ARRAY_COMPRESS_DEFAULT)); + ClassDB::bind_method(D_METHOD("mesh_surface_get_format_offset", "format", "vertex_count", "array_index"), &RenderingServer::mesh_surface_get_format_offset); + ClassDB::bind_method(D_METHOD("mesh_surface_get_format_vertex_stride", "format", "vertex_count"), &RenderingServer::mesh_surface_get_format_vertex_stride); + ClassDB::bind_method(D_METHOD("mesh_surface_get_format_attribute_stride", "format", "vertex_count"), &RenderingServer::mesh_surface_get_format_attribute_stride); + ClassDB::bind_method(D_METHOD("mesh_surface_get_format_skin_stride", "format", "vertex_count"), &RenderingServer::mesh_surface_get_format_skin_stride); + //ClassDB::bind_method(D_METHOD("mesh_add_surface_from_arrays", "mesh", "primitive", "arrays", "blend_shapes", "lods", "compress_format"), &RenderingServer::mesh_add_surface_from_arrays, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(ARRAY_COMPRESS_DEFAULT)); ClassDB::bind_method(D_METHOD("mesh_get_blend_shape_count", "mesh"), &RenderingServer::mesh_get_blend_shape_count); ClassDB::bind_method(D_METHOD("mesh_set_blend_shape_mode", "mesh", "mode"), &RenderingServer::mesh_set_blend_shape_mode); ClassDB::bind_method(D_METHOD("mesh_get_blend_shape_mode", "mesh"), &RenderingServer::mesh_get_blend_shape_mode); @@ -1947,14 +1862,6 @@ void RenderingServer::_bind_methods() { BIND_ENUM_CONSTANT(ARRAY_FORMAT_WEIGHTS); BIND_ENUM_CONSTANT(ARRAY_FORMAT_INDEX); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_NORMAL); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_TANGENT); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_COLOR); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_TEX_UV); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_TEX_UV2); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_INDEX); - BIND_ENUM_CONSTANT(ARRAY_COMPRESS_DEFAULT); - BIND_ENUM_CONSTANT(ARRAY_FLAG_USE_2D_VERTICES); BIND_ENUM_CONSTANT(ARRAY_FLAG_USE_DYNAMIC_UPDATE); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 6102958aaa..c22cf3ace4 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -52,7 +52,7 @@ class RenderingServer : public Object { void _camera_set_orthogonal(RID p_camera, float p_size, float p_z_near, float p_z_far); void _canvas_item_add_style_box(RID p_item, const Rect2 &p_rect, const Rect2 &p_source, RID p_texture, const Vector<float> &p_margins, const Color &p_modulate = Color(1, 1, 1)); - Array _get_array_from_surface(uint32_t p_format, Vector<uint8_t> p_vertex_data, int p_vertex_len, Vector<uint8_t> p_index_data, int p_index_len) const; + Array _get_array_from_surface(uint32_t p_format, Vector<uint8_t> p_vertex_data, Vector<uint8_t> p_attrib_data, Vector<uint8_t> p_skin_data, int p_vertex_len, Vector<uint8_t> p_index_data, int p_index_len) const; protected: RID _make_test_cube(); @@ -61,7 +61,7 @@ protected: RID white_texture; RID test_material; - Error _surface_set_data(Array p_arrays, uint32_t p_format, uint32_t *p_offsets, uint32_t p_stride, Vector<uint8_t> &r_vertex_array, int p_vertex_array_len, Vector<uint8_t> &r_index_array, int p_index_array_len, AABB &r_aabb, Vector<AABB> &r_bone_aabb); + Error _surface_set_data(Array p_arrays, uint32_t p_format, uint32_t *p_offsets, uint32_t p_vertex_stride, uint32_t p_attrib_stride, uint32_t p_skin_stride, Vector<uint8_t> &r_vertex_array, Vector<uint8_t> &r_attrib_array, Vector<uint8_t> &r_skin_array, int p_vertex_array_len, Vector<uint8_t> &r_index_array, int p_index_array_len, AABB &r_aabb, Vector<AABB> &r_bone_aabb); static RenderingServer *(*create_func)(); static void _bind_methods(); @@ -199,16 +199,36 @@ public: /* MESH API */ enum ArrayType { - ARRAY_VERTEX = 0, - ARRAY_NORMAL = 1, - ARRAY_TANGENT = 2, - ARRAY_COLOR = 3, - ARRAY_TEX_UV = 4, - ARRAY_TEX_UV2 = 5, - ARRAY_BONES = 6, - ARRAY_WEIGHTS = 7, - ARRAY_INDEX = 8, - ARRAY_MAX = 9 + ARRAY_VERTEX = 0, // RG32F or RGB32F (depending on 2D bit) + ARRAY_NORMAL = 1, // A2B10G10R10 + ARRAY_TANGENT = 2, // A2B10G10R10, A flips sign of binormal + ARRAY_COLOR = 3, // RGBA16F + ARRAY_TEX_UV = 4, // RG32F + ARRAY_TEX_UV2 = 5, // RG32F + ARRAY_CUSTOM0 = 6, // depends on ArrayCustomFormat + ARRAY_CUSTOM1 = 7, + ARRAY_CUSTOM2 = 8, + ARRAY_CUSTOM3 = 9, + ARRAY_BONES = 10, // RGBA16UI (x2 if 8 weights) + ARRAY_WEIGHTS = 11, // RGBA16UNORM (x2 if 8 weights) + ARRAY_INDEX = 12, // 16 or 32 bits depending on length > 0xFFFF + ARRAY_MAX = 13 + }; + + enum { + ARRAY_CUSTOM_COUNT = ARRAY_BONES - ARRAY_CUSTOM0 + }; + + enum ArrayCustomFormat { + ARRAY_CUSTOM_RGBA8_UNORM, + ARRAY_CUSTOM_RGBA8_SNORM, + ARRAY_CUSTOM_RG_HALF, + ARRAY_CUSTOM_RGBA_HALF, + ARRAY_CUSTOM_R_FLOAT, + ARRAY_CUSTOM_RG_FLOAT, + ARRAY_CUSTOM_RGB_FLOAT, + ARRAY_CUSTOM_RGBA_FLOAT, + ARRAY_CUSTOM_MAX }; enum ArrayFormat { @@ -219,21 +239,29 @@ public: ARRAY_FORMAT_COLOR = 1 << ARRAY_COLOR, ARRAY_FORMAT_TEX_UV = 1 << ARRAY_TEX_UV, ARRAY_FORMAT_TEX_UV2 = 1 << ARRAY_TEX_UV2, + ARRAY_FORMAT_CUSTOM0 = 1 << ARRAY_CUSTOM0, + ARRAY_FORMAT_CUSTOM1 = 1 << ARRAY_CUSTOM1, + ARRAY_FORMAT_CUSTOM2 = 1 << ARRAY_CUSTOM2, + ARRAY_FORMAT_CUSTOM3 = 1 << ARRAY_CUSTOM3, ARRAY_FORMAT_BONES = 1 << ARRAY_BONES, ARRAY_FORMAT_WEIGHTS = 1 << ARRAY_WEIGHTS, ARRAY_FORMAT_INDEX = 1 << ARRAY_INDEX, - ARRAY_COMPRESS_BASE = (ARRAY_INDEX + 1), - ARRAY_COMPRESS_NORMAL = 1 << (ARRAY_NORMAL + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_TANGENT = 1 << (ARRAY_TANGENT + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_COLOR = 1 << (ARRAY_COLOR + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_TEX_UV = 1 << (ARRAY_TEX_UV + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_TEX_UV2 = 1 << (ARRAY_TEX_UV2 + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_INDEX = 1 << (ARRAY_INDEX + ARRAY_COMPRESS_BASE), - ARRAY_COMPRESS_DEFAULT = ARRAY_COMPRESS_NORMAL | ARRAY_COMPRESS_TANGENT | ARRAY_COMPRESS_COLOR | ARRAY_COMPRESS_TEX_UV | ARRAY_COMPRESS_TEX_UV2, + ARRAY_FORMAT_BLEND_SHAPE_MASK = ~(ARRAY_FORMAT_COLOR | ARRAY_FORMAT_TEX_UV | ARRAY_FORMAT_TEX_UV2 | ARRAY_FORMAT_BONES | ARRAY_FORMAT_WEIGHTS | ARRAY_FORMAT_CUSTOM0 | ARRAY_FORMAT_CUSTOM1 | ARRAY_FORMAT_CUSTOM2 | ARRAY_FORMAT_CUSTOM3 | ARRAY_FORMAT_INDEX), - ARRAY_FLAG_USE_2D_VERTICES = ARRAY_COMPRESS_INDEX << 1, - ARRAY_FLAG_USE_DYNAMIC_UPDATE = ARRAY_COMPRESS_INDEX << 3, + ARRAY_FORMAT_CUSTOM_BASE = (ARRAY_INDEX + 1), + ARRAY_FORMAT_CUSTOM_BITS = 3, + ARRAY_FORMAT_CUSTOM0_SHIFT = (ARRAY_FORMAT_CUSTOM_BASE + 0), + ARRAY_FORMAT_CUSTOM1_SHIFT = (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS), + ARRAY_FORMAT_CUSTOM2_SHIFT = (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * 2), + ARRAY_FORMAT_CUSTOM3_SHIFT = (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * 3), + + ARRAY_FORMAT_CUSTOM_MASK = 0x7, + ARRAY_COMPRESS_FLAGS_BASE = (ARRAY_INDEX + 1 + 12), + + ARRAY_FLAG_USE_2D_VERTICES = 1 << (ARRAY_COMPRESS_FLAGS_BASE + 0), + ARRAY_FLAG_USE_DYNAMIC_UPDATE = 1 << (ARRAY_COMPRESS_FLAGS_BASE + 1), + ARRAY_FLAG_USE_8_BONE_WEIGHTS = 1 << (ARRAY_COMPRESS_FLAGS_BASE + 2), }; enum PrimitiveType { @@ -249,11 +277,15 @@ public: PrimitiveType primitive = PRIMITIVE_MAX; uint32_t format = 0; - Vector<uint8_t> vertex_data; + Vector<uint8_t> vertex_data; // vertex, normal, tangent (change with skinning, blendshape) + Vector<uint8_t> attribute_data; // color,uv, uv2, custom0-3 + Vector<uint8_t> skin_data; // bone index, bone weight uint32_t vertex_count = 0; Vector<uint8_t> index_data; uint32_t index_count = 0; + uint32_t blend_shape_count = 0; + AABB aabb; struct LOD { float edge_length; @@ -262,7 +294,7 @@ public: Vector<LOD> lods; Vector<AABB> bone_aabbs; - Vector<Vector<uint8_t>> blend_shapes; + Vector<uint8_t> blend_shape_data; RID material; }; @@ -270,17 +302,20 @@ public: virtual RID mesh_create_from_surfaces(const Vector<SurfaceData> &p_surfaces) = 0; virtual RID mesh_create() = 0; - virtual uint32_t mesh_surface_get_format_offset(uint32_t p_format, int p_vertex_len, int p_index_len, int p_array_index) const; - virtual uint32_t mesh_surface_get_format_stride(uint32_t p_format, int p_vertex_len, int p_index_len) const; + virtual uint32_t mesh_surface_get_format_offset(uint32_t p_format, int p_vertex_len, int p_array_index) const; + virtual uint32_t mesh_surface_get_format_vertex_stride(uint32_t p_format, int p_vertex_len) const; + virtual uint32_t mesh_surface_get_format_attribute_stride(uint32_t p_format, int p_vertex_len) const; + virtual uint32_t mesh_surface_get_format_skin_stride(uint32_t p_format, int p_vertex_len) const; + /// Returns stride - virtual uint32_t mesh_surface_make_offsets_from_format(uint32_t p_format, int p_vertex_len, int p_index_len, uint32_t *r_offsets) const; - virtual Error mesh_create_surface_data_from_arrays(SurfaceData *r_surface_data, PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), uint32_t p_compress_format = ARRAY_COMPRESS_DEFAULT); + virtual void mesh_surface_make_offsets_from_format(uint32_t p_format, int p_vertex_len, int p_index_len, uint32_t *r_offsets, uint32_t &r_vertex_element_size, uint32_t &r_attrib_element_size, uint32_t &r_skin_element_size) const; + virtual Error mesh_create_surface_data_from_arrays(SurfaceData *r_surface_data, PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), uint32_t p_compress_format = 0); Array mesh_create_arrays_from_surface_data(const SurfaceData &p_data) const; Array mesh_surface_get_arrays(RID p_mesh, int p_surface) const; Array mesh_surface_get_blend_shape_arrays(RID p_mesh, int p_surface) const; Dictionary mesh_surface_get_lods(RID p_mesh, int p_surface) const; - virtual void mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), uint32_t p_compress_format = ARRAY_COMPRESS_DEFAULT); + virtual void mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), uint32_t p_compress_format = 0); virtual void mesh_add_surface(RID p_mesh, const SurfaceData &p_surface) = 0; virtual int mesh_get_blend_shape_count(RID p_mesh) const = 0; diff --git a/tests/test_crypto.h b/tests/test_crypto.h new file mode 100644 index 0000000000..9e219ceec9 --- /dev/null +++ b/tests/test_crypto.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* test_crypto.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 TEST_CRYPTO_H +#define TEST_CRYPTO_H + +#include "core/crypto/crypto.h" +#include "tests/test_macros.h" +#include <stdio.h> + +namespace TestCrypto { + +class _MockCrypto : public Crypto { + virtual PackedByteArray generate_random_bytes(int p_bytes) { return PackedByteArray(); } + virtual Ref<CryptoKey> generate_rsa(int p_bytes) { return nullptr; } + virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after) { return nullptr; } + + virtual Vector<uint8_t> sign(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Ref<CryptoKey> p_key) { return Vector<uint8_t>(); } + virtual bool verify(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Vector<uint8_t> p_signature, Ref<CryptoKey> p_key) { return false; } + virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_plaintext) { return Vector<uint8_t>(); } + virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_ciphertext) { return Vector<uint8_t>(); } + virtual PackedByteArray hmac_digest(HashingContext::HashType p_hash_type, PackedByteArray p_key, PackedByteArray p_msg) { return PackedByteArray(); } +}; + +PackedByteArray raw_to_pba(const uint8_t *arr, size_t len) { + PackedByteArray pba; + pba.resize(len); + for (size_t i = 0; i < len; i++) { + pba.set(i, arr[i]); + } + return pba; +} + +TEST_CASE("[Crypto] PackedByteArray constant time compare") { + const uint8_t hm1[] = { 144, 140, 176, 38, 88, 113, 101, 45, 71, 105, 10, 91, 248, 16, 117, 244, 189, 30, 238, 29, 219, 134, 82, 130, 212, 114, 161, 166, 188, 169, 200, 106 }; + const uint8_t hm2[] = { 80, 30, 144, 228, 108, 38, 188, 125, 150, 64, 165, 127, 221, 118, 144, 232, 45, 100, 15, 248, 193, 244, 245, 34, 116, 147, 132, 200, 110, 27, 38, 75 }; + PackedByteArray p1 = raw_to_pba(hm1, sizeof(hm1) / sizeof(hm1[0])); + PackedByteArray p2 = raw_to_pba(hm2, sizeof(hm2) / sizeof(hm2[0])); + _MockCrypto crypto; + bool equal = crypto.constant_time_compare(p1, p1); + CHECK(equal); + equal = crypto.constant_time_compare(p1, p2); + CHECK(!equal); +} +} // namespace TestCrypto + +#endif // TEST_CRYPTO_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 9d1e7da6f7..dd8c36e737 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -39,6 +39,7 @@ #include "test_color.h" #include "test_command_queue.h" #include "test_config_file.h" +#include "test_crypto.h" #include "test_curve.h" #include "test_expression.h" #include "test_gradient.h" diff --git a/thirdparty/README.md b/thirdparty/README.md index d011832210..e86d688ec1 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -671,7 +671,7 @@ File extracted from upstream release tarball: ## xatlas - Upstream: https://github.com/jpcy/xatlas -- Version: git (470576d3516f7e6d8b4554e7c941194a935969fd, 2020) +- Version: git (5571fc7ef0d06832947c0a935ccdcf083f7a9264, 2020) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/xatlas/xatlas.cpp b/thirdparty/xatlas/xatlas.cpp index 43aec33a9f..9f66ae0067 100644 --- a/thirdparty/xatlas/xatlas.cpp +++ b/thirdparty/xatlas/xatlas.cpp @@ -33,19 +33,25 @@ https://github.com/brandonpelfrey/Fast-BVH MIT License Copyright (c) 2012 Brandon Pelfrey */ -#include <atomic> -#include <condition_variable> -#include <mutex> -#include <thread> +#include "xatlas.h" +#ifndef XATLAS_C_API +#define XATLAS_C_API 0 +#endif +#if XATLAS_C_API +#include "xatlas_c.h" +#endif #include <assert.h> #include <float.h> // FLT_MAX #include <limits.h> #include <math.h> +#include <atomic> +#include <condition_variable> +#include <mutex> +#include <thread> #define __STDC_LIMIT_MACROS #include <stdint.h> #include <stdio.h> #include <string.h> -#include "xatlas.h" #ifndef XA_DEBUG #ifdef NDEBUG @@ -59,7 +65,7 @@ Copyright (c) 2012 Brandon Pelfrey #define XA_PROFILE 0 #endif #if XA_PROFILE -#include <time.h> +#include <chrono> #endif #ifndef XA_MULTITHREADED @@ -70,7 +76,10 @@ Copyright (c) 2012 Brandon Pelfrey #define XA_XSTR(x) XA_STR(x) #ifndef XA_ASSERT -#define XA_ASSERT(exp) if (!(exp)) { XA_PRINT_WARNING("\rASSERT: %s %s %d\n", XA_XSTR(exp), __FILE__, __LINE__); } +#define XA_ASSERT(exp) \ + if (!(exp)) { \ + XA_PRINT_WARNING("\rASSERT: %s %s %d\n", XA_XSTR(exp), __FILE__, __LINE__); \ + } #endif #ifndef XA_DEBUG_ASSERT @@ -78,13 +87,13 @@ Copyright (c) 2012 Brandon Pelfrey #endif #ifndef XA_PRINT -#define XA_PRINT(...) \ +#define XA_PRINT(...) \ if (xatlas::internal::s_print && xatlas::internal::s_printVerbose) \ xatlas::internal::s_print(__VA_ARGS__); #endif #ifndef XA_PRINT_WARNING -#define XA_PRINT_WARNING(...) \ +#define XA_PRINT_WARNING(...) \ if (xatlas::internal::s_print) \ xatlas::internal::s_print(__VA_ARGS__); #endif @@ -116,9 +125,9 @@ Copyright (c) 2012 Brandon Pelfrey #define XA_MERGE_CHARTS 1 #define XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION 0.5f #define XA_RECOMPUTE_CHARTS 1 -#define XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION 0 -#define XA_FIX_INTERNAL_BOUNDARY_LOOPS 1 -#define XA_PRINT_CHART_WARNINGS 0 +#define XA_CHECK_PARAM_WINDING 0 +#define XA_CHECK_PIECEWISE_CHART_QUALITY 0 +#define XA_CHECK_T_JUNCTIONS 0 #define XA_DEBUG_HEAP 0 #define XA_DEBUG_SINGLE_CHART 0 @@ -131,25 +140,19 @@ Copyright (c) 2012 Brandon Pelfrey #define XA_DEBUG_EXPORT_OBJ_CHART_GROUPS 0 #define XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS 0 #define XA_DEBUG_EXPORT_OBJ_CHARTS 0 -#define XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION 0 -#define XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR 0 +#define XA_DEBUG_EXPORT_OBJ_TJUNCTION 0 // XA_CHECK_T_JUNCTIONS must also be set #define XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION 0 #define XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION 0 #define XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS 0 -#define XA_DEBUG_EXPORT_OBJ (0 \ - || XA_DEBUG_EXPORT_OBJ_FACE_GROUPS \ - || XA_DEBUG_EXPORT_OBJ_CHART_GROUPS \ - || XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS \ - || XA_DEBUG_EXPORT_OBJ_CHARTS \ - || XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION \ - || XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR \ - || XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION \ - || XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION \ - || XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS) +#define XA_DEBUG_EXPORT_OBJ (0 || XA_DEBUG_EXPORT_OBJ_FACE_GROUPS || XA_DEBUG_EXPORT_OBJ_CHART_GROUPS || XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS || XA_DEBUG_EXPORT_OBJ_CHARTS || XA_DEBUG_EXPORT_OBJ_TJUNCTION || XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION || XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION || XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS) #ifdef _MSC_VER -#define XA_FOPEN(_file, _filename, _mode) { if (fopen_s(&_file, _filename, _mode) != 0) _file = NULL; } +#define XA_FOPEN(_file, _filename, _mode) \ + { \ + if (fopen_s(&_file, _filename, _mode) != 0) \ + _file = NULL; \ + } #define XA_SPRINTF(_buffer, _size, _format, ...) sprintf_s(_buffer, _size, _format, __VA_ARGS__) #else #define XA_FOPEN(_file, _filename, _mode) _file = fopen(_filename, _mode) @@ -165,74 +168,76 @@ static PrintFunc s_print = printf; static bool s_printVerbose = false; #if XA_PROFILE -#define XA_PROFILE_START(var) const clock_t var##Start = clock(); -#define XA_PROFILE_END(var) internal::s_profile.var += clock() - var##Start; -#define XA_PROFILE_PRINT_AND_RESET(label, var) XA_PRINT("%s%.2f seconds (%g ms)\n", label, internal::clockToSeconds(internal::s_profile.var), internal::clockToMs(internal::s_profile.var)); internal::s_profile.var = 0; +typedef uint64_t Duration; + +#define XA_PROFILE_START(var) const std::chrono::time_point<std::chrono::high_resolution_clock> var##Start = std::chrono::high_resolution_clock::now(); +#define XA_PROFILE_END(var) internal::s_profile.var += uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - var##Start).count()); +#define XA_PROFILE_PRINT_AND_RESET(label, var) \ + XA_PRINT("%s%.2f seconds (%g ms)\n", label, internal::durationToSeconds(internal::s_profile.var), internal::durationToMs(internal::s_profile.var)); \ + internal::s_profile.var = 0u; #define XA_PROFILE_ALLOC 0 -struct ProfileData -{ +struct ProfileData { #if XA_PROFILE_ALLOC - std::atomic<clock_t> alloc; + std::atomic<Duration> alloc; #endif - clock_t addMeshReal; - clock_t addMeshCopyData; - std::atomic<clock_t> addMeshThread; - std::atomic<clock_t> addMeshCreateColocals; - clock_t computeChartsReal; - std::atomic<clock_t> computeChartsThread; - std::atomic<clock_t> createFaceGroups; - std::atomic<clock_t> extractInvalidMeshGeometry; - std::atomic<clock_t> chartGroupComputeChartsReal; - std::atomic<clock_t> chartGroupComputeChartsThread; - std::atomic<clock_t> createChartGroupMesh; - std::atomic<clock_t> createChartGroupMeshColocals; - std::atomic<clock_t> createChartGroupMeshBoundaries; - std::atomic<clock_t> buildAtlas; - std::atomic<clock_t> buildAtlasInit; - std::atomic<clock_t> planarCharts; - std::atomic<clock_t> clusteredCharts; - std::atomic<clock_t> clusteredChartsPlaceSeeds; - std::atomic<clock_t> clusteredChartsPlaceSeedsBoundaryIntersection; - std::atomic<clock_t> clusteredChartsRelocateSeeds; - std::atomic<clock_t> clusteredChartsReset; - std::atomic<clock_t> clusteredChartsGrow; - std::atomic<clock_t> clusteredChartsGrowBoundaryIntersection; - std::atomic<clock_t> clusteredChartsMerge; - std::atomic<clock_t> clusteredChartsFillHoles; - std::atomic<clock_t> copyChartFaces; - clock_t parameterizeChartsReal; - std::atomic<clock_t> parameterizeChartsThread; - std::atomic<clock_t> createChartMesh; - std::atomic<clock_t> fixChartMeshTJunctions; - std::atomic<clock_t> closeChartMeshHoles; - std::atomic<clock_t> parameterizeChartsOrthogonal; - std::atomic<clock_t> parameterizeChartsLSCM; - std::atomic<clock_t> parameterizeChartsRecompute; - std::atomic<clock_t> parameterizeChartsPiecewise; - std::atomic<clock_t> parameterizeChartsPiecewiseBoundaryIntersection; - std::atomic<clock_t> parameterizeChartsEvaluateQuality; - clock_t packCharts; - clock_t packChartsAddCharts; - std::atomic<clock_t> packChartsAddChartsThread; - std::atomic<clock_t> packChartsAddChartsRestoreTexcoords; - clock_t packChartsRasterize; - clock_t packChartsDilate; - clock_t packChartsFindLocation; - clock_t packChartsBlit; - clock_t buildOutputMeshes; + std::chrono::time_point<std::chrono::high_resolution_clock> addMeshRealStart; + Duration addMeshReal; + Duration addMeshCopyData; + std::atomic<Duration> addMeshThread; + std::atomic<Duration> addMeshCreateColocals; + Duration computeChartsReal; + std::atomic<Duration> computeChartsThread; + std::atomic<Duration> createFaceGroups; + std::atomic<Duration> extractInvalidMeshGeometry; + std::atomic<Duration> chartGroupComputeChartsReal; + std::atomic<Duration> chartGroupComputeChartsThread; + std::atomic<Duration> createChartGroupMesh; + std::atomic<Duration> createChartGroupMeshColocals; + std::atomic<Duration> createChartGroupMeshBoundaries; + std::atomic<Duration> buildAtlas; + std::atomic<Duration> buildAtlasInit; + std::atomic<Duration> planarCharts; + std::atomic<Duration> originalUvCharts; + std::atomic<Duration> clusteredCharts; + std::atomic<Duration> clusteredChartsPlaceSeeds; + std::atomic<Duration> clusteredChartsPlaceSeedsBoundaryIntersection; + std::atomic<Duration> clusteredChartsRelocateSeeds; + std::atomic<Duration> clusteredChartsReset; + std::atomic<Duration> clusteredChartsGrow; + std::atomic<Duration> clusteredChartsGrowBoundaryIntersection; + std::atomic<Duration> clusteredChartsMerge; + std::atomic<Duration> clusteredChartsFillHoles; + std::atomic<Duration> copyChartFaces; + std::atomic<Duration> createChartMeshAndParameterizeReal; + std::atomic<Duration> createChartMeshAndParameterizeThread; + std::atomic<Duration> createChartMesh; + std::atomic<Duration> parameterizeCharts; + std::atomic<Duration> parameterizeChartsOrthogonal; + std::atomic<Duration> parameterizeChartsLSCM; + std::atomic<Duration> parameterizeChartsRecompute; + std::atomic<Duration> parameterizeChartsPiecewise; + std::atomic<Duration> parameterizeChartsPiecewiseBoundaryIntersection; + std::atomic<Duration> parameterizeChartsEvaluateQuality; + Duration packCharts; + Duration packChartsAddCharts; + std::atomic<Duration> packChartsAddChartsThread; + std::atomic<Duration> packChartsAddChartsRestoreTexcoords; + Duration packChartsRasterize; + Duration packChartsDilate; + Duration packChartsFindLocation; + Duration packChartsBlit; + Duration buildOutputMeshes; }; static ProfileData s_profile; -static double clockToMs(clock_t c) -{ - return c * 1000.0 / CLOCKS_PER_SEC; +static double durationToMs(Duration c) { + return (double)c * 0.001; } -static double clockToSeconds(clock_t c) -{ - return c / (double)CLOCKS_PER_SEC; +static double durationToSeconds(Duration c) { + return (double)c * 0.000001; } #else #define XA_PROFILE_START(var) @@ -241,10 +246,8 @@ static double clockToSeconds(clock_t c) #define XA_PROFILE_ALLOC 0 #endif -struct MemTag -{ - enum - { +struct MemTag { + enum { Default, BitImage, BVH, @@ -267,8 +270,7 @@ struct MemTag }; #if XA_DEBUG_HEAP -struct AllocHeader -{ +struct AllocHeader { size_t size; const char *file; int line; @@ -281,11 +283,10 @@ struct AllocHeader static std::mutex s_allocMutex; static AllocHeader *s_allocRoot = nullptr; static size_t s_allocTotalCount = 0, s_allocTotalSize = 0, s_allocPeakSize = 0, s_allocCount[MemTag::Count] = { 0 }, s_allocTotalTagSize[MemTag::Count] = { 0 }, s_allocPeakTagSize[MemTag::Count] = { 0 }; -static uint32_t s_allocId =0 ; +static uint32_t s_allocId = 0; static constexpr uint32_t kAllocRedzone = 0x12345678; -static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line) -{ +static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line) { std::unique_lock<std::mutex> lock(s_allocMutex); if (!size && !ptr) return nullptr; @@ -346,8 +347,7 @@ static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line return newPtr + sizeof(AllocHeader); } -static void ReportLeaks() -{ +static void ReportLeaks() { printf("Checking for memory leaks...\n"); bool anyLeaks = false; AllocHeader *header = s_allocRoot; @@ -375,8 +375,7 @@ static void ReportLeaks() s_allocTotalTagSize[i] = s_allocPeakTagSize[i] = 0; } -static void PrintMemoryUsage() -{ +static void PrintMemoryUsage() { XA_PRINT("Total allocations: %zu\n", s_allocTotalCount); XA_PRINT("Memory usage: %0.2fMB current, %0.2fMB peak\n", internal::s_allocTotalSize / 1024.0f / 1024.0f, internal::s_allocPeakSize / 1024.0f / 1024.0f); static const char *labels[] = { // Sync with MemTag @@ -405,8 +404,7 @@ static void PrintMemoryUsage() #define XA_PRINT_MEM_USAGE internal::PrintMemoryUsage(); #else -static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/, int /*line*/) -{ +static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/, int /*line*/) { if (size == 0 && !ptr) return nullptr; if (size == 0 && s_free) { @@ -432,89 +430,75 @@ static constexpr float kEpsilon = 0.0001f; static constexpr float kAreaEpsilon = FLT_EPSILON; static constexpr float kNormalEpsilon = 0.001f; -static int align(int x, int a) -{ +static int align(int x, int a) { return (x + a - 1) & ~(a - 1); } template <typename T> -static T max(const T &a, const T &b) -{ +static T max(const T &a, const T &b) { return a > b ? a : b; } template <typename T> -static T min(const T &a, const T &b) -{ +static T min(const T &a, const T &b) { return a < b ? a : b; } template <typename T> -static T max3(const T &a, const T &b, const T &c) -{ +static T max3(const T &a, const T &b, const T &c) { return max(a, max(b, c)); } /// Return the maximum of the three arguments. template <typename T> -static T min3(const T &a, const T &b, const T &c) -{ +static T min3(const T &a, const T &b, const T &c) { return min(a, min(b, c)); } /// Clamp between two values. template <typename T> -static T clamp(const T &x, const T &a, const T &b) -{ +static T clamp(const T &x, const T &a, const T &b) { return min(max(x, a), b); } template <typename T> -static void swap(T &a, T &b) -{ +static void swap(T &a, T &b) { T temp = a; a = b; b = temp; } -union FloatUint32 -{ +union FloatUint32 { float f; uint32_t u; }; -static bool isFinite(float f) -{ +static bool isFinite(float f) { FloatUint32 fu; fu.f = f; return fu.u != 0x7F800000u && fu.u != 0x7F800001u; } -static bool isNan(float f) -{ +static bool isNan(float f) { return f != f; } // Robust floating point comparisons: // http://realtimecollisiondetection.net/blog/?p=89 -static bool equal(const float f0, const float f1, const float epsilon) -{ +static bool equal(const float f0, const float f1, const float epsilon) { //return fabs(f0-f1) <= epsilon; return fabs(f0 - f1) <= epsilon * max3(1.0f, fabsf(f0), fabsf(f1)); } -static int ftoi_ceil(float val) -{ +static int ftoi_ceil(float val) { return (int)ceilf(val); } -static bool isZero(const float f, const float epsilon) -{ +static bool isZero(const float f, const float epsilon) { return fabs(f) <= epsilon; } -static float square(float f) -{ +static float square(float f) { return f * f; } @@ -524,9 +508,8 @@ static float square(float f) * @note isPowerOfTwo(x) == true -> nextPowerOfTwo(x) == x * @note nextPowerOfTwo(x) = 2 << log2(x-1) */ -static uint32_t nextPowerOfTwo(uint32_t x) -{ - XA_DEBUG_ASSERT( x != 0 ); +static uint32_t nextPowerOfTwo(uint32_t x) { + XA_DEBUG_ASSERT(x != 0); // On modern CPUs this is supposed to be as fast as using the bsr instruction. x--; x |= x >> 1; @@ -537,38 +520,34 @@ static uint32_t nextPowerOfTwo(uint32_t x) return x + 1; } -class Vector2 -{ +class Vector2 { public: Vector2() {} - explicit Vector2(float f) : x(f), y(f) {} - Vector2(float x, float y): x(x), y(y) {} + explicit Vector2(float f) : + x(f), y(f) {} + Vector2(float _x, float _y) : + x(_x), y(_y) {} - Vector2 operator-() const - { + Vector2 operator-() const { return Vector2(-x, -y); } - void operator+=(const Vector2 &v) - { + void operator+=(const Vector2 &v) { x += v.x; y += v.y; } - void operator-=(const Vector2 &v) - { + void operator-=(const Vector2 &v) { x -= v.x; y -= v.y; } - void operator*=(float s) - { + void operator*=(float s) { x *= s; y *= s; } - void operator*=(const Vector2 &v) - { + void operator*=(const Vector2 &v) { x *= v.x; y *= v.y; } @@ -576,13 +555,11 @@ public: float x, y; }; -static bool operator==(const Vector2 &a, const Vector2 &b) -{ +static bool operator==(const Vector2 &a, const Vector2 &b) { return a.x == b.x && a.y == b.y; } -static bool operator!=(const Vector2 &a, const Vector2 &b) -{ +static bool operator!=(const Vector2 &a, const Vector2 &b) { return a.x != b.x || a.y != b.y; } @@ -591,78 +568,64 @@ static bool operator!=(const Vector2 &a, const Vector2 &b) return Vector2(a.x + b.x, a.y + b.y); }*/ -static Vector2 operator-(const Vector2 &a, const Vector2 &b) -{ +static Vector2 operator-(const Vector2 &a, const Vector2 &b) { return Vector2(a.x - b.x, a.y - b.y); } -static Vector2 operator*(const Vector2 &v, float s) -{ +static Vector2 operator*(const Vector2 &v, float s) { return Vector2(v.x * s, v.y * s); } -static float dot(const Vector2 &a, const Vector2 &b) -{ +static float dot(const Vector2 &a, const Vector2 &b) { return a.x * b.x + a.y * b.y; } -static float lengthSquared(const Vector2 &v) -{ +static float lengthSquared(const Vector2 &v) { return v.x * v.x + v.y * v.y; } -static float length(const Vector2 &v) -{ +static float length(const Vector2 &v) { return sqrtf(lengthSquared(v)); } #if XA_DEBUG -static bool isNormalized(const Vector2 &v, float epsilon = kNormalEpsilon) -{ +static bool isNormalized(const Vector2 &v, float epsilon = kNormalEpsilon) { return equal(length(v), 1, epsilon); } #endif -static Vector2 normalize(const Vector2 &v, float epsilon) -{ - float l = length(v); - XA_DEBUG_ASSERT(!isZero(l, epsilon)); - XA_UNUSED(epsilon); - Vector2 n = v * (1.0f / l); +static Vector2 normalize(const Vector2 &v) { + const float l = length(v); + XA_DEBUG_ASSERT(l > 0.0f); // Never negative. + const Vector2 n = v * (1.0f / l); XA_DEBUG_ASSERT(isNormalized(n)); return n; } -static Vector2 normalizeSafe(const Vector2 &v, const Vector2 &fallback, float epsilon) -{ - float l = length(v); - if (isZero(l, epsilon)) - return fallback; - return v * (1.0f / l); +static Vector2 normalizeSafe(const Vector2 &v, const Vector2 &fallback) { + const float l = length(v); + if (l > 0.0f) // Never negative. + return v * (1.0f / l); + return fallback; } -static bool equal(const Vector2 &v1, const Vector2 &v2, float epsilon) -{ +static bool equal(const Vector2 &v1, const Vector2 &v2, float epsilon) { return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon); } -static Vector2 min(const Vector2 &a, const Vector2 &b) -{ +static Vector2 min(const Vector2 &a, const Vector2 &b) { return Vector2(min(a.x, b.x), min(a.y, b.y)); } -static Vector2 max(const Vector2 &a, const Vector2 &b) -{ +static Vector2 max(const Vector2 &a, const Vector2 &b) { return Vector2(max(a.x, b.x), max(a.y, b.y)); } -static bool isFinite(const Vector2 &v) -{ +static bool isFinite(const Vector2 &v) { return isFinite(v.x) && isFinite(v.y); } -static float triangleArea(const Vector2 &a, const Vector2 &b, const Vector2 &c) -{ +static float triangleArea(const Vector2 &a, const Vector2 &b, const Vector2 &c) { // IC: While it may be appealing to use the following expression: //return (c.x * a.y + a.x * b.y + b.x * c.y - b.x * a.y - c.x * b.y - a.x * c.y) * 0.5f; // That's actually a terrible idea. Small triangles far from the origin can end up producing fairly large floating point @@ -676,8 +639,7 @@ static float triangleArea(const Vector2 &a, const Vector2 &b, const Vector2 &c) return (v0.x * v1.y - v0.y * v1.x) * 0.5f; } -static bool linesIntersect(const Vector2 &a1, const Vector2 &a2, const Vector2 &b1, const Vector2 &b2, float epsilon) -{ +static bool linesIntersect(const Vector2 &a1, const Vector2 &a2, const Vector2 &b1, const Vector2 &b2, float epsilon) { const Vector2 v0 = a2 - a1; const Vector2 v1 = b2 - b1; const float denom = -v1.x * v0.y + v0.x * v1.y; @@ -685,76 +647,70 @@ static bool linesIntersect(const Vector2 &a1, const Vector2 &a2, const Vector2 & return false; const float s = (-v0.y * (a1.x - b1.x) + v0.x * (a1.y - b1.y)) / denom; if (s > epsilon && s < 1.0f - epsilon) { - const float t = ( v1.x * (a1.y - b1.y) - v1.y * (a1.x - b1.x)) / denom; + const float t = (v1.x * (a1.y - b1.y) - v1.y * (a1.x - b1.x)) / denom; return t > epsilon && t < 1.0f - epsilon; } return false; } -struct Vector2i -{ +struct Vector2i { Vector2i() {} - Vector2i(int32_t x, int32_t y) : x(x), y(y) {} + Vector2i(int32_t _x, int32_t _y) : + x(_x), y(_y) {} int32_t x, y; }; -class Vector3 -{ +class Vector3 { public: Vector3() {} - explicit Vector3(float f) : x(f), y(f), z(f) {} - Vector3(float x, float y, float z) : x(x), y(y), z(z) {} - Vector3(const Vector2 &v, float z) : x(v.x), y(v.y), z(z) {} - - Vector2 xy() const - { + explicit Vector3(float f) : + x(f), y(f), z(f) {} + Vector3(float _x, float _y, float _z) : + x(_x), y(_y), z(_z) {} + Vector3(const Vector2 &v, float _z) : + x(v.x), y(v.y), z(_z) {} + + Vector2 xy() const { return Vector2(x, y); } - Vector3 operator-() const - { + Vector3 operator-() const { return Vector3(-x, -y, -z); } - void operator+=(const Vector3 &v) - { + void operator+=(const Vector3 &v) { x += v.x; y += v.y; z += v.z; } - void operator-=(const Vector3 &v) - { + void operator-=(const Vector3 &v) { x -= v.x; y -= v.y; z -= v.z; } - void operator*=(float s) - { + void operator*=(float s) { x *= s; y *= s; z *= s; } - void operator/=(float s) - { + void operator/=(float s) { float is = 1.0f / s; x *= is; y *= is; z *= is; } - void operator*=(const Vector3 &v) - { + void operator*=(const Vector3 &v) { x *= v.x; y *= v.y; z *= v.z; } - void operator/=(const Vector3 &v) - { + void operator/=(const Vector3 &v) { x /= v.x; y /= v.y; z /= v.z; @@ -763,260 +719,151 @@ public: float x, y, z; }; -static Vector3 operator+(const Vector3 &a, const Vector3 &b) -{ +static Vector3 operator+(const Vector3 &a, const Vector3 &b) { return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); } -static Vector3 operator-(const Vector3 &a, const Vector3 &b) -{ +static Vector3 operator-(const Vector3 &a, const Vector3 &b) { return Vector3(a.x - b.x, a.y - b.y, a.z - b.z); } -static Vector3 cross(const Vector3 &a, const Vector3 &b) -{ +static bool operator==(const Vector3 &a, const Vector3 &b) { + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +static Vector3 cross(const Vector3 &a, const Vector3 &b) { return Vector3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); } -static Vector3 operator*(const Vector3 &v, float s) -{ +static Vector3 operator*(const Vector3 &v, float s) { return Vector3(v.x * s, v.y * s, v.z * s); } -static Vector3 operator/(const Vector3 &v, float s) -{ +static Vector3 operator/(const Vector3 &v, float s) { return v * (1.0f / s); } -static float dot(const Vector3 &a, const Vector3 &b) -{ +static float dot(const Vector3 &a, const Vector3 &b) { return a.x * b.x + a.y * b.y + a.z * b.z; } -static float lengthSquared(const Vector3 &v) -{ +static float lengthSquared(const Vector3 &v) { return v.x * v.x + v.y * v.y + v.z * v.z; } -static float length(const Vector3 &v) -{ +static float length(const Vector3 &v) { return sqrtf(lengthSquared(v)); } -static bool isNormalized(const Vector3 &v, float epsilon = kNormalEpsilon) -{ - return equal(length(v), 1, epsilon); +static bool isNormalized(const Vector3 &v, float epsilon = kNormalEpsilon) { + return equal(length(v), 1.0f, epsilon); } -static Vector3 normalize(const Vector3 &v, float epsilon) -{ - float l = length(v); - XA_DEBUG_ASSERT(!isZero(l, epsilon)); - XA_UNUSED(epsilon); - Vector3 n = v * (1.0f / l); +static Vector3 normalize(const Vector3 &v) { + const float l = length(v); + XA_DEBUG_ASSERT(l > 0.0f); // Never negative. + const Vector3 n = v * (1.0f / l); XA_DEBUG_ASSERT(isNormalized(n)); return n; } -static Vector3 normalizeSafe(const Vector3 &v, const Vector3 &fallback, float epsilon) -{ - float l = length(v); - if (isZero(l, epsilon)) { - return fallback; - } - return v * (1.0f / l); +static Vector3 normalizeSafe(const Vector3 &v, const Vector3 &fallback) { + const float l = length(v); + if (l > 0.0f) // Never negative. + return v * (1.0f / l); + return fallback; } -static bool equal(const Vector3 &v0, const Vector3 &v1, float epsilon) -{ +static bool equal(const Vector3 &v0, const Vector3 &v1, float epsilon) { return fabs(v0.x - v1.x) <= epsilon && fabs(v0.y - v1.y) <= epsilon && fabs(v0.z - v1.z) <= epsilon; } -static Vector3 min(const Vector3 &a, const Vector3 &b) -{ +static Vector3 min(const Vector3 &a, const Vector3 &b) { return Vector3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); } -static Vector3 max(const Vector3 &a, const Vector3 &b) -{ +static Vector3 max(const Vector3 &a, const Vector3 &b) { return Vector3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); } #if XA_DEBUG -bool isFinite(const Vector3 &v) -{ +bool isFinite(const Vector3 &v) { return isFinite(v.x) && isFinite(v.y) && isFinite(v.z); } #endif -struct Extents2 -{ +struct Extents2 { Vector2 min, max; Extents2() {} - - Extents2(Vector2 p1, Vector2 p2) - { + + Extents2(Vector2 p1, Vector2 p2) { min = xatlas::internal::min(p1, p2); max = xatlas::internal::max(p1, p2); } - void reset() - { + void reset() { min.x = min.y = FLT_MAX; max.x = max.y = -FLT_MAX; } - void add(Vector2 p) - { + void add(Vector2 p) { min = xatlas::internal::min(min, p); max = xatlas::internal::max(max, p); } - Vector2 midpoint() const - { + Vector2 midpoint() const { return Vector2(min.x + (max.x - min.x) * 0.5f, min.y + (max.y - min.y) * 0.5f); } - static bool intersect(const Extents2 &e1, const Extents2 &e2) - { + static bool intersect(const Extents2 &e1, const Extents2 &e2) { return e1.min.x <= e2.max.x && e1.max.x >= e2.min.x && e1.min.y <= e2.max.y && e1.max.y >= e2.min.y; } }; -struct Plane -{ - Plane() = default; - - Plane(const Vector3 &p1, const Vector3 &p2, const Vector3 &p3) - { - normal = cross(p2 - p1, p3 - p1); - dist = dot(normal, p1); - } - - float distance(const Vector3 &p) const - { - return dot(normal, p) - dist; - } - - void normalize() - { - const float len = length(normal); - if (len > 0.0f) { - const float il = 1.0f / len; - normal *= il; - dist *= il; - } - } - - Vector3 normal; - float dist; -}; - -static bool lineIntersectsPoint(const Vector3 &point, const Vector3 &lineStart, const Vector3 &lineEnd, float *t, float epsilon) -{ - float tt; - if (!t) - t = &tt; - *t = 0.0f; - if (equal(lineStart, point, epsilon) || equal(lineEnd, point, epsilon)) - return false; // Vertex lies on either line vertices. - const Vector3 v01 = point - lineStart; - const Vector3 v21 = lineEnd - lineStart; - const float l = length(v21); - const float d = length(cross(v01, v21)) / l; - if (!isZero(d, epsilon)) - return false; - *t = dot(v01, v21) / (l * l); - return *t > kEpsilon && *t < 1.0f - kEpsilon; -} - -static bool sameSide(const Vector3 &p1, const Vector3 &p2, const Vector3 &a, const Vector3 &b) -{ - const Vector3 &ab = b - a; - return dot(cross(ab, p1 - a), cross(ab, p2 - a)) >= 0.0f; -} - -// http://blackpawn.com/texts/pointinpoly/default.html -static bool pointInTriangle(const Vector3 &p, const Vector3 &a, const Vector3 &b, const Vector3 &c) -{ - return sameSide(p, a, b, c) && sameSide(p, b, a, c) && sameSide(p, c, a, b); -} - -#if XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION -// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm -static bool rayIntersectsTriangle(const Vector3 &rayOrigin, const Vector3 &rayDir, const Vector3 *tri, float *t) -{ - *t = 0.0f; - const Vector3 &edge1 = tri[1] - tri[0]; - const Vector3 &edge2 = tri[2] - tri[0]; - const Vector3 h = cross(rayDir, edge2); - const float a = dot(edge1, h); - if (a > -kEpsilon && a < kEpsilon) - return false; // This ray is parallel to this triangle. - const float f = 1.0f / a; - const Vector3 s = rayOrigin - tri[0]; - const float u = f * dot(s, h); - if (u < 0.0f || u > 1.0f) - return false; - const Vector3 q = cross(s, edge1); - const float v = f * dot(rayDir, q); - if (v < 0.0f || u + v > 1.0f) - return false; - // At this stage we can compute t to find out where the intersection point is on the line. - *t = f * dot(edge2, q); - if (*t > kEpsilon && *t < 1.0f - kEpsilon) - return true; - // This means that there is a line intersection but not a ray intersection. - return false; -} -#endif - // From Fast-BVH -struct AABB -{ - AABB() : min(FLT_MAX, FLT_MAX, FLT_MAX), max(-FLT_MAX, -FLT_MAX, -FLT_MAX) {} - AABB(const Vector3 &min, const Vector3 &max) : min(min), max(max) { } - AABB(const Vector3 &p, float radius = 0.0f) : min(p), max(p) { if (radius > 0.0f) expand(radius); } - - bool intersect(const AABB &other) const - { +struct AABB { + AABB() : + min(FLT_MAX, FLT_MAX, FLT_MAX), max(-FLT_MAX, -FLT_MAX, -FLT_MAX) {} + AABB(const Vector3 &_min, const Vector3 &_max) : + min(_min), max(_max) {} + AABB(const Vector3 &p, float radius = 0.0f) : + min(p), max(p) { + if (radius > 0.0f) + expand(radius); + } + + bool intersect(const AABB &other) const { return min.x <= other.max.x && max.x >= other.min.x && min.y <= other.max.y && max.y >= other.min.y && min.z <= other.max.z && max.z >= other.min.z; } - void expandToInclude(const Vector3 &p) - { + void expandToInclude(const Vector3 &p) { min = internal::min(min, p); max = internal::max(max, p); } - void expandToInclude(const AABB &aabb) - { + void expandToInclude(const AABB &aabb) { min = internal::min(min, aabb.min); max = internal::max(max, aabb.max); } - void expand(float amount) - { + void expand(float amount) { min -= Vector3(amount); max += Vector3(amount); } - Vector3 centroid() const - { + Vector3 centroid() const { return min + (max - min) * 0.5f; } - uint32_t maxDimension() const - { + uint32_t maxDimension() const { const Vector3 extent = max - min; uint32_t result = 0; if (extent.y > extent.x) { result = 1; if (extent.z > extent.y) result = 2; - } - else if(extent.z > extent.x) + } else if (extent.z > extent.x) result = 2; return result; } @@ -1024,10 +871,9 @@ struct AABB Vector3 min, max; }; -struct ArrayBase -{ - ArrayBase(uint32_t elementSize, int memTag = MemTag::Default) : buffer(nullptr), elementSize(elementSize), size(0), capacity(0) - { +struct ArrayBase { + ArrayBase(uint32_t _elementSize, int memTag = MemTag::Default) : + buffer(nullptr), elementSize(_elementSize), size(0), capacity(0) { #if XA_DEBUG_HEAP this->memTag = memTag; #else @@ -1035,31 +881,31 @@ struct ArrayBase #endif } - ~ArrayBase() - { + ~ArrayBase() { XA_FREE(buffer); } - XA_INLINE void clear() - { + XA_INLINE void clear() { size = 0; } - void copyFrom(const uint8_t *data, uint32_t length) - { + void copyFrom(const uint8_t *data, uint32_t length) { + XA_DEBUG_ASSERT(data); + XA_DEBUG_ASSERT(length > 0); resize(length, true); - memcpy(buffer, data, length * elementSize); + if (buffer && data && length > 0) + memcpy(buffer, data, length * elementSize); } - void copyTo(ArrayBase &other) const - { + void copyTo(ArrayBase &other) const { XA_DEBUG_ASSERT(elementSize == other.elementSize); + XA_DEBUG_ASSERT(size > 0); other.resize(size, true); - memcpy(other.buffer, buffer, size * elementSize); + if (other.buffer && buffer && size > 0) + memcpy(other.buffer, buffer, size * elementSize); } - void destroy() - { + void destroy() { size = 0; XA_FREE(buffer); buffer = nullptr; @@ -1068,17 +914,18 @@ struct ArrayBase } // Insert the given element at the given index shifting all the elements up. - void insertAt(uint32_t index, const uint8_t *value) - { + void insertAt(uint32_t index, const uint8_t *value) { XA_DEBUG_ASSERT(index >= 0 && index <= size); + XA_DEBUG_ASSERT(value); resize(size + 1, false); - if (index < size - 1) + XA_DEBUG_ASSERT(buffer); + if (buffer && index < size - 1) memmove(buffer + elementSize * (index + 1), buffer + elementSize * index, elementSize * (size - 1 - index)); - memcpy(&buffer[index * elementSize], value, elementSize); + if (buffer && value) + memcpy(&buffer[index * elementSize], value, elementSize); } - void moveTo(ArrayBase &other) - { + void moveTo(ArrayBase &other) { XA_DEBUG_ASSERT(elementSize == other.elementSize); other.destroy(); other.buffer = buffer; @@ -1092,55 +939,61 @@ struct ArrayBase elementSize = size = capacity = 0; } - void pop_back() - { + void pop_back() { XA_DEBUG_ASSERT(size > 0); resize(size - 1, false); } - void push_back(const uint8_t *value) - { + void push_back(const uint8_t *value) { XA_DEBUG_ASSERT(value < buffer || value >= buffer + size); + XA_DEBUG_ASSERT(value); resize(size + 1, false); - memcpy(&buffer[(size - 1) * elementSize], value, elementSize); + XA_DEBUG_ASSERT(buffer); + if (buffer && value) + memcpy(&buffer[(size - 1) * elementSize], value, elementSize); } - void push_back(const ArrayBase &other) - { + void push_back(const ArrayBase &other) { XA_DEBUG_ASSERT(elementSize == other.elementSize); - if (other.size == 0) - return; - const uint32_t oldSize = size; - resize(size + other.size, false); - memcpy(buffer + oldSize * elementSize, other.buffer, other.size * other.elementSize); + if (other.size > 0) { + const uint32_t oldSize = size; + resize(size + other.size, false); + XA_DEBUG_ASSERT(buffer); + if (buffer) + memcpy(buffer + oldSize * elementSize, other.buffer, other.size * other.elementSize); + } } // Remove the element at the given index. This is an expensive operation! - void removeAt(uint32_t index) - { + void removeAt(uint32_t index) { XA_DEBUG_ASSERT(index >= 0 && index < size); - if (size != 1) - memmove(buffer + elementSize * index, buffer + elementSize * (index + 1), elementSize * (size - 1 - index)); - size--; + XA_DEBUG_ASSERT(buffer); + if (buffer) { + if (size > 1) + memmove(buffer + elementSize * index, buffer + elementSize * (index + 1), elementSize * (size - 1 - index)); + if (size > 0) + size--; + } } // Element at index is swapped with the last element, then the array length is decremented. - void removeAtFast(uint32_t index) - { + void removeAtFast(uint32_t index) { XA_DEBUG_ASSERT(index >= 0 && index < size); - if (size != 1 && index != size - 1) - memcpy(buffer + elementSize * index, buffer + elementSize * (size - 1), elementSize); - size--; + XA_DEBUG_ASSERT(buffer); + if (buffer) { + if (size > 1 && index != size - 1) + memcpy(buffer + elementSize * index, buffer + elementSize * (size - 1), elementSize); + if (size > 0) + size--; + } } - void reserve(uint32_t desiredSize) - { + void reserve(uint32_t desiredSize) { if (desiredSize > capacity) setArrayCapacity(desiredSize); } - void resize(uint32_t newSize, bool exact) - { + void resize(uint32_t newSize, bool exact) { size = newSize; if (size > capacity) { // First allocation is always exact. Otherwise, following allocations grow array to 150% of desired size. @@ -1153,8 +1006,7 @@ struct ArrayBase } } - void setArrayCapacity(uint32_t newCapacity) - { + void setArrayCapacity(uint32_t newCapacity) { XA_DEBUG_ASSERT(newCapacity >= size); if (newCapacity == 0) { // free the buffer. @@ -1174,8 +1026,7 @@ struct ArrayBase } #if XA_DEBUG_HEAP - void setMemTag(int _memTag) - { + void setMemTag(int _memTag) { this->memTag = _memTag; } #endif @@ -1189,28 +1040,27 @@ struct ArrayBase #endif }; -template<typename T> -class Array -{ +template <typename T> +class Array { public: - Array(int memTag = MemTag::Default) : m_base(sizeof(T), memTag) {} - Array(const Array&) = delete; + Array(int memTag = MemTag::Default) : + m_base(sizeof(T), memTag) {} + Array(const Array &) = delete; Array &operator=(const Array &) = delete; - XA_INLINE const T &operator[](uint32_t index) const - { + XA_INLINE const T &operator[](uint32_t index) const { XA_DEBUG_ASSERT(index < m_base.size); + XA_DEBUG_ASSERT(m_base.buffer); return ((const T *)m_base.buffer)[index]; } - XA_INLINE T &operator[](uint32_t index) - { + XA_INLINE T &operator[](uint32_t index) { XA_DEBUG_ASSERT(index < m_base.size); + XA_DEBUG_ASSERT(m_base.buffer); return ((T *)m_base.buffer)[index]; } - XA_INLINE const T &back() const - { + XA_INLINE const T &back() const { XA_DEBUG_ASSERT(!isEmpty()); return ((const T *)m_base.buffer)[m_base.size - 1]; } @@ -1218,8 +1068,7 @@ public: XA_INLINE T *begin() { return (T *)m_base.buffer; } XA_INLINE void clear() { m_base.clear(); } - bool contains(const T &value) const - { + bool contains(const T &value) const { for (uint32_t i = 0; i < m_base.size; i++) { if (((const T *)m_base.buffer)[i] == value) return true; @@ -1244,28 +1093,25 @@ public: void reserve(uint32_t desiredSize) { m_base.reserve(desiredSize); } void resize(uint32_t newSize) { m_base.resize(newSize, true); } - void runCtors() - { + void runCtors() { for (uint32_t i = 0; i < m_base.size; i++) new (&((T *)m_base.buffer)[i]) T; } - void runDtors() - { + void runDtors() { for (uint32_t i = 0; i < m_base.size; i++) ((T *)m_base.buffer)[i].~T(); } - void fill(const T &value) - { + void fill(const T &value) { auto buffer = (T *)m_base.buffer; for (uint32_t i = 0; i < m_base.size; i++) buffer[i] = value; } - void fillBytes(uint8_t value) - { - memset(m_base.buffer, (int)value, m_base.size * m_base.elementSize); + void fillBytes(uint8_t value) { + if (m_base.buffer && m_base.size > 0) + memset(m_base.buffer, (int)value, m_base.size * m_base.elementSize); } #if XA_DEBUG_HEAP @@ -1273,41 +1119,67 @@ public: #endif XA_INLINE uint32_t size() const { return m_base.size; } - XA_INLINE void zeroOutMemory() { memset(m_base.buffer, 0, m_base.elementSize * m_base.size); } + + XA_INLINE void zeroOutMemory() { + if (m_base.buffer && m_base.size > 0) + memset(m_base.buffer, 0, m_base.elementSize * m_base.size); + } private: ArrayBase m_base; }; -template<typename T> -struct ArrayView -{ - ArrayView() : data(nullptr), length(0) {} - ArrayView(Array<T> &a) : data(a.data()), length(a.size()) {} - ArrayView(T *data, uint32_t length) : data(data), length(length) {} - ArrayView &operator=(Array<T> &a) { data = a.data(); length = a.size(); return *this; } - XA_INLINE const T &operator[](uint32_t index) const { XA_DEBUG_ASSERT(index < length); return data[index]; } +template <typename T> +struct ArrayView { + ArrayView() : + data(nullptr), length(0) {} + ArrayView(Array<T> &a) : + data(a.data()), length(a.size()) {} + ArrayView(T *_data, uint32_t _length) : + data(_data), length(_length) {} + ArrayView &operator=(Array<T> &a) { + data = a.data(); + length = a.size(); + return *this; + } + XA_INLINE const T &operator[](uint32_t index) const { + XA_DEBUG_ASSERT(index < length); + return data[index]; + } + XA_INLINE T &operator[](uint32_t index) { + XA_DEBUG_ASSERT(index < length); + return data[index]; + } T *data; uint32_t length; }; -template<typename T> -struct ConstArrayView -{ - ConstArrayView() : data(nullptr), length(0) {} - ConstArrayView(const Array<T> &a) : data(a.data()), length(a.size()) {} - ConstArrayView(const T *data, uint32_t length) : data(data), length(length) {} - ConstArrayView &operator=(const Array<T> &a) { data = a.data(); length = a.size(); return *this; } - XA_INLINE const T &operator[](uint32_t index) const { XA_DEBUG_ASSERT(index < length); return data[index]; } +template <typename T> +struct ConstArrayView { + ConstArrayView() : + data(nullptr), length(0) {} + ConstArrayView(const Array<T> &a) : + data(a.data()), length(a.size()) {} + ConstArrayView(ArrayView<T> av) : + data(av.data), length(av.length) {} + ConstArrayView(const T *_data, uint32_t _length) : + data(_data), length(_length) {} + ConstArrayView &operator=(const Array<T> &a) { + data = a.data(); + length = a.size(); + return *this; + } + XA_INLINE const T &operator[](uint32_t index) const { + XA_DEBUG_ASSERT(index < length); + return data[index]; + } const T *data; uint32_t length; }; /// Basis class to compute tangent space basis, ortogonalizations and to transform vectors from one space to another. -struct Basis -{ - XA_NODISCARD static Vector3 computeTangent(const Vector3 &normal) - { +struct Basis { + XA_NODISCARD static Vector3 computeTangent(const Vector3 &normal) { XA_ASSERT(isNormalized(normal)); // Choose minimum axis. Vector3 tangent; @@ -1319,12 +1191,11 @@ struct Basis tangent = Vector3(0, 0, 1); // Ortogonalize tangent -= normal * dot(normal, tangent); - tangent = normalize(tangent, kEpsilon); + tangent = normalize(tangent); return tangent; } - XA_NODISCARD static Vector3 computeBitangent(const Vector3 &normal, const Vector3 &tangent) - { + XA_NODISCARD static Vector3 computeBitangent(const Vector3 &normal, const Vector3 &tangent) { return cross(normal, tangent); } @@ -1334,42 +1205,36 @@ struct Basis }; // Simple bit array. -class BitArray -{ +class BitArray { public: - BitArray() : m_size(0) {} + BitArray() : + m_size(0) {} - BitArray(uint32_t sz) - { + BitArray(uint32_t sz) { resize(sz); } - void resize(uint32_t new_size) - { + void resize(uint32_t new_size) { m_size = new_size; m_wordArray.resize((m_size + 31) >> 5); } - bool get(uint32_t index) const - { + bool get(uint32_t index) const { XA_DEBUG_ASSERT(index < m_size); return (m_wordArray[index >> 5] & (1 << (index & 31))) != 0; } - void set(uint32_t index) - { + void set(uint32_t index) { XA_DEBUG_ASSERT(index < m_size); m_wordArray[index >> 5] |= (1 << (index & 31)); } - void unset(uint32_t index) - { + void unset(uint32_t index) { XA_DEBUG_ASSERT(index < m_size); m_wordArray[index >> 5] &= ~(1 << (index & 31)); } - void zeroOutMemory() - { + void zeroOutMemory() { m_wordArray.zeroOutMemory(); } @@ -1378,13 +1243,13 @@ private: Array<uint32_t> m_wordArray; }; -class BitImage -{ +class BitImage { public: - BitImage() : m_width(0), m_height(0), m_rowStride(0), m_data(MemTag::BitImage) {} + BitImage() : + m_width(0), m_height(0), m_rowStride(0), m_data(MemTag::BitImage) {} - BitImage(uint32_t w, uint32_t h) : m_width(w), m_height(h), m_data(MemTag::BitImage) - { + BitImage(uint32_t w, uint32_t h) : + m_width(w), m_height(h), m_data(MemTag::BitImage) { m_rowStride = (m_width + 63) >> 6; m_data.resize(m_rowStride * m_height); m_data.zeroOutMemory(); @@ -1395,16 +1260,14 @@ public: uint32_t width() const { return m_width; } uint32_t height() const { return m_height; } - void copyTo(BitImage &other) - { + void copyTo(BitImage &other) { other.m_width = m_width; other.m_height = m_height; other.m_rowStride = m_rowStride; m_data.copyTo(other.m_data); } - void resize(uint32_t w, uint32_t h, bool discard) - { + void resize(uint32_t w, uint32_t h, bool discard) { const uint32_t rowStride = (w + 63) >> 6; if (discard) { m_data.resize(rowStride * h); @@ -1428,28 +1291,24 @@ public: m_rowStride = rowStride; } - bool get(uint32_t x, uint32_t y) const - { + bool get(uint32_t x, uint32_t y) const { XA_DEBUG_ASSERT(x < m_width && y < m_height); const uint32_t index = (x >> 6) + y * m_rowStride; return (m_data[index] & (UINT64_C(1) << (uint64_t(x) & UINT64_C(63)))) != 0; } - void set(uint32_t x, uint32_t y) - { + void set(uint32_t x, uint32_t y) { XA_DEBUG_ASSERT(x < m_width && y < m_height); const uint32_t index = (x >> 6) + y * m_rowStride; m_data[index] |= UINT64_C(1) << (uint64_t(x) & UINT64_C(63)); XA_DEBUG_ASSERT(get(x, y)); } - void zeroOutMemory() - { + void zeroOutMemory() { m_data.zeroOutMemory(); } - bool canBlit(const BitImage &image, uint32_t offsetX, uint32_t offsetY) const - { + bool canBlit(const BitImage &image, uint32_t offsetX, uint32_t offsetY) const { for (uint32_t y = 0; y < image.m_height; y++) { const uint32_t thisY = y + offsetY; if (thisY >= m_height) @@ -1473,8 +1332,7 @@ public: return true; } - void dilate(uint32_t padding) - { + void dilate(uint32_t padding) { BitImage tmp(m_width, m_height); for (uint32_t p = 0; p < padding; p++) { tmp.zeroOutMemory(); @@ -1484,15 +1342,21 @@ public: if (!b) { if (x > 0) { b |= get(x - 1, y); - if (y > 0) b |= get(x - 1, y - 1); - if (y < m_height - 1) b |= get(x - 1, y + 1); + if (y > 0) + b |= get(x - 1, y - 1); + if (y < m_height - 1) + b |= get(x - 1, y + 1); } - if (y > 0) b |= get(x, y - 1); - if (y < m_height - 1) b |= get(x, y + 1); + if (y > 0) + b |= get(x, y - 1); + if (y < m_height - 1) + b |= get(x, y + 1); if (x < m_width - 1) { b |= get(x + 1, y); - if (y > 0) b |= get(x + 1, y - 1); - if (y < m_height - 1) b |= get(x + 1, y + 1); + if (y > 0) + b |= get(x + 1, y - 1); + if (y < m_height - 1) + b |= get(x + 1, y + 1); } } if (b) @@ -1511,11 +1375,10 @@ private: }; // From Fast-BVH -class BVH -{ +class BVH { public: - BVH(const Array<AABB> &objectAabbs, uint32_t leafSize = 4) : m_objectIds(MemTag::BVH), m_nodes(MemTag::BVH) - { + BVH(const Array<AABB> &objectAabbs, uint32_t leafSize = 4) : + m_objectIds(MemTag::BVH), m_nodes(MemTag::BVH) { m_objectAabbs = &objectAabbs; if (m_objectAabbs->isEmpty()) return; @@ -1535,7 +1398,7 @@ public: Node node; m_nodes.reserve(objectAabbs.size() * 2); uint32_t nNodes = 0; - while(stackptr > 0) { + while (stackptr > 0) { // Pop the next item off of the stack const BuildEntry &bnode = todo[--stackptr]; const uint32_t start = bnode.start; @@ -1548,7 +1411,7 @@ public: // Calculate the bounding box for this node AABB bb(objectAabbs[m_objectIds[start]]); AABB bc(objectAabbs[m_objectIds[start]].centroid()); - for(uint32_t p = start + 1; p < end; ++p) { + for (uint32_t p = start + 1; p < end; ++p) { bb.expandToInclude(objectAabbs[m_objectIds[p]]); bc.expandToInclude(objectAabbs[m_objectIds[p]].centroid()); } @@ -1564,7 +1427,7 @@ public: m_nodes[bnode.parent].rightOffset--; // When this is the second touch, this is the right child. // The right child sets up the offset for the flat tree. - if (m_nodes[bnode.parent].rightOffset == kTouchedTwice ) + if (m_nodes[bnode.parent].rightOffset == kTouchedTwice) m_nodes[bnode.parent].rightOffset = nNodes - 1 - bnode.parent; } // If this is a leaf, no need to subdivide. @@ -1599,21 +1462,20 @@ public: } } - void query(const AABB &queryAabb, Array<uint32_t> &result) const - { + void query(const AABB &queryAabb, Array<uint32_t> &result) const { result.clear(); // Working set uint32_t todo[64]; int32_t stackptr = 0; // "Push" on the root node to the working set todo[stackptr] = 0; - while(stackptr >= 0) { + while (stackptr >= 0) { // Pop off the next node to work on. const int ni = todo[stackptr--]; const Node &node = m_nodes[ni]; // Is leaf -> Intersect if (node.rightOffset == 0) { - for(uint32_t o = 0; o < node.nPrims; ++o) { + for (uint32_t o = 0; o < node.nPrims; ++o) { const uint32_t obj = node.start + o; if (queryAabb.intersect((*m_objectAabbs)[m_objectIds[obj]])) result.push_back(m_objectIds[obj]); @@ -1630,14 +1492,12 @@ public: } private: - struct BuildEntry - { + struct BuildEntry { uint32_t parent; // If non-zero then this is the index of the parent. (used in offsets) uint32_t start, end; // The range of objects in the object list covered by this node. }; - struct Node - { + struct Node { AABB aabb; uint32_t start, nPrims, rightOffset; }; @@ -1647,16 +1507,14 @@ private: Array<Node> m_nodes; }; -struct Fit -{ - static bool computeBasis(const Vector3 *points, uint32_t pointsCount, Basis *basis) - { - if (computeLeastSquaresNormal(points, pointsCount, &basis->normal)) { +struct Fit { + static bool computeBasis(ConstArrayView<Vector3> points, Basis *basis) { + if (computeLeastSquaresNormal(points, &basis->normal)) { basis->tangent = Basis::computeTangent(basis->normal); basis->bitangent = Basis::computeBitangent(basis->normal, basis->tangent); return true; } - return computeEigen(points, pointsCount, basis); + return computeEigen(points, basis); } private: @@ -1664,21 +1522,20 @@ private: // Fast, and accurate to within a few degrees. // Returns None if the points do not span a plane. // https://www.ilikebigbits.com/2015_03_04_plane_from_points.html - static bool computeLeastSquaresNormal(const Vector3 *points, uint32_t pointsCount, Vector3 *normal) - { - XA_DEBUG_ASSERT(pointsCount >= 3); - if (pointsCount == 3) { - *normal = normalize(cross(points[2] - points[0], points[1] - points[0]), kEpsilon); + static bool computeLeastSquaresNormal(ConstArrayView<Vector3> points, Vector3 *normal) { + XA_DEBUG_ASSERT(points.length >= 3); + if (points.length == 3) { + *normal = normalize(cross(points[2] - points[0], points[1] - points[0])); return true; } - const float invN = 1.0f / float(pointsCount); + const float invN = 1.0f / float(points.length); Vector3 centroid(0.0f); - for (uint32_t i = 0; i < pointsCount; i++) + for (uint32_t i = 0; i < points.length; i++) centroid += points[i]; centroid *= invN; // Calculate full 3x3 covariance matrix, excluding symmetries: float xx = 0.0f, xy = 0.0f, xz = 0.0f, yy = 0.0f, yz = 0.0f, zz = 0.0f; - for (uint32_t i = 0; i < pointsCount; i++) { + for (uint32_t i = 0; i < points.length; i++) { Vector3 r = points[i] - centroid; xx += r.x * r.x; xy += r.x * r.y; @@ -1730,7 +1587,7 @@ private: // Pick path with best conditioning: Vector3 dir(0.0f); if (det_max == det_x) - dir = Vector3(det_x,xz * yz - xy * zz,xy * yz - xz * yy); + dir = Vector3(det_x, xz * yz - xy * zz, xy * yz - xz * yy); else if (det_max == det_y) dir = Vector3(xz * yz - xy * zz, det_y, xy * xz - yz * xx); else if (det_max == det_z) @@ -1743,41 +1600,37 @@ private: return isNormalized(*normal); } - static bool computeEigen(const Vector3 *points, uint32_t pointsCount, Basis *basis) - { + static bool computeEigen(ConstArrayView<Vector3> points, Basis *basis) { float matrix[6]; - computeCovariance(pointsCount, points, matrix); + computeCovariance(points, matrix); if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) return false; float eigenValues[3]; Vector3 eigenVectors[3]; if (!eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) return false; - basis->normal = normalize(eigenVectors[2], kEpsilon); - basis->tangent = normalize(eigenVectors[0], kEpsilon); - basis->bitangent = normalize(eigenVectors[1], kEpsilon); + basis->normal = normalize(eigenVectors[2]); + basis->tangent = normalize(eigenVectors[0]); + basis->bitangent = normalize(eigenVectors[1]); return true; } - static Vector3 computeCentroid(int n, const Vector3 * points) - { + static Vector3 computeCentroid(ConstArrayView<Vector3> points) { Vector3 centroid(0.0f); - for (int i = 0; i < n; i++) { + for (uint32_t i = 0; i < points.length; i++) centroid += points[i]; - } - centroid /= float(n); + centroid /= float(points.length); return centroid; } - static Vector3 computeCovariance(int n, const Vector3 * points, float * covariance) - { + static Vector3 computeCovariance(ConstArrayView<Vector3> points, float *covariance) { // compute the centroid - Vector3 centroid = computeCentroid(n, points); + Vector3 centroid = computeCentroid(points); // compute covariance matrix for (int i = 0; i < 6; i++) { covariance[i] = 0.0f; } - for (int i = 0; i < n; i++) { + for (uint32_t i = 0; i < points.length; i++) { Vector3 v = points[i] - centroid; covariance[0] += v.x * v.x; covariance[1] += v.x * v.y; @@ -1792,8 +1645,7 @@ private: // Tridiagonal solver from Charles Bloom. // Householder transforms followed by QL decomposition. // Seems to be based on the code from Numerical Recipes in C. - static bool eigenSolveSymmetric3(const float matrix[6], float eigenValues[3], Vector3 eigenVectors[3]) - { + static bool eigenSolveSymmetric3(const float matrix[6], float eigenValues[3], Vector3 eigenVectors[3]) { XA_DEBUG_ASSERT(matrix != nullptr && eigenValues != nullptr && eigenVectors != nullptr); float subd[3]; float diag[3]; @@ -1818,7 +1670,7 @@ private: // eigenvectors are the columns; make them the rows : for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { - (&eigenVectors[j].x)[i] = (float) work[i][j]; + (&eigenVectors[j].x)[i] = (float)work[i][j]; } } // shuffle to sort by singular value : @@ -1840,8 +1692,7 @@ private: } private: - static void EigenSolver3_Tridiagonal(float mat[3][3], float *diag, float *subd) - { + static void EigenSolver3_Tridiagonal(float mat[3][3], float *diag, float *subd) { // Householder reduction T = Q^t M Q // Input: // mat, symmetric 3x3 matrix M @@ -1893,8 +1744,7 @@ private: } } - static bool EigenSolver3_QLAlgorithm(float mat[3][3], float *diag, float *subd) - { + static bool EigenSolver3_QLAlgorithm(float mat[3][3], float *diag, float *subd) { // QL iteration with implicit shifting to reduce matrix from tridiagonal // to diagonal const int maxiter = 32; @@ -1904,21 +1754,21 @@ private: int m; for (m = ell; m <= 1; m++) { float dd = fabsf(diag[m]) + fabsf(diag[m + 1]); - if ( fabsf(subd[m]) + dd == dd ) + if (fabsf(subd[m]) + dd == dd) break; } - if ( m == ell ) + if (m == ell) break; float g = (diag[ell + 1] - diag[ell]) / (2 * subd[ell]); float r = sqrtf(g * g + 1); - if ( g < 0 ) + if (g < 0) g = diag[m] - diag[ell] + subd[ell] / (g - r); else g = diag[m] - diag[ell] + subd[ell] / (g + r); float s = 1, c = 1, p = 0; for (int i = m - 1; i >= ell; i--) { float f = s * subd[i], b = c * subd[i]; - if ( fabsf(f) >= fabsf(g) ) { + if (fabsf(f) >= fabsf(g)) { c = g / f; r = sqrtf(c * c + 1); subd[i + 1] = f * r; @@ -1944,7 +1794,7 @@ private: subd[ell] = g; subd[m] = 0; } - if ( iter == maxiter ) + if (iter == maxiter) // should not get here under normal circumstances return false; } @@ -1952,56 +1802,48 @@ private: } }; -static uint32_t sdbmHash(const void *data_in, uint32_t size, uint32_t h = 5381) -{ - const uint8_t *data = (const uint8_t *) data_in; +static uint32_t sdbmHash(const void *data_in, uint32_t size, uint32_t h = 5381) { + const uint8_t *data = (const uint8_t *)data_in; uint32_t i = 0; while (i < size) { - h = (h << 16) + (h << 6) - h + (uint32_t ) data[i++]; + h = (h << 16) + (h << 6) - h + (uint32_t)data[i++]; } return h; } template <typename T> -static uint32_t hash(const T &t, uint32_t h = 5381) -{ +static uint32_t hash(const T &t, uint32_t h = 5381) { return sdbmHash(&t, sizeof(T), h); } template <typename Key> -struct Hash -{ +struct Hash { uint32_t operator()(const Key &k) const { return hash(k); } }; template <typename Key> -struct PassthroughHash -{ +struct PassthroughHash { uint32_t operator()(const Key &k) const { return (uint32_t)k; } }; template <typename Key> -struct Equal -{ +struct Equal { bool operator()(const Key &k0, const Key &k1) const { return k0 == k1; } }; -template<typename Key, typename H = Hash<Key>, typename E = Equal<Key> > -class HashMap -{ +template <typename Key, typename H = Hash<Key>, typename E = Equal<Key>> +class HashMap { public: - HashMap(int memTag, uint32_t size) : m_memTag(memTag), m_size(size), m_numSlots(0), m_slots(nullptr), m_keys(memTag), m_next(memTag) - { + HashMap(int memTag, uint32_t size) : + m_memTag(memTag), m_size(size), m_numSlots(0), m_slots(nullptr), m_keys(memTag), m_next(memTag) { } - ~HashMap() - { + ~HashMap() { if (m_slots) XA_FREE(m_slots); } - void destroy() - { + void destroy() { if (m_slots) { XA_FREE(m_slots); m_slots = nullptr; @@ -2010,8 +1852,7 @@ public: m_next.destroy(); } - uint32_t add(const Key &key) - { + uint32_t add(const Key &key) { if (!m_slots) alloc(); const uint32_t hash = computeHash(key); @@ -2021,36 +1862,18 @@ public: return m_keys.size() - 1; } - uint32_t get(const Key &key) const - { + uint32_t get(const Key &key) const { if (!m_slots) return UINT32_MAX; - const uint32_t hash = computeHash(key); - uint32_t i = m_slots[hash]; - E equal; - while (i != UINT32_MAX) { - if (equal(m_keys[i], key)) - return i; - i = m_next[i]; - } - return UINT32_MAX; + return find(key, m_slots[computeHash(key)]); } - uint32_t getNext(uint32_t current) const - { - uint32_t i = m_next[current]; - E equal; - while (i != UINT32_MAX) { - if (equal(m_keys[i], m_keys[current])) - return i; - i = m_next[i]; - } - return UINT32_MAX; + uint32_t getNext(const Key &key, uint32_t current) const { + return find(key, m_next[current]); } private: - void alloc() - { + void alloc() { XA_DEBUG_ASSERT(m_size > 0); m_numSlots = nextPowerOfTwo(m_size); auto minNumSlots = uint32_t(m_size * 1.3); @@ -2063,12 +1886,21 @@ private: m_next.reserve(m_size); } - uint32_t computeHash(const Key &key) const - { + uint32_t computeHash(const Key &key) const { H hash; return hash(key) & (m_numSlots - 1); } + uint32_t find(const Key &key, uint32_t current) const { + E equal; + while (current != UINT32_MAX) { + if (equal(m_keys[current], key)) + return current; + current = m_next[current]; + } + return current; + } + int m_memTag; uint32_t m_size; uint32_t m_numSlots; @@ -2077,9 +1909,8 @@ private: Array<uint32_t> m_next; }; -template<typename T> -static void insertionSort(T *data, uint32_t length) -{ +template <typename T> +static void insertionSort(T *data, uint32_t length) { for (int32_t i = 1; i < (int32_t)length; i++) { T x = data[i]; int32_t j = i - 1; @@ -2091,21 +1922,18 @@ static void insertionSort(T *data, uint32_t length) } } -class KISSRng -{ +class KISSRng { public: KISSRng() { reset(); } - void reset() - { + void reset() { x = 123456789; y = 362436000; z = 521288629; c = 7654321; } - uint32_t getRange(uint32_t range) - { + uint32_t getRange(uint32_t range) { if (range == 0) return 0; x = 69069 * x + 12345; @@ -2124,12 +1952,10 @@ private: // Based on Pierre Terdiman's and Michael Herf's source code. // http://www.codercorner.com/RadixSortRevisited.htm // http://www.stereopsis.com/radix.html -class RadixSort -{ +class RadixSort { public: - void sort(const float *input, uint32_t count) - { - if (input == nullptr || count == 0) { + void sort(ConstArrayView<float> input) { + if (input.length == 0) { m_buffer1.clear(); m_buffer2.clear(); m_ranks = m_buffer1.data(); @@ -2137,33 +1963,27 @@ public: return; } // Resize lists if needed - m_buffer1.resize(count); - m_buffer2.resize(count); + m_buffer1.resize(input.length); + m_buffer2.resize(input.length); m_ranks = m_buffer1.data(); m_ranks2 = m_buffer2.data(); m_validRanks = false; - if (count < 32) - insertionSort(input, count); + if (input.length < 32) + insertionSort(input); else { // @@ Avoid touching the input multiple times. - for (uint32_t i = 0; i < count; i++) { + for (uint32_t i = 0; i < input.length; i++) { floatFlip((uint32_t &)input[i]); } - radixSort<uint32_t>((const uint32_t *)input, count); - for (uint32_t i = 0; i < count; i++) { + radixSort(ConstArrayView<uint32_t>((const uint32_t *)input.data, input.length)); + for (uint32_t i = 0; i < input.length; i++) { ifloatFlip((uint32_t &)input[i]); } } } - void sort(const Array<float> &input) - { - sort(input.data(), input.size()); - } - // Access to results. m_ranks is a list of indices in sorted order, i.e. in the order you may further process your data - const uint32_t *ranks() const - { + const uint32_t *ranks() const { XA_DEBUG_ASSERT(m_validRanks); return m_ranks; } @@ -2171,54 +1991,40 @@ public: private: uint32_t *m_ranks, *m_ranks2; Array<uint32_t> m_buffer1, m_buffer2; - bool m_validRanks; + bool m_validRanks = false; - void floatFlip(uint32_t &f) - { + void floatFlip(uint32_t &f) { int32_t mask = (int32_t(f) >> 31) | 0x80000000; // Warren Hunt, Manchor Ko. f ^= mask; } - void ifloatFlip(uint32_t &f) - { + void ifloatFlip(uint32_t &f) { uint32_t mask = ((f >> 31) - 1) | 0x80000000; // Michael Herf. f ^= mask; } - template<typename T> - void createHistograms(const T *buffer, uint32_t count, uint32_t *histogram) - { - const uint32_t bucketCount = sizeof(T); // (8 * sizeof(T)) / log2(radix) + void createHistograms(ConstArrayView<uint32_t> input, uint32_t *histogram) { + const uint32_t bucketCount = sizeof(uint32_t); // Init bucket pointers. uint32_t *h[bucketCount]; for (uint32_t i = 0; i < bucketCount; i++) { h[i] = histogram + 256 * i; } // Clear histograms. - memset(histogram, 0, 256 * bucketCount * sizeof(uint32_t )); + memset(histogram, 0, 256 * bucketCount * sizeof(uint32_t)); // @@ Add support for signed integers. // Build histograms. - const uint8_t *p = (const uint8_t *)buffer; // @@ Does this break aliasing rules? - const uint8_t *pe = p + count * sizeof(T); + const uint8_t *p = (const uint8_t *)input.data; // @@ Does this break aliasing rules? + const uint8_t *pe = p + input.length * sizeof(uint32_t); while (p != pe) { h[0][*p++]++, h[1][*p++]++, h[2][*p++]++, h[3][*p++]++; -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4127) -#endif - if (bucketCount == 8) h[4][*p++]++, h[5][*p++]++, h[6][*p++]++, h[7][*p++]++; -#ifdef _MSC_VER -#pragma warning(pop) -#endif } } - template <typename T> - void insertionSort(const T *input, uint32_t count) - { + void insertionSort(ConstArrayView<float> input) { if (!m_validRanks) { m_ranks[0] = 0; - for (uint32_t i = 1; i != count; ++i) { + for (uint32_t i = 1; i != input.length; ++i) { int rank = m_ranks[i] = i; uint32_t j = i; while (j != 0 && input[rank] < input[m_ranks[j - 1]]) { @@ -2231,7 +2037,7 @@ private: } m_validRanks = true; } else { - for (uint32_t i = 1; i != count; ++i) { + for (uint32_t i = 1; i != input.length; ++i) { int rank = m_ranks[i]; uint32_t j = i; while (j != 0 && input[rank] < input[m_ranks[j - 1]]) { @@ -2245,35 +2051,34 @@ private: } } - template <typename T> - void radixSort(const T *input, uint32_t count) - { - const uint32_t P = sizeof(T); // pass count + void radixSort(ConstArrayView<uint32_t> input) { + const uint32_t P = sizeof(uint32_t); // pass count // Allocate histograms & offsets on the stack uint32_t histogram[256 * P]; uint32_t *link[256]; - createHistograms(input, count, histogram); + createHistograms(input, histogram); // Radix sort, j is the pass number (0=LSB, P=MSB) for (uint32_t j = 0; j < P; j++) { // Pointer to this bucket. const uint32_t *h = &histogram[j * 256]; - const uint8_t *inputBytes = (const uint8_t *)input; // @@ Is this aliasing legal? + auto inputBytes = (const uint8_t *)input.data; // @@ Is this aliasing legal? inputBytes += j; - if (h[inputBytes[0]] == count) { + if (h[inputBytes[0]] == input.length) { // Skip this pass, all values are the same. continue; } // Create offsets link[0] = m_ranks2; - for (uint32_t i = 1; i < 256; i++) link[i] = link[i - 1] + h[i - 1]; + for (uint32_t i = 1; i < 256; i++) + link[i] = link[i - 1] + h[i - 1]; // Perform Radix Sort if (!m_validRanks) { - for (uint32_t i = 0; i < count; i++) { + for (uint32_t i = 0; i < input.length; i++) { *link[inputBytes[i * P]]++ = i; } m_validRanks = true; } else { - for (uint32_t i = 0; i < count; i++) { + for (uint32_t i = 0; i < input.length; i++) { const uint32_t idx = m_ranks[i]; *link[inputBytes[idx * P]]++ = idx; } @@ -2283,7 +2088,7 @@ private: } // All values were equal, generate linear ranks. if (!m_validRanks) { - for (uint32_t i = 0; i < count; i++) + for (uint32_t i = 0; i < input.length; i++) m_ranks[i] = i; m_validRanks = true; } @@ -2291,30 +2096,25 @@ private: }; // Wrapping this in a class allows temporary arrays to be re-used. -class BoundingBox2D -{ +class BoundingBox2D { public: Vector2 majorAxis, minorAxis, minCorner, maxCorner; - void clear() - { + void clear() { m_boundaryVertices.clear(); } - void appendBoundaryVertex(Vector2 v) - { + void appendBoundaryVertex(Vector2 v) { m_boundaryVertices.push_back(v); } // This should compute convex hull and use rotating calipers to find the best box. Currently it uses a brute force method. - // If vertices is null or vertexCount is 0, the boundary vertices are used. - void compute(const Vector2 *vertices = nullptr, uint32_t vertexCount = 0) - { - if (!vertices || vertexCount == 0) { - vertices = m_boundaryVertices.data(); - vertexCount = m_boundaryVertices.size(); - } - convexHull(m_boundaryVertices.data(), m_boundaryVertices.size(), m_hull, 0.00001f); + // If vertices are empty, the boundary vertices are used. + void compute(ConstArrayView<Vector2> vertices = ConstArrayView<Vector2>()) { + XA_DEBUG_ASSERT(!m_boundaryVertices.isEmpty()); + if (vertices.length == 0) + vertices = m_boundaryVertices; + convexHull(m_boundaryVertices, m_hull, 0.00001f); // @@ Ideally I should use rotating calipers to find the best box. Using brute force for now. float best_area = FLT_MAX; Vector2 best_min(0); @@ -2324,13 +2124,13 @@ public: for (uint32_t i = 0, j = hullCount - 1; i < hullCount; j = i, i++) { if (equal(m_hull[i], m_hull[j], kEpsilon)) continue; - Vector2 axis = normalize(m_hull[i] - m_hull[j], 0.0f); + Vector2 axis = normalize(m_hull[i] - m_hull[j]); XA_DEBUG_ASSERT(isFinite(axis)); // Compute bounding box. Vector2 box_min(FLT_MAX, FLT_MAX); Vector2 box_max(-FLT_MAX, -FLT_MAX); // Consider all points, not only boundary points, in case the input chart is malformed. - for (uint32_t v = 0; v < vertexCount; v++) { + for (uint32_t v = 0; v < vertices.length; v++) { const Vector2 &point = vertices[v]; const float x = dot(axis, point); const float y = dot(Vector2(-axis.y, axis.x), point); @@ -2357,28 +2157,27 @@ public: private: // Compute the convex hull using Graham Scan. - void convexHull(const Vector2 *input, uint32_t inputCount, Array<Vector2> &output, float epsilon) - { - m_coords.resize(inputCount); - for (uint32_t i = 0; i < inputCount; i++) + void convexHull(ConstArrayView<Vector2> input, Array<Vector2> &output, float epsilon) { + m_coords.resize(input.length); + for (uint32_t i = 0; i < input.length; i++) m_coords[i] = input[i].x; m_radix.sort(m_coords); const uint32_t *ranks = m_radix.ranks(); m_top.clear(); m_bottom.clear(); - m_top.reserve(inputCount); - m_bottom.reserve(inputCount); + m_top.reserve(input.length); + m_bottom.reserve(input.length); Vector2 P = input[ranks[0]]; - Vector2 Q = input[ranks[inputCount - 1]]; + Vector2 Q = input[ranks[input.length - 1]]; float topy = max(P.y, Q.y); float boty = min(P.y, Q.y); - for (uint32_t i = 0; i < inputCount; i++) { + for (uint32_t i = 0; i < input.length; i++) { Vector2 p = input[ranks[i]]; if (p.y >= boty) m_top.push_back(p); } - for (uint32_t i = 0; i < inputCount; i++) { - Vector2 p = input[ranks[inputCount - 1 - i]]; + for (uint32_t i = 0; i < input.length; i++) { + Vector2 p = input[ranks[input.length - 1 - i]]; if (p.y <= topy) m_bottom.push_back(p); } @@ -2387,7 +2186,7 @@ private: XA_DEBUG_ASSERT(m_top.size() >= 2); output.push_back(m_top[0]); output.push_back(m_top[1]); - for (uint32_t i = 2; i < m_top.size(); ) { + for (uint32_t i = 2; i < m_top.size();) { Vector2 a = output[output.size() - 2]; Vector2 b = output[output.size() - 1]; Vector2 c = m_top[i]; @@ -2403,7 +2202,7 @@ private: XA_DEBUG_ASSERT(m_bottom.size() >= 2); output.push_back(m_bottom[1]); // Filter bottom list. - for (uint32_t i = 2; i < m_bottom.size(); ) { + for (uint32_t i = 2; i < m_bottom.size();) { Vector2 a = output[output.size() - 2]; Vector2 b = output[output.size() - 1]; Vector2 c = m_bottom[i]; @@ -2426,32 +2225,45 @@ private: RadixSort m_radix; }; -static uint32_t meshEdgeFace(uint32_t edge) { return edge / 3; } -static uint32_t meshEdgeIndex0(uint32_t edge) { return edge; } +struct EdgeKey { + EdgeKey(const EdgeKey &k) : + v0(k.v0), v1(k.v1) {} + EdgeKey(uint32_t _v0, uint32_t _v1) : + v0(_v0), v1(_v1) {} + bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; } -static uint32_t meshEdgeIndex1(uint32_t edge) -{ + uint32_t v0; + uint32_t v1; +}; + +struct EdgeHash { + uint32_t operator()(const EdgeKey &k) const { return k.v0 * 32768u + k.v1; } +}; + +static uint32_t meshEdgeFace(uint32_t edge) { + return edge / 3; +} +static uint32_t meshEdgeIndex0(uint32_t edge) { + return edge; +} + +static uint32_t meshEdgeIndex1(uint32_t edge) { const uint32_t faceFirstEdge = edge / 3 * 3; return faceFirstEdge + (edge - faceFirstEdge + 1) % 3; } -struct MeshFlags -{ - enum - { - HasIgnoredFaces = 1<<0, - HasNormals = 1<<1 +struct MeshFlags { + enum { + HasIgnoredFaces = 1 << 0, + HasNormals = 1 << 1, + HasMaterials = 1 << 2 }; }; -class Mesh; -static void meshGetBoundaryLoops(const Mesh &mesh, Array<uint32_t> &boundaryLoops); - -class Mesh -{ +class Mesh { public: - Mesh(float epsilon, uint32_t approxVertexCount, uint32_t approxFaceCount, uint32_t flags = 0, uint32_t id = UINT32_MAX) : m_epsilon(epsilon), m_flags(flags), m_id(id), m_faceIgnore(MemTag::Mesh), m_indices(MemTag::MeshIndices), m_positions(MemTag::MeshPositions), m_normals(MemTag::MeshNormals), m_texcoords(MemTag::MeshTexcoords), m_nextColocalVertex(MemTag::MeshColocals), m_boundaryEdges(MemTag::MeshBoundaries), m_oppositeEdges(MemTag::MeshBoundaries), m_nextBoundaryEdges(MemTag::MeshBoundaries), m_edgeMap(MemTag::MeshEdgeMap, approxFaceCount * 3) - { + Mesh(float epsilon, uint32_t approxVertexCount, uint32_t approxFaceCount, uint32_t flags = 0, uint32_t id = UINT32_MAX) : + m_epsilon(epsilon), m_flags(flags), m_id(id), m_faceIgnore(MemTag::Mesh), m_faceMaterials(MemTag::Mesh), m_indices(MemTag::MeshIndices), m_positions(MemTag::MeshPositions), m_normals(MemTag::MeshNormals), m_texcoords(MemTag::MeshTexcoords), m_nextColocalVertex(MemTag::MeshColocals), m_firstColocalVertex(MemTag::MeshColocals), m_boundaryEdges(MemTag::MeshBoundaries), m_oppositeEdges(MemTag::MeshBoundaries), m_edgeMap(MemTag::MeshEdgeMap, approxFaceCount * 3) { m_indices.reserve(approxFaceCount * 3); m_positions.reserve(approxVertexCount); m_texcoords.reserve(approxVertexCount); @@ -2459,13 +2271,14 @@ public: m_faceIgnore.reserve(approxFaceCount); if (m_flags & MeshFlags::HasNormals) m_normals.reserve(approxVertexCount); + if (m_flags & MeshFlags::HasMaterials) + m_faceMaterials.reserve(approxFaceCount); } uint32_t flags() const { return m_flags; } uint32_t id() const { return m_id; } - void addVertex(const Vector3 &pos, const Vector3 &normal = Vector3(0.0f), const Vector2 &texcoord = Vector2(0.0f)) - { + void addVertex(const Vector3 &pos, const Vector3 &normal = Vector3(0.0f), const Vector2 &texcoord = Vector2(0.0f)) { XA_DEBUG_ASSERT(isFinite(pos)); m_positions.push_back(pos); if (m_flags & MeshFlags::HasNormals) @@ -2473,45 +2286,22 @@ public: m_texcoords.push_back(texcoord); } - struct AddFaceResult - { - enum Enum - { - OK, - DuplicateEdge = 1 - }; - }; - - AddFaceResult::Enum addFace(uint32_t v0, uint32_t v1, uint32_t v2, bool ignore = false) - { - uint32_t indexArray[3]; - indexArray[0] = v0; - indexArray[1] = v1; - indexArray[2] = v2; - return addFace(indexArray, ignore); - } - - AddFaceResult::Enum addFace(const uint32_t *indices, bool ignore = false) - { - AddFaceResult::Enum result = AddFaceResult::OK; + void addFace(const uint32_t *indices, bool ignore = false, uint32_t material = UINT32_MAX) { if (m_flags & MeshFlags::HasIgnoredFaces) m_faceIgnore.push_back(ignore); + if (m_flags & MeshFlags::HasMaterials) + m_faceMaterials.push_back(material); const uint32_t firstIndex = m_indices.size(); for (uint32_t i = 0; i < 3; i++) m_indices.push_back(indices[i]); for (uint32_t i = 0; i < 3; i++) { const uint32_t vertex0 = m_indices[firstIndex + i]; const uint32_t vertex1 = m_indices[firstIndex + (i + 1) % 3]; - const EdgeKey key(vertex0, vertex1); - if (m_edgeMap.get(key) != UINT32_MAX) - result = AddFaceResult::DuplicateEdge; - m_edgeMap.add(key); + m_edgeMap.add(EdgeKey(vertex0, vertex1)); } - return result; } - void createColocals() - { + void createColocalsBVH() { const uint32_t vertexCount = m_positions.size(); Array<AABB> aabbs(MemTag::BVH); aabbs.resize(vertexCount); @@ -2522,6 +2312,8 @@ public: Array<uint32_t> potential(MemTag::MeshColocals); m_nextColocalVertex.resize(vertexCount); m_nextColocalVertex.fillBytes(0xff); + m_firstColocalVertex.resize(vertexCount); + m_firstColocalVertex.fillBytes(0xff); for (uint32_t i = 0; i < vertexCount; i++) { if (m_nextColocalVertex[i] != UINT32_MAX) continue; // Already linked. @@ -2537,18 +2329,65 @@ public: if (colocals.size() == 1) { // No colocals for this vertex. m_nextColocalVertex[i] = i; - continue; + m_firstColocalVertex[i] = i; + continue; } // Link in ascending order. insertionSort(colocals.data(), colocals.size()); - for (uint32_t j = 0; j < colocals.size(); j++) + for (uint32_t j = 0; j < colocals.size(); j++) { m_nextColocalVertex[colocals[j]] = colocals[(j + 1) % colocals.size()]; + m_firstColocalVertex[colocals[j]] = colocals[0]; + } XA_DEBUG_ASSERT(m_nextColocalVertex[i] != UINT32_MAX); } } - void createBoundaries() - { + void createColocalsHash() { + const uint32_t vertexCount = m_positions.size(); + HashMap<Vector3> positionToVertexMap(MemTag::Default, vertexCount); + for (uint32_t i = 0; i < vertexCount; i++) + positionToVertexMap.add(m_positions[i]); + Array<uint32_t> colocals(MemTag::MeshColocals); + m_nextColocalVertex.resize(vertexCount); + m_nextColocalVertex.fillBytes(0xff); + m_firstColocalVertex.resize(vertexCount); + m_firstColocalVertex.fillBytes(0xff); + for (uint32_t i = 0; i < vertexCount; i++) { + if (m_nextColocalVertex[i] != UINT32_MAX) + continue; // Already linked. + // Find other vertices colocal to this one. + colocals.clear(); + colocals.push_back(i); // Always add this vertex. + uint32_t otherVertex = positionToVertexMap.get(m_positions[i]); + while (otherVertex != UINT32_MAX) { + if (otherVertex != i && equal(m_positions[i], m_positions[otherVertex], m_epsilon) && m_nextColocalVertex[otherVertex] == UINT32_MAX) + colocals.push_back(otherVertex); + otherVertex = positionToVertexMap.getNext(m_positions[i], otherVertex); + } + if (colocals.size() == 1) { + // No colocals for this vertex. + m_nextColocalVertex[i] = i; + m_firstColocalVertex[i] = i; + continue; + } + // Link in ascending order. + insertionSort(colocals.data(), colocals.size()); + for (uint32_t j = 0; j < colocals.size(); j++) { + m_nextColocalVertex[colocals[j]] = colocals[(j + 1) % colocals.size()]; + m_firstColocalVertex[colocals[j]] = colocals[0]; + } + XA_DEBUG_ASSERT(m_nextColocalVertex[i] != UINT32_MAX); + } + } + + void createColocals() { + if (m_epsilon <= FLT_EPSILON) + createColocalsHash(); + else + createColocalsBVH(); + } + + void createBoundaries() { const uint32_t edgeCount = m_indices.size(); const uint32_t vertexCount = m_positions.size(); m_oppositeEdges.resize(edgeCount); @@ -2578,151 +2417,54 @@ public: } } - void linkBoundaries() - { - const uint32_t edgeCount = m_indices.size(); - HashMap<uint32_t> vertexToEdgeMap(MemTag::Mesh, edgeCount); // Edge is index / 2 - for (uint32_t i = 0; i < edgeCount; i++) { - vertexToEdgeMap.add(m_indices[meshEdgeIndex0(i)]); - vertexToEdgeMap.add(m_indices[meshEdgeIndex1(i)]); - } - m_nextBoundaryEdges.resize(edgeCount); - for (uint32_t i = 0; i < edgeCount; i++) - m_nextBoundaryEdges[i] = UINT32_MAX; - uint32_t numBoundaryLoops = 0, numUnclosedBoundaries = 0; - BitArray linkedEdges(edgeCount); - linkedEdges.zeroOutMemory(); - for (;;) { - // Find the first boundary edge that hasn't been linked yet. - uint32_t firstEdge = UINT32_MAX; - for (uint32_t i = 0; i < edgeCount; i++) { - if (m_oppositeEdges[i] == UINT32_MAX && !linkedEdges.get(i)) { - firstEdge = i; - break; - } - } - if (firstEdge == UINT32_MAX) - break; - uint32_t currentEdge = firstEdge; - for (;;) { - // Find the next boundary edge. The first vertex will be the same as (or colocal to) the current edge second vertex. - const uint32_t startVertex = m_indices[meshEdgeIndex1(currentEdge)]; - uint32_t bestNextEdge = UINT32_MAX; - for (ColocalVertexIterator it(this, startVertex); !it.isDone(); it.advance()) { - uint32_t mapIndex = vertexToEdgeMap.get(it.vertex()); - while (mapIndex != UINT32_MAX) { - const uint32_t otherEdge = mapIndex / 2; // Two vertices added per edge. - if (m_oppositeEdges[otherEdge] != UINT32_MAX) - goto next; // Not a boundary edge. - if (linkedEdges.get(otherEdge)) - goto next; // Already linked. - if (m_indices[meshEdgeIndex0(otherEdge)] != it.vertex()) - goto next; // Edge contains the vertex, but it's the wrong one. - // First edge (closing the boundary loop) has the highest priority. - // Non-colocal vertex has the next highest. - if (bestNextEdge != firstEdge && (bestNextEdge == UINT32_MAX || it.vertex() == startVertex)) - bestNextEdge = otherEdge; - next: - mapIndex = vertexToEdgeMap.getNext(mapIndex); - } - } - if (bestNextEdge == UINT32_MAX) { - numUnclosedBoundaries++; - if (currentEdge == firstEdge) - linkedEdges.set(firstEdge); // Only 1 edge in this boundary "loop". - break; // Can't find a next edge. - } - m_nextBoundaryEdges[currentEdge] = bestNextEdge; - linkedEdges.set(bestNextEdge); - currentEdge = bestNextEdge; - if (currentEdge == firstEdge) { - numBoundaryLoops++; - break; // Closed the boundary loop. - } - } - } -#if XA_FIX_INTERNAL_BOUNDARY_LOOPS - // Find internal boundary loops and separate them. - // Detect by finding two edges in a boundary loop that have a colocal end vertex. - // Fix by swapping their next boundary edge. - // Need to start over after every fix since known boundary loops have changed. - Array<uint32_t> boundaryLoops; - fixInternalBoundary: - meshGetBoundaryLoops(*this, boundaryLoops); - for (uint32_t loop = 0; loop < boundaryLoops.size(); loop++) { - linkedEdges.zeroOutMemory(); - for (Mesh::BoundaryLoopEdgeIterator it1(this, boundaryLoops[loop]); !it1.isDone(); it1.advance()) { - const uint32_t e1 = it1.edge(); - if (linkedEdges.get(e1)) - continue; - for (Mesh::BoundaryLoopEdgeIterator it2(this, boundaryLoops[loop]); !it2.isDone(); it2.advance()) { - const uint32_t e2 = it2.edge(); - if (e1 == e2 || !isBoundaryEdge(e2) || linkedEdges.get(e2)) - continue; - if (!areColocal(m_indices[meshEdgeIndex1(e1)], m_indices[meshEdgeIndex1(e2)])) - continue; - swap(m_nextBoundaryEdges[e1], m_nextBoundaryEdges[e2]); - linkedEdges.set(e1); - linkedEdges.set(e2); - goto fixInternalBoundary; // start over - } - } - } -#endif - } - /// Find edge, test all colocals. - uint32_t findEdge(uint32_t vertex0, uint32_t vertex1) const - { - uint32_t result = UINT32_MAX; - if (m_nextColocalVertex.isEmpty()) { + uint32_t findEdge(uint32_t vertex0, uint32_t vertex1) const { + // Try to find exact vertex match first. + { EdgeKey key(vertex0, vertex1); uint32_t edge = m_edgeMap.get(key); while (edge != UINT32_MAX) { // Don't find edges of ignored faces. - if (!isFaceIgnored(meshEdgeFace(edge))) { - //XA_DEBUG_ASSERT(m_id != UINT32_MAX || (m_id == UINT32_MAX && result == UINT32_MAX)); // duplicate edge - ignore on initial meshes - result = edge; -#if !XA_DEBUG - return result; -#endif - } - edge = m_edgeMap.getNext(edge); + if (!isFaceIgnored(meshEdgeFace(edge))) + return edge; + edge = m_edgeMap.getNext(key, edge); } - } else { - for (ColocalVertexIterator it0(this, vertex0); !it0.isDone(); it0.advance()) { - for (ColocalVertexIterator it1(this, vertex1); !it1.isDone(); it1.advance()) { - EdgeKey key(it0.vertex(), it1.vertex()); + } + // If colocals were created, try every permutation. + if (!m_nextColocalVertex.isEmpty()) { + uint32_t colocalVertex0 = vertex0; + for (;;) { + uint32_t colocalVertex1 = vertex1; + for (;;) { + EdgeKey key(colocalVertex0, colocalVertex1); uint32_t edge = m_edgeMap.get(key); while (edge != UINT32_MAX) { // Don't find edges of ignored faces. - if (!isFaceIgnored(meshEdgeFace(edge))) { - XA_DEBUG_ASSERT(m_id != UINT32_MAX || (m_id == UINT32_MAX && result == UINT32_MAX)); // duplicate edge - ignore on initial meshes - result = edge; -#if !XA_DEBUG - return result; -#endif - } - edge = m_edgeMap.getNext(edge); + if (!isFaceIgnored(meshEdgeFace(edge))) + return edge; + edge = m_edgeMap.getNext(key, edge); } + colocalVertex1 = m_nextColocalVertex[colocalVertex1]; + if (colocalVertex1 == vertex1) + break; // Back to start. } + colocalVertex0 = m_nextColocalVertex[colocalVertex0]; + if (colocalVertex0 == vertex0) + break; // Back to start. } } - return result; + return UINT32_MAX; } // Edge map can be destroyed when no longer used to reduce memory usage. It's used by: // * Mesh::createBoundaries() - // * Mesh::ColocalEdgeIterator (used by MeshFaceGroups) - // * meshCloseHole() - void destroyEdgeMap() - { + // * Mesh::edgeMap() (used by MeshFaceGroups) + void destroyEdgeMap() { m_edgeMap.destroy(); } #if XA_DEBUG_EXPORT_OBJ - void writeObjVertices(FILE *file) const - { + void writeObjVertices(FILE *file) const { for (uint32_t i = 0; i < m_positions.size(); i++) fprintf(file, "v %g %g %g\n", m_positions[i].x, m_positions[i].y, m_positions[i].z); if (m_flags & MeshFlags::HasNormals) { @@ -2733,8 +2475,7 @@ public: fprintf(file, "vt %g %g\n", m_texcoords[i].x, m_texcoords[i].y); } - void writeObjFace(FILE *file, uint32_t face, uint32_t offset = 0) const - { + void writeObjFace(FILE *file, uint32_t face, uint32_t offset = 0) const { fprintf(file, "f "); for (uint32_t j = 0; j < 3; j++) { const uint32_t index = m_indices[face * 3 + j] + 1 + offset; // 1-indexed @@ -2742,8 +2483,7 @@ public: } } - void writeObjBoundaryEges(FILE *file) const - { + void writeObjBoundaryEges(FILE *file) const { if (m_oppositeEdges.isEmpty()) return; // Boundaries haven't been created. fprintf(file, "o boundary_edges\n"); @@ -2754,31 +2494,7 @@ public: } } - void writeObjLinkedBoundaries(FILE *file) const - { - if (m_oppositeEdges.isEmpty() || m_nextBoundaryEdges.isEmpty()) - return; // Boundaries haven't been created and/or linked. - Array<uint32_t> boundaryLoops; - meshGetBoundaryLoops(*this, boundaryLoops); - for (uint32_t i = 0; i < boundaryLoops.size(); i++) { - uint32_t edge = boundaryLoops[i]; - fprintf(file, "o boundary_%04d\n", i); - fprintf(file, "l"); - for (;;) { - const uint32_t vertex0 = m_indices[meshEdgeIndex0(edge)]; - const uint32_t vertex1 = m_indices[meshEdgeIndex1(edge)]; - fprintf(file, " %d", vertex0 + 1); // 1-indexed - edge = m_nextBoundaryEdges[edge]; - if (edge == boundaryLoops[i] || edge == UINT32_MAX) { - fprintf(file, " %d\n", vertex1 + 1); // 1-indexed - break; - } - } - } - } - - void writeObjFile(const char *filename) const - { + void writeObjFile(const char *filename) const { FILE *file; XA_FOPEN(file, filename, "w"); if (!file) @@ -2789,13 +2505,11 @@ public: for (uint32_t i = 0; i < faceCount(); i++) writeObjFace(file, i); writeObjBoundaryEges(file); - writeObjLinkedBoundaries(file); fclose(file); } #endif - float computeSurfaceArea() const - { + float computeSurfaceArea() const { float area = 0; for (uint32_t f = 0; f < faceCount(); f++) area += computeFaceArea(f); @@ -2804,24 +2518,21 @@ public: } // Returned value is always positive, even if some triangles are flipped. - float computeParametricArea() const - { + float computeParametricArea() const { float area = 0; for (uint32_t f = 0; f < faceCount(); f++) area += fabsf(computeFaceParametricArea(f)); // May be negative, depends on texcoord winding. - return area; + return area; } - float computeFaceArea(uint32_t face) const - { + float computeFaceArea(uint32_t face) const { const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]]; const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]]; const Vector3 &p2 = m_positions[m_indices[face * 3 + 2]]; return length(cross(p1 - p0, p2 - p0)) * 0.5f; } - Vector3 computeFaceCentroid(uint32_t face) const - { + Vector3 computeFaceCentroid(uint32_t face) const { Vector3 sum(0.0f); for (uint32_t i = 0; i < 3; i++) sum += m_positions[m_indices[face * 3 + i]]; @@ -2830,8 +2541,7 @@ public: // Average of the edge midpoints weighted by the edge length. // I want a point inside the triangle, but closer to the cirumcenter. - Vector3 computeFaceCenter(uint32_t face) const - { + Vector3 computeFaceCenter(uint32_t face) const { const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]]; const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]]; const Vector3 &p2 = m_positions[m_indices[face * 3 + 2]]; @@ -2844,28 +2554,25 @@ public: return m0 + m1 + m2; } - Vector3 computeFaceNormal(uint32_t face) const - { + Vector3 computeFaceNormal(uint32_t face) const { const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]]; const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]]; const Vector3 &p2 = m_positions[m_indices[face * 3 + 2]]; const Vector3 e0 = p2 - p0; const Vector3 e1 = p1 - p0; const Vector3 normalAreaScaled = cross(e0, e1); - return normalizeSafe(normalAreaScaled, Vector3(0, 0, 1), 0.0f); + return normalizeSafe(normalAreaScaled, Vector3(0, 0, 1)); } - float computeFaceParametricArea(uint32_t face) const - { + float computeFaceParametricArea(uint32_t face) const { const Vector2 &t0 = m_texcoords[m_indices[face * 3 + 0]]; const Vector2 &t1 = m_texcoords[m_indices[face * 3 + 1]]; const Vector2 &t2 = m_texcoords[m_indices[face * 3 + 2]]; return triangleArea(t0, t1, t2); } - + // @@ This is not exactly accurate, we should compare the texture coordinates... - bool isSeam(uint32_t edge) const - { + bool isSeam(uint32_t edge) const { const uint32_t oppositeEdge = m_oppositeEdges[edge]; if (oppositeEdge == UINT32_MAX) return false; // boundary edge @@ -2876,8 +2583,7 @@ public: return m_indices[e0] != m_indices[oe1] || m_indices[e1] != m_indices[oe0]; } - bool isTextureSeam(uint32_t edge) const - { + bool isTextureSeam(uint32_t edge) const { const uint32_t oppositeEdge = m_oppositeEdges[edge]; if (oppositeEdge == UINT32_MAX) return false; // boundary edge @@ -2888,26 +2594,9 @@ public: return m_texcoords[m_indices[e0]] != m_texcoords[m_indices[oe1]] || m_texcoords[m_indices[e1]] != m_texcoords[m_indices[oe0]]; } - uint32_t firstColocal(uint32_t vertex) const - { - for (ColocalVertexIterator it(this, vertex); !it.isDone(); it.advance()) { - if (it.vertex() < vertex) - vertex = it.vertex(); - } - return vertex; - } - - bool areColocal(uint32_t vertex0, uint32_t vertex1) const - { - if (vertex0 == vertex1) - return true; - if (m_nextColocalVertex.isEmpty()) - return false; - for (ColocalVertexIterator it(this, vertex0); !it.isDone(); it.advance()) { - if (it.vertex() == vertex1) - return true; - } - return false; + uint32_t firstColocalVertex(uint32_t vertex) const { + XA_DEBUG_ASSERT(m_firstColocalVertex.size() == m_positions.size()); + return m_firstColocalVertex[vertex]; } XA_INLINE float epsilon() const { return m_epsilon; } @@ -2919,23 +2608,28 @@ public: XA_INLINE uint32_t vertexCount() const { return m_positions.size(); } XA_INLINE uint32_t vertexAt(uint32_t i) const { return m_indices[i]; } XA_INLINE const Vector3 &position(uint32_t vertex) const { return m_positions[vertex]; } - XA_INLINE const Vector3 *positions() const { return m_positions.data(); } - XA_INLINE const Vector3 &normal(uint32_t vertex) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasNormals); return m_normals[vertex]; } + XA_INLINE ConstArrayView<Vector3> positions() const { return m_positions; } + XA_INLINE const Vector3 &normal(uint32_t vertex) const { + XA_DEBUG_ASSERT(m_flags & MeshFlags::HasNormals); + return m_normals[vertex]; + } XA_INLINE const Vector2 &texcoord(uint32_t vertex) const { return m_texcoords[vertex]; } XA_INLINE Vector2 &texcoord(uint32_t vertex) { return m_texcoords[vertex]; } - XA_INLINE const Vector2 *texcoords() const { return m_texcoords.data(); } - XA_INLINE Vector2 *texcoords() { return m_texcoords.data(); } + XA_INLINE const ConstArrayView<Vector2> texcoords() const { return m_texcoords; } + XA_INLINE ArrayView<Vector2> texcoords() { return m_texcoords; } XA_INLINE uint32_t faceCount() const { return m_indices.size() / 3; } - XA_INLINE const uint32_t *indices() const { return m_indices.data(); } + XA_INLINE ConstArrayView<uint32_t> indices() const { return m_indices; } XA_INLINE uint32_t indexCount() const { return m_indices.size(); } XA_INLINE bool isFaceIgnored(uint32_t face) const { return (m_flags & MeshFlags::HasIgnoredFaces) && m_faceIgnore[face]; } + XA_INLINE uint32_t faceMaterial(uint32_t face) const { return (m_flags & MeshFlags::HasMaterials) ? m_faceMaterials[face] : UINT32_MAX; } + XA_INLINE const HashMap<EdgeKey, EdgeHash> &edgeMap() const { return m_edgeMap; } private: - float m_epsilon; uint32_t m_flags; uint32_t m_id; Array<bool> m_faceIgnore; + Array<uint32_t> m_faceMaterials; Array<uint32_t> m_indices; Array<Vector3> m_positions; Array<Vector3> m_normals; @@ -2943,205 +2637,31 @@ private: // Populated by createColocals Array<uint32_t> m_nextColocalVertex; // In: vertex index. Out: the vertex index of the next colocal position. + Array<uint32_t> m_firstColocalVertex; // Populated by createBoundaries BitArray m_isBoundaryVertex; Array<uint32_t> m_boundaryEdges; Array<uint32_t> m_oppositeEdges; // In: edge index. Out: the index of the opposite edge (i.e. wound the opposite direction). UINT32_MAX if the input edge is a boundary edge. - // Populated by linkBoundaries - Array<uint32_t> m_nextBoundaryEdges; // The index of the next boundary edge. UINT32_MAX if the edge is not a boundary edge. - - struct EdgeKey - { - EdgeKey(const EdgeKey &k) : v0(k.v0), v1(k.v1) {} - EdgeKey(uint32_t v0, uint32_t v1) : v0(v0), v1(v1) {} - bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; } - - uint32_t v0; - uint32_t v1; - }; - - struct EdgeHash - { - uint32_t operator()(const EdgeKey &k) const { return k.v0 * 32768u + k.v1; } - }; - HashMap<EdgeKey, EdgeHash> m_edgeMap; public: - class BoundaryLoopEdgeIterator - { + class FaceEdgeIterator { public: - BoundaryLoopEdgeIterator(const Mesh *mesh, uint32_t edge) : m_mesh(mesh), m_first(UINT32_MAX), m_current(edge) {} - - void advance() - { - if (m_first == UINT32_MAX) - m_first = m_current; - m_current = m_mesh->m_nextBoundaryEdges[m_current]; - } - - bool isDone() const - { - return m_first == m_current || m_current == UINT32_MAX; - } - - uint32_t edge() const - { - return m_current; - } - - uint32_t nextEdge() const - { - return m_mesh->m_nextBoundaryEdges[m_current]; - } - - private: - const Mesh *m_mesh; - uint32_t m_first; - uint32_t m_current; - }; - - class ColocalVertexIterator - { - public: - ColocalVertexIterator(const Mesh *mesh, uint32_t v) : m_mesh(mesh), m_first(UINT32_MAX), m_current(v) {} - - void advance() - { - if (m_first == UINT32_MAX) - m_first = m_current; - if (!m_mesh->m_nextColocalVertex.isEmpty()) - m_current = m_mesh->m_nextColocalVertex[m_current]; - } - - bool isDone() const - { - return m_first == m_current; - } - - uint32_t vertex() const - { - return m_current; - } - - const Vector3 *pos() const - { - return &m_mesh->m_positions[m_current]; - } - - private: - const Mesh *m_mesh; - uint32_t m_first; - uint32_t m_current; - }; - - class ColocalEdgeIterator - { - public: - ColocalEdgeIterator(const Mesh *mesh, uint32_t vertex0, uint32_t vertex1) : m_mesh(mesh), m_vertex0It(mesh, vertex0), m_vertex1It(mesh, vertex1), m_vertex1(vertex1) - { - do { - if (!resetElement()) { - advanceVertex1(); - } - else { - break; - } - } while (!isDone()); - } - - void advance() - { - advanceElement(); - } - - bool isDone() const - { - return m_vertex0It.isDone() && m_vertex1It.isDone() && m_edge == UINT32_MAX; - } - - uint32_t edge() const - { - return m_edge; - } - - private: - bool resetElement() - { - m_edge = m_mesh->m_edgeMap.get(Mesh::EdgeKey(m_vertex0It.vertex(), m_vertex1It.vertex())); - while (m_edge != UINT32_MAX) { - if (!isIgnoredFace()) - break; - m_edge = m_mesh->m_edgeMap.getNext(m_edge); - } - if (m_edge == UINT32_MAX) { - return false; - } - return true; - } - - void advanceElement() - { - for (;;) { - m_edge = m_mesh->m_edgeMap.getNext(m_edge); - if (m_edge == UINT32_MAX) - break; - if (!isIgnoredFace()) - break; - } - if (m_edge == UINT32_MAX) - advanceVertex1(); - } - - void advanceVertex1() - { - auto successful = false; - while (!successful) { - m_vertex1It.advance(); - if (m_vertex1It.isDone()) { - if (!m_vertex0It.isDone()) { - m_vertex0It.advance(); - m_vertex1It = ColocalVertexIterator(m_mesh, m_vertex1); - } - else { - return; - } - } - successful = resetElement(); - } - } - - bool isIgnoredFace() const - { - return m_mesh->m_faceIgnore[meshEdgeFace(m_edge)]; - } - - const Mesh *m_mesh; - ColocalVertexIterator m_vertex0It, m_vertex1It; - const uint32_t m_vertex1; - uint32_t m_edge; - }; - - class FaceEdgeIterator - { - public: - FaceEdgeIterator (const Mesh *mesh, uint32_t face) : m_mesh(mesh), m_face(face), m_relativeEdge(0) - { + FaceEdgeIterator(const Mesh *mesh, uint32_t face) : + m_mesh(mesh), m_face(face), m_relativeEdge(0) { m_edge = m_face * 3; } - void advance() - { + void advance() { if (m_relativeEdge < 3) { m_edge++; m_relativeEdge++; } } - bool isDone() const - { + bool isDone() const { return m_relativeEdge == 3; } @@ -3152,9 +2672,8 @@ public: uint32_t relativeEdge() const { return m_relativeEdge; } uint32_t face() const { return m_face; } uint32_t oppositeEdge() const { return m_mesh->m_oppositeEdges[m_edge]; } - - uint32_t oppositeFace() const - { + + uint32_t oppositeFace() const { const uint32_t oedge = m_mesh->m_oppositeEdges[m_edge]; if (oedge == UINT32_MAX) return UINT32_MAX; @@ -3178,19 +2697,18 @@ public: }; }; -struct MeshFaceGroups -{ +struct MeshFaceGroups { typedef uint32_t Handle; static constexpr Handle kInvalid = UINT32_MAX; - MeshFaceGroups(const Mesh *mesh) : m_mesh(mesh), m_groups(MemTag::Mesh), m_firstFace(MemTag::Mesh), m_nextFace(MemTag::Mesh), m_faceCount(MemTag::Mesh) {} + MeshFaceGroups(const Mesh *mesh) : + m_mesh(mesh), m_groups(MemTag::Mesh), m_firstFace(MemTag::Mesh), m_nextFace(MemTag::Mesh), m_faceCount(MemTag::Mesh) {} XA_INLINE Handle groupAt(uint32_t face) const { return m_groups[face]; } XA_INLINE uint32_t groupCount() const { return m_faceCount.size(); } XA_INLINE uint32_t nextFace(uint32_t face) const { return m_nextFace[face]; } XA_INLINE uint32_t faceCount(uint32_t group) const { return m_faceCount[group]; } - void compute() - { + void compute() { m_groups.resize(m_mesh->faceCount()); m_groups.fillBytes(0xff); // Set all faces to kInvalid uint32_t firstUnassignedFace = 0; @@ -3222,57 +2740,25 @@ struct MeshFaceGroups break; const uint32_t f = growFaces.back(); growFaces.pop_back(); + const uint32_t material = m_mesh->faceMaterial(f); for (Mesh::FaceEdgeIterator edgeIt(m_mesh, f); !edgeIt.isDone(); edgeIt.advance()) { - // Iterate opposite edges. There may be more than one - non-manifold geometry can have duplicate edges. - // Prioritize the one with exact vertex match, not just colocal. - // If *any* of the opposite edges are already assigned to this group, don't do anything. - bool alreadyAssignedToThisGroup = false; - uint32_t bestConnectedFace = UINT32_MAX; - for (Mesh::ColocalEdgeIterator oppositeEdgeIt(m_mesh, edgeIt.vertex1(), edgeIt.vertex0()); !oppositeEdgeIt.isDone(); oppositeEdgeIt.advance()) { - const uint32_t oppositeEdge = oppositeEdgeIt.edge(); - const uint32_t oppositeFace = meshEdgeFace(oppositeEdge); -#if 0 - // Reject opposite face if dihedral angle >= 90 degrees. - { - Vector3 a = m_mesh->computeFaceNormal(f); - Vector3 b = m_mesh->computeFaceNormal(oppositeFace); - if (dot(a, b) <= 0.0f) - continue; - } -#endif - if (m_mesh->isFaceIgnored(oppositeFace)) - continue; // Don't add ignored faces to group. - if (m_groups[oppositeFace] == group) { - alreadyAssignedToThisGroup = true; - break; - } - if (m_groups[oppositeFace] != kInvalid) - continue; // Connected face is already assigned to another group. - if (faceDuplicatesGroupEdge(group, oppositeFace)) - continue; // Don't want duplicate edges in a group. - const uint32_t oppositeVertex0 = m_mesh->vertexAt(meshEdgeIndex0(oppositeEdge)); - const uint32_t oppositeVertex1 = m_mesh->vertexAt(meshEdgeIndex1(oppositeEdge)); - if (bestConnectedFace == UINT32_MAX || (oppositeVertex0 == edgeIt.vertex1() && oppositeVertex1 == edgeIt.vertex0())) - bestConnectedFace = oppositeFace; -#if 0 - else { - // Choose the opposite face with the smallest dihedral angle. - const float d1 = 1.0f - dot(computeFaceNormal(f), computeFaceNormal(bestConnectedFace)); - const float d2 = 1.0f - dot(computeFaceNormal(f), computeFaceNormal(oppositeFace)); - if (d2 < d1) - bestConnectedFace = oppositeFace; - } -#endif - } - if (!alreadyAssignedToThisGroup && bestConnectedFace != UINT32_MAX) { - m_groups[bestConnectedFace] = group; - m_nextFace[bestConnectedFace] = UINT32_MAX; - if (prevFace != UINT32_MAX) - m_nextFace[prevFace] = bestConnectedFace; - prevFace = bestConnectedFace; - groupFaceCount++; - growFaces.push_back(bestConnectedFace); - } + const uint32_t oppositeEdge = m_mesh->findEdge(edgeIt.vertex1(), edgeIt.vertex0()); + if (oppositeEdge == UINT32_MAX) + continue; // Boundary edge. + const uint32_t oppositeFace = meshEdgeFace(oppositeEdge); + if (m_mesh->isFaceIgnored(oppositeFace)) + continue; // Don't add ignored faces to group. + if (m_mesh->faceMaterial(oppositeFace) != material) + continue; // Different material. + if (m_groups[oppositeFace] != kInvalid) + continue; // Connected face is already assigned to another group. + m_groups[oppositeFace] = group; + m_nextFace[oppositeFace] = UINT32_MAX; + if (prevFace != UINT32_MAX) + m_nextFace[prevFace] = oppositeFace; + prevFace = oppositeFace; + groupFaceCount++; + growFaces.push_back(oppositeFace); } } m_faceCount.push_back(groupFaceCount); @@ -3281,27 +2767,23 @@ struct MeshFaceGroups } } - class Iterator - { + class Iterator { public: - Iterator(const MeshFaceGroups *meshFaceGroups, Handle group) : m_meshFaceGroups(meshFaceGroups) - { + Iterator(const MeshFaceGroups *meshFaceGroups, Handle group) : + m_meshFaceGroups(meshFaceGroups) { XA_DEBUG_ASSERT(group != kInvalid); m_current = m_meshFaceGroups->m_firstFace[group]; } - void advance() - { + void advance() { m_current = m_meshFaceGroups->m_nextFace[m_current]; } - bool isDone() const - { + bool isDone() const { return m_current == UINT32_MAX; } - uint32_t face() const - { + uint32_t face() const { return m_current; } @@ -3311,18 +2793,6 @@ struct MeshFaceGroups }; private: - // Check if the face duplicates any edges of any face already in the group. - bool faceDuplicatesGroupEdge(Handle group, uint32_t face) const - { - for (Mesh::FaceEdgeIterator edgeIt(m_mesh, face); !edgeIt.isDone(); edgeIt.advance()) { - for (Mesh::ColocalEdgeIterator colocalEdgeIt(m_mesh, edgeIt.vertex0(), edgeIt.vertex1()); !colocalEdgeIt.isDone(); colocalEdgeIt.advance()) { - if (m_groups[meshEdgeFace(colocalEdgeIt.edge())] == group) - return true; - } - } - return false; - } - const Mesh *m_mesh; Array<Handle> m_groups; Array<uint32_t> m_firstFace; @@ -3332,243 +2802,27 @@ private: constexpr MeshFaceGroups::Handle MeshFaceGroups::kInvalid; -static bool meshCloseHole(Mesh *mesh, const Array<uint32_t> &holeVertices, const Vector3 &normal) -{ -#if XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION - const uint32_t faceCount = mesh->faceCount(); -#endif - const bool compareNormal = equal(normal, Vector3(0.0f), FLT_EPSILON); - uint32_t frontCount = holeVertices.size(); - Array<uint32_t> frontVertices; - Array<Vector3> frontPoints; - Array<float> frontAngles; - frontVertices.resize(frontCount); - frontPoints.resize(frontCount); - for (uint32_t i = 0; i < frontCount; i++) { - frontVertices[i] = holeVertices[i]; - frontPoints[i] = mesh->position(frontVertices[i]); - } - while (frontCount >= 3) { - frontAngles.resize(frontCount); - float smallestAngle = kPi2, smallestAngleIgnoringNormal = kPi2; - uint32_t smallestAngleIndex = UINT32_MAX, smallestAngleIndexIgnoringNormal = UINT32_MAX; - for (uint32_t i = 0; i < frontCount; i++) { - const uint32_t i1 = i == 0 ? frontCount - 1 : i - 1; - const uint32_t i2 = i; - const uint32_t i3 = (i + 1) % frontCount; - const Vector3 edge1 = frontPoints[i1] - frontPoints[i2]; - const Vector3 edge2 = frontPoints[i3] - frontPoints[i2]; - frontAngles[i] = atan2f(length(cross(edge1, edge2)), dot(edge1, edge2)); - if (frontAngles[i] >= smallestAngle || isNan(frontAngles[i])) - continue; - // Don't duplicate edges. - if (mesh->findEdge(frontVertices[i1], frontVertices[i2]) != UINT32_MAX) - continue; - if (mesh->findEdge(frontVertices[i2], frontVertices[i3]) != UINT32_MAX) - continue; - if (mesh->findEdge(frontVertices[i3], frontVertices[i1]) != UINT32_MAX) - continue; - /* - Make sure he new edge that would be formed by (i3, i1) doesn't intersect any vertices. This often happens when fixing t-junctions. - - i2 - * - / \ - / \ - i1 *--*--* i3 - \ | / - \|/ - * - */ - bool intersection = false; - for (uint32_t j = 0; j < frontCount; j++) { - if (j == i1 || j == i2 || j == i3) - continue; - if (lineIntersectsPoint(frontPoints[j], frontPoints[i3], frontPoints[i1], nullptr, mesh->epsilon())) { - intersection = true; - break; - } - } - if (intersection) - continue; - // Don't add the triangle if a boundary point lies on the same plane as the triangle, and is inside it. - intersection = false; - const Plane plane(frontPoints[i1], frontPoints[i2], frontPoints[i3]); - for (uint32_t j = 0; j < frontCount; j++) { - if (j == i1 || j == i2 || j == i3) - continue; - if (!isZero(plane.distance(frontPoints[j]), mesh->epsilon())) - continue; - if (pointInTriangle(frontPoints[j], frontPoints[i1], frontPoints[i2], frontPoints[i3])) { - intersection = true; - break; - } - } - if (intersection) - continue; -#if XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION - // Don't add the triangle if the new edge (i3, i1), intersects any other triangle that isn't part of the filled hole. - intersection = false; - const Vector3 newEdgeVector = frontPoints[i1] - frontPoints[i3]; - for (uint32_t f = 0; f < faceCount; f++) { - Vector3 tri[3]; - for (uint32_t j = 0; j < 3; j++) - tri[j] = mesh->position(mesh->vertexAt(f * 3 + j)); - float t; - if (rayIntersectsTriangle(frontPoints[i3], newEdgeVector, tri, &t)) { - intersection = true; - break; - } - } - if (intersection) - continue; -#endif - // Skip backwards facing triangles. - if (compareNormal) { - if (frontAngles[i] < smallestAngleIgnoringNormal) { - smallestAngleIgnoringNormal = frontAngles[i]; - smallestAngleIndexIgnoringNormal = i; - } - const Vector3 e0 = frontPoints[i3] - frontPoints[i1]; - const Vector3 e1 = frontPoints[i2] - frontPoints[i1]; - const Vector3 triNormal = normalizeSafe(cross(e0, e1), Vector3(0.0f), mesh->epsilon()); - if (dot(normal, triNormal) <= 0.0f) - continue; - } - smallestAngle = smallestAngleIgnoringNormal = frontAngles[i]; - smallestAngleIndex = smallestAngleIndexIgnoringNormal = i; - } - // Closing holes failed if we don't have a smallest angle. - // Fallback to ignoring the backwards facing normal test if possible. - if (smallestAngleIndex == UINT32_MAX || smallestAngle <= 0.0f || smallestAngle >= kPi) { - if (smallestAngleIgnoringNormal == UINT32_MAX || smallestAngleIgnoringNormal <= 0.0f || smallestAngleIgnoringNormal >= kPi) - return false; - else - smallestAngleIndex = smallestAngleIndexIgnoringNormal; - } - const uint32_t i1 = smallestAngleIndex == 0 ? frontCount - 1 : smallestAngleIndex - 1; - const uint32_t i2 = smallestAngleIndex; - const uint32_t i3 = (smallestAngleIndex + 1) % frontCount; - const Mesh::AddFaceResult::Enum result = mesh->addFace(frontVertices[i1], frontVertices[i2], frontVertices[i3]); - XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK); // Shouldn't happen due to the findEdge calls above. - XA_UNUSED(result); - frontVertices.removeAt(i2); - frontPoints.removeAt(i2); - frontCount = frontVertices.size(); - } - return true; -} - -static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, const Vector3 &normal, uint32_t *holeCount, Array<uint32_t> *holeFaceCounts) -{ - if (holeFaceCounts) - holeFaceCounts->clear(); - // Compute lengths. - const uint32_t boundaryCount = boundaryLoops.size(); - Array<float> boundaryLengths; - Array<uint32_t> boundaryEdgeCounts; - boundaryEdgeCounts.resize(boundaryCount); - for (uint32_t i = 0; i < boundaryCount; i++) { - float boundaryLength = 0.0f; - boundaryEdgeCounts[i] = 0; - for (Mesh::BoundaryLoopEdgeIterator it(mesh, boundaryLoops[i]); !it.isDone(); it.advance()) { - const Vector3 &t0 = mesh->position(mesh->vertexAt(meshEdgeIndex0(it.edge()))); - const Vector3 &t1 = mesh->position(mesh->vertexAt(meshEdgeIndex1(it.edge()))); - boundaryLength += length(t1 - t0); - boundaryEdgeCounts[i]++; - } - boundaryLengths.push_back(boundaryLength); - } - // Find disk boundary. - uint32_t diskBoundary = 0; - float maxLength = boundaryLengths[0]; - for (uint32_t i = 1; i < boundaryCount; i++) { - if (boundaryLengths[i] > maxLength) { - maxLength = boundaryLengths[i]; - diskBoundary = i; - } - } - // Close holes. - Array<uint32_t> holeVertices; - Array<Vector3> holePoints; - bool result = true; - for (uint32_t i = 0; i < boundaryCount; i++) { - if (diskBoundary == i) - continue; // Skip disk boundary. - holeVertices.resize(boundaryEdgeCounts[i]); - holePoints.resize(boundaryEdgeCounts[i]); - // Winding is backwards for internal boundaries. - uint32_t e = 0; - for (Mesh::BoundaryLoopEdgeIterator it(mesh, boundaryLoops[i]); !it.isDone(); it.advance()) { - const uint32_t vertex = mesh->vertexAt(meshEdgeIndex0(it.edge())); - holeVertices[boundaryEdgeCounts[i] - 1 - e] = vertex; - holePoints[boundaryEdgeCounts[i] - 1 - e] = mesh->position(vertex); - e++; - } - const uint32_t oldFaceCount = mesh->faceCount(); - if (!meshCloseHole(mesh, holeVertices, normal)) - result = false; // Return false if any hole failed to close, but keep trying to close other holes. - if (holeCount) - (*holeCount)++; - if (holeFaceCounts) - holeFaceCounts->push_back(mesh->faceCount() - oldFaceCount); - } - return result; -} - -static bool meshIsPlanar(const Mesh &mesh) -{ - const Vector3 p1 = mesh.position(mesh.vertexAt(0)); - const Vector3 p2 = mesh.position(mesh.vertexAt(1)); - const Vector3 p3 = mesh.position(mesh.vertexAt(2)); - const Plane plane(p1, p2, p3); - const uint32_t vertexCount = mesh.vertexCount(); - for (uint32_t v = 0; v < vertexCount; v++) { - const float d = plane.distance(mesh.position(v)); - if (!isZero(d, mesh.epsilon())) - return false; - } - return true; -} - -/* -Fixing T-junctions. - -- Find T-junctions. Find vertices that are on an edge. -- This test is approximate. -- Insert edges on a spatial index to speedup queries. -- Consider only open edges, that is edges that have no pairs. -- Consider only vertices on boundaries. -- Close T-junction. -- Split edge. - -*/ -struct SplitEdge -{ - uint32_t edge; - float t; - uint32_t vertex; - - bool operator<(const SplitEdge &other) const - { - if (edge < other.edge) - return true; - else if (edge == other.edge) { - if (t < other.t) - return true; - } +#if XA_CHECK_T_JUNCTIONS +static bool lineIntersectsPoint(const Vector3 &point, const Vector3 &lineStart, const Vector3 &lineEnd, float *t, float epsilon) { + float tt; + if (!t) + t = &tt; + *t = 0.0f; + if (equal(lineStart, point, epsilon) || equal(lineEnd, point, epsilon)) + return false; // Vertex lies on either line vertices. + const Vector3 v01 = point - lineStart; + const Vector3 v21 = lineEnd - lineStart; + const float l = length(v21); + const float d = length(cross(v01, v21)) / l; + if (!isZero(d, epsilon)) return false; - } -}; + *t = dot(v01, v21) / (l * l); + return *t > kEpsilon && *t < 1.0f - kEpsilon; +} -// Returns nullptr if there were no t-junctions to fix. -static Mesh *meshFixTJunctions(const Mesh &inputMesh, bool *duplicatedEdge, bool *failed, uint32_t *fixedTJunctionsCount) -{ - if (duplicatedEdge) - *duplicatedEdge = false; - if (failed) - *failed = false; - Array<SplitEdge> splitEdges; +// Returns the number of T-junctions found. +static int meshCheckTJunctions(const Mesh &inputMesh) { + int count = 0; const uint32_t vertexCount = inputMesh.vertexCount(); const uint32_t edgeCount = inputMesh.edgeCount(); for (uint32_t v = 0; v < vertexCount; v++) { @@ -3582,155 +2836,130 @@ static Mesh *meshFixTJunctions(const Mesh &inputMesh, bool *duplicatedEdge, bool const Vector3 &edgePos1 = inputMesh.position(inputMesh.vertexAt(meshEdgeIndex0(e))); const Vector3 &edgePos2 = inputMesh.position(inputMesh.vertexAt(meshEdgeIndex1(e))); float t; - if (!lineIntersectsPoint(pos, edgePos1, edgePos2, &t, inputMesh.epsilon())) - continue; - SplitEdge splitEdge; - splitEdge.edge = e; - splitEdge.t = t; - splitEdge.vertex = v; - splitEdges.push_back(splitEdge); + if (lineIntersectsPoint(pos, edgePos1, edgePos2, &t, inputMesh.epsilon())) + count++; } } - if (splitEdges.isEmpty()) - return nullptr; - const uint32_t faceCount = inputMesh.faceCount(); - Mesh *mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, inputMesh.epsilon(), vertexCount + splitEdges.size(), faceCount); - for (uint32_t v = 0; v < vertexCount; v++) - mesh->addVertex(inputMesh.position(v)); - Array<uint32_t> indexArray; - indexArray.reserve(4); - Array<SplitEdge> faceSplitEdges; - faceSplitEdges.reserve(4); - for (uint32_t f = 0; f < faceCount; f++) { - // Find t-junctions in this face. - faceSplitEdges.clear(); - for (uint32_t i = 0; i < splitEdges.size(); i++) { - if (meshEdgeFace(splitEdges[i].edge) == f) - faceSplitEdges.push_back(splitEdges[i]); - } - if (!faceSplitEdges.isEmpty()) { - // Need to split edges in winding order when a single edge has multiple t-junctions. - insertionSort(faceSplitEdges.data(), faceSplitEdges.size()); - indexArray.clear(); - for (Mesh::FaceEdgeIterator it(&inputMesh, f); !it.isDone(); it.advance()) { - indexArray.push_back(it.vertex0()); - for (uint32_t se = 0; se < faceSplitEdges.size(); se++) { - const SplitEdge &splitEdge = faceSplitEdges[se]; - if (splitEdge.edge == it.edge()) - indexArray.push_back(splitEdge.vertex); + return count; +} +#endif + +// References invalid faces and vertices in a mesh. +struct InvalidMeshGeometry { + // If meshFaceGroups is not null, invalid faces have the face group MeshFaceGroups::kInvalid. + // If meshFaceGroups is null, invalid faces are Mesh::isFaceIgnored. + void extract(const Mesh *mesh, const MeshFaceGroups *meshFaceGroups) { + // Copy invalid faces. + m_faces.clear(); + const uint32_t meshFaceCount = mesh->faceCount(); + for (uint32_t f = 0; f < meshFaceCount; f++) { + if ((meshFaceGroups && meshFaceGroups->groupAt(f) == MeshFaceGroups::kInvalid) || (!meshFaceGroups && mesh->isFaceIgnored(f))) + m_faces.push_back(f); + } + // Create *unique* list of vertices of invalid faces. + const uint32_t faceCount = m_faces.size(); + m_indices.resize(faceCount * 3); + const uint32_t approxVertexCount = min(faceCount * 3, mesh->vertexCount()); + m_vertexToSourceVertexMap.clear(); + m_vertexToSourceVertexMap.reserve(approxVertexCount); + HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToVertexMap(MemTag::Mesh, approxVertexCount); + for (uint32_t f = 0; f < faceCount; f++) { + const uint32_t face = m_faces[f]; + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = mesh->vertexAt(face * 3 + i); + uint32_t newVertex = sourceVertexToVertexMap.get(vertex); + if (newVertex == UINT32_MAX) { + newVertex = sourceVertexToVertexMap.add(vertex); + m_vertexToSourceVertexMap.push_back(vertex); } - } - if (!meshCloseHole(mesh, indexArray, Vector3(0.0f))) { - if (failed) - *failed = true; - } - } else { - // No t-junctions in this face. Copy from input mesh. - if (mesh->addFace(&inputMesh.indices()[f * 3]) == Mesh::AddFaceResult::DuplicateEdge) { - if (duplicatedEdge) - *duplicatedEdge = true; + m_indices[f * 3 + i] = newVertex; } } } - if (fixedTJunctionsCount) - *fixedTJunctionsCount = splitEdges.size(); - return mesh; -} -// boundaryLoops are the first edges for each boundary loop. -static void meshGetBoundaryLoops(const Mesh &mesh, Array<uint32_t> &boundaryLoops) -{ - const uint32_t edgeCount = mesh.edgeCount(); - BitArray bitFlags(edgeCount); - bitFlags.zeroOutMemory(); - boundaryLoops.clear(); - // Search for boundary edges. Mark all the edges that belong to the same boundary. - for (uint32_t e = 0; e < edgeCount; e++) { - if (bitFlags.get(e) || !mesh.isBoundaryEdge(e)) - continue; - for (Mesh::BoundaryLoopEdgeIterator it(&mesh, e); !it.isDone(); it.advance()) - bitFlags.set(it.edge()); - boundaryLoops.push_back(e); - } -} + ConstArrayView<uint32_t> faces() const { return m_faces; } + ConstArrayView<uint32_t> indices() const { return m_indices; } + ConstArrayView<uint32_t> vertices() const { return m_vertexToSourceVertexMap; } -struct Progress -{ - Progress(ProgressCategory::Enum category, ProgressFunc func, void *userData, uint32_t maxValue) : value(0), cancel(false), m_category(category), m_func(func), m_userData(userData), m_maxValue(maxValue), m_progress(0) - { +private: + Array<uint32_t> m_faces, m_indices; + Array<uint32_t> m_vertexToSourceVertexMap; // Map face vertices to vertices of the source mesh. +}; + +struct Progress { + Progress(ProgressCategory category, ProgressFunc func, void *userData, uint32_t maxValue) : + cancel(false), m_category(category), m_func(func), m_userData(userData), m_value(0), m_maxValue(maxValue), m_percent(0) { if (m_func) { if (!m_func(category, 0, userData)) cancel = true; } } - ~Progress() - { + ~Progress() { if (m_func) { if (!m_func(m_category, 100, m_userData)) cancel = true; } } - void update() - { - if (!m_func) - return; - m_mutex.lock(); - const uint32_t newProgress = uint32_t(ceilf(value.load() / (float)m_maxValue * 100.0f)); - if (newProgress != m_progress && newProgress < 100) { - m_progress = newProgress; - if (!m_func(m_category, m_progress, m_userData)) - cancel = true; - } - m_mutex.unlock(); + void increment(uint32_t value) { + m_value += value; + update(); } - void setMaxValue(uint32_t maxValue) - { - m_mutex.lock(); + void setMaxValue(uint32_t maxValue) { m_maxValue = maxValue; - m_mutex.unlock(); + update(); } - std::atomic<uint32_t> value; std::atomic<bool> cancel; private: - ProgressCategory::Enum m_category; + void update() { + if (!m_func) + return; + const uint32_t newPercent = uint32_t(ceilf(m_value.load() / (float)m_maxValue.load() * 100.0f)); + if (newPercent != m_percent) { + // Atomic max. + uint32_t oldPercent = m_percent; + while (oldPercent < newPercent && !m_percent.compare_exchange_weak(oldPercent, newPercent)) { + } + if (!m_func(m_category, m_percent, m_userData)) + cancel = true; + } + } + + ProgressCategory m_category; ProgressFunc m_func; void *m_userData; - uint32_t m_maxValue; - uint32_t m_progress; - std::mutex m_mutex; + std::atomic<uint32_t> m_value, m_maxValue, m_percent; }; -struct Spinlock -{ - void lock() { while(m_lock.test_and_set(std::memory_order_acquire)) {} } +struct Spinlock { + void lock() { + while (m_lock.test_and_set(std::memory_order_acquire)) { + } + } void unlock() { m_lock.clear(std::memory_order_release); } private: std::atomic_flag m_lock = ATOMIC_FLAG_INIT; }; -struct TaskGroupHandle -{ +struct TaskGroupHandle { uint32_t value = UINT32_MAX; }; -struct Task -{ - void (*func)(void *userData); - void *userData; +struct Task { + void (*func)(void *groupUserData, void *taskUserData); + void *userData; // Passed to func as taskUserData. }; #if XA_MULTITHREADED -class TaskScheduler -{ +class TaskScheduler { public: - TaskScheduler() : m_shutdown(false) - { + TaskScheduler() : + m_shutdown(false) { m_threadIndex = 0; // Max with current task scheduler usage is 1 per thread + 1 deep nesting, but allow for some slop. m_maxGroups = std::thread::hardware_concurrency() * 4; @@ -3739,6 +2968,7 @@ public: new (&m_groups[i]) TaskGroup(); m_groups[i].free = true; m_groups[i].ref = 0; + m_groups[i].userData = nullptr; } m_workers.resize(std::thread::hardware_concurrency() <= 1 ? 1 : std::thread::hardware_concurrency() - 1); for (uint32_t i = 0; i < m_workers.size(); i++) { @@ -3748,8 +2978,7 @@ public: } } - ~TaskScheduler() - { + ~TaskScheduler() { m_shutdown = true; for (uint32_t i = 0; i < m_workers.size(); i++) { Worker &worker = m_workers[i]; @@ -3767,13 +2996,12 @@ public: XA_FREE(m_groups); } - uint32_t threadCount() const - { + uint32_t threadCount() const { return max(1u, std::thread::hardware_concurrency()); // Including the main thread. } - TaskGroupHandle createTaskGroup(uint32_t reserveSize = 0) - { + // userData is passed to Task::func as groupUserData. + TaskGroupHandle createTaskGroup(void *userData = nullptr, uint32_t reserveSize = 0) { // Claim the first free group. for (uint32_t i = 0; i < m_maxGroups; i++) { TaskGroup &group = m_groups[i]; @@ -3785,6 +3013,8 @@ public: group.queue.clear(); group.queue.reserve(reserveSize); group.queueLock.unlock(); + group.userData = userData; + group.ref = 0; TaskGroupHandle handle; handle.value = i; return handle; @@ -3795,8 +3025,7 @@ public: return handle; } - void run(TaskGroupHandle handle, const Task &task) - { + void run(TaskGroupHandle handle, const Task &task) { XA_DEBUG_ASSERT(handle.value != UINT32_MAX); TaskGroup &group = m_groups[handle.value]; group.queueLock.lock(); @@ -3810,8 +3039,7 @@ public: } } - void wait(TaskGroupHandle *handle) - { + void wait(TaskGroupHandle *handle) { if (handle->value == UINT32_MAX) { XA_DEBUG_ASSERT(false); return; @@ -3826,7 +3054,7 @@ public: group.queueLock.unlock(); if (!task) break; - task->func(task->userData); + task->func(group.userData, task->userData); group.ref--; } // Even though the task queue is empty, workers can still be running tasks. @@ -3839,17 +3067,16 @@ public: static uint32_t currentThreadIndex() { return m_threadIndex; } private: - struct TaskGroup - { + struct TaskGroup { std::atomic<bool> free; Array<Task> queue; // Items are never removed. queueHead is incremented to pop items. uint32_t queueHead = 0; Spinlock queueLock; std::atomic<uint32_t> ref; // Increment when a task is enqueued, decrement when a task finishes. + void *userData; }; - struct Worker - { + struct Worker { std::thread *thread = nullptr; std::mutex mutex; std::condition_variable cv; @@ -3862,12 +3089,11 @@ private: uint32_t m_maxGroups; static thread_local uint32_t m_threadIndex; - static void workerThread(TaskScheduler *scheduler, Worker *worker, uint32_t threadIndex) - { + static void workerThread(TaskScheduler *scheduler, Worker *worker, uint32_t threadIndex) { m_threadIndex = threadIndex; std::unique_lock<std::mutex> lock(worker->mutex); for (;;) { - worker->cv.wait(lock, [=]{ return worker->wakeup.load(); }); + worker->cv.wait(lock, [=] { return worker->wakeup.load(); }); worker->wakeup = false; for (;;) { if (scheduler->m_shutdown) @@ -3889,7 +3115,7 @@ private: } if (!task) break; - task->func(task->userData); + task->func(group->userData, task->userData); group->ref--; } } @@ -3898,44 +3124,39 @@ private: thread_local uint32_t TaskScheduler::m_threadIndex; #else -class TaskScheduler -{ +class TaskScheduler { public: - ~TaskScheduler() - { + ~TaskScheduler() { for (uint32_t i = 0; i < m_groups.size(); i++) destroyGroup({ i }); } - uint32_t threadCount() const - { + uint32_t threadCount() const { return 1; } - TaskGroupHandle createTaskGroup(uint32_t reserveSize = 0) - { + TaskGroupHandle createTaskGroup(void *userData = nullptr, uint32_t reserveSize = 0) { TaskGroup *group = XA_NEW(MemTag::Default, TaskGroup); group->queue.reserve(reserveSize); + group->userData = userData; m_groups.push_back(group); TaskGroupHandle handle; handle.value = m_groups.size() - 1; return handle; } - void run(TaskGroupHandle handle, Task task) - { + void run(TaskGroupHandle handle, Task task) { m_groups[handle.value]->queue.push_back(task); } - void wait(TaskGroupHandle *handle) - { + void wait(TaskGroupHandle *handle) { if (handle->value == UINT32_MAX) { XA_DEBUG_ASSERT(false); return; } TaskGroup *group = m_groups[handle->value]; for (uint32_t i = 0; i < group->queue.size(); i++) - group->queue[i].func(group->queue[i].userData); + group->queue[i].func(group->userData, group->queue[i].userData); group->queue.clear(); destroyGroup(*handle); handle->value = UINT32_MAX; @@ -3944,8 +3165,7 @@ public: static uint32_t currentThreadIndex() { return 0; } private: - void destroyGroup(TaskGroupHandle handle) - { + void destroyGroup(TaskGroupHandle handle) { TaskGroup *group = m_groups[handle.value]; if (group) { group->~TaskGroup(); @@ -3954,9 +3174,9 @@ private: } } - struct TaskGroup - { + struct TaskGroup { Array<Task> queue; + void *userData; }; Array<TaskGroup *> m_groups; @@ -3968,8 +3188,7 @@ const uint8_t TGA_TYPE_RGB = 2; const uint8_t TGA_ORIGIN_UPPER = 0x20; #pragma pack(push, 1) -struct TgaHeader -{ +struct TgaHeader { uint8_t id_length; uint8_t colormap_type; uint8_t image_type; @@ -3986,8 +3205,7 @@ struct TgaHeader }; #pragma pack(pop) -static void WriteTga(const char *filename, const uint8_t *data, uint32_t width, uint32_t height) -{ +static void WriteTga(const char *filename, const uint8_t *data, uint32_t width, uint32_t height) { XA_DEBUG_ASSERT(sizeof(TgaHeader) == TgaHeader::Size); FILE *f; XA_FOPEN(f, filename, "wb"); @@ -4012,12 +3230,10 @@ static void WriteTga(const char *filename, const uint8_t *data, uint32_t width, } #endif -template<typename T> -class ThreadLocal -{ +template <typename T> +class ThreadLocal { public: - ThreadLocal() - { + ThreadLocal() { #if XA_MULTITHREADED const uint32_t n = std::thread::hardware_concurrency(); #else @@ -4028,8 +3244,7 @@ public: new (&m_array[i]) T; } - ~ThreadLocal() - { + ~ThreadLocal() { #if XA_MULTITHREADED const uint32_t n = std::thread::hardware_concurrency(); #else @@ -4040,8 +3255,7 @@ public: XA_FREE(m_array); } - T &get() const - { + T &get() const { return m_array[TaskScheduler::currentThreadIndex()]; } @@ -4049,11 +3263,104 @@ private: T *m_array; }; -class UniformGrid2 -{ +// Implemented as a struct so the temporary arrays can be reused. +struct Triangulator { + // This is doing a simple ear-clipping algorithm that skips invalid triangles. Ideally, we should + // also sort the ears by angle, start with the ones that have the smallest angle and proceed in order. + void triangulatePolygon(ConstArrayView<Vector3> vertices, ConstArrayView<uint32_t> inputIndices, Array<uint32_t> &outputIndices) { + m_polygonVertices.clear(); + m_polygonVertices.reserve(inputIndices.length); + outputIndices.clear(); + if (inputIndices.length == 3) { + // Simple case for triangles. + outputIndices.push_back(inputIndices[0]); + outputIndices.push_back(inputIndices[1]); + outputIndices.push_back(inputIndices[2]); + } else { + // Build 2D polygon projecting vertices onto normal plane. + // Faces are not necesarily planar, this is for example the case, when the face comes from filling a hole. In such cases + // it's much better to use the best fit plane. + Basis basis; + basis.normal = normalize(cross(vertices[inputIndices[1]] - vertices[inputIndices[0]], vertices[inputIndices[2]] - vertices[inputIndices[1]])); + basis.tangent = basis.computeTangent(basis.normal); + basis.bitangent = basis.computeBitangent(basis.normal, basis.tangent); + const uint32_t edgeCount = inputIndices.length; + m_polygonPoints.clear(); + m_polygonPoints.reserve(edgeCount); + m_polygonAngles.clear(); + m_polygonAngles.reserve(edgeCount); + for (uint32_t i = 0; i < inputIndices.length; i++) { + m_polygonVertices.push_back(inputIndices[i]); + const Vector3 &pos = vertices[inputIndices[i]]; + m_polygonPoints.push_back(Vector2(dot(basis.tangent, pos), dot(basis.bitangent, pos))); + } + m_polygonAngles.resize(edgeCount); + while (m_polygonVertices.size() > 2) { + const uint32_t size = m_polygonVertices.size(); + // Update polygon angles. @@ Update only those that have changed. + float minAngle = kPi2; + uint32_t bestEar = 0; // Use first one if none of them is valid. + bool bestIsValid = false; + for (uint32_t i = 0; i < size; i++) { + uint32_t i0 = i; + uint32_t i1 = (i + 1) % size; // Use Sean's polygon interation trick. + uint32_t i2 = (i + 2) % size; + Vector2 p0 = m_polygonPoints[i0]; + Vector2 p1 = m_polygonPoints[i1]; + Vector2 p2 = m_polygonPoints[i2]; + float d = clamp(dot(p0 - p1, p2 - p1) / (length(p0 - p1) * length(p2 - p1)), -1.0f, 1.0f); + float angle = acosf(d); + float area = triangleArea(p0, p1, p2); + if (area < 0.0f) + angle = kPi2 - angle; + m_polygonAngles[i1] = angle; + if (angle < minAngle || !bestIsValid) { + // Make sure this is a valid ear, if not, skip this point. + bool valid = true; + for (uint32_t j = 0; j < size; j++) { + if (j == i0 || j == i1 || j == i2) + continue; + Vector2 p = m_polygonPoints[j]; + if (pointInTriangle(p, p0, p1, p2)) { + valid = false; + break; + } + } + if (valid || !bestIsValid) { + minAngle = angle; + bestEar = i1; + bestIsValid = valid; + } + } + } + // Clip best ear: + const uint32_t i0 = (bestEar + size - 1) % size; + const uint32_t i1 = (bestEar + 0) % size; + const uint32_t i2 = (bestEar + 1) % size; + outputIndices.push_back(m_polygonVertices[i0]); + outputIndices.push_back(m_polygonVertices[i1]); + outputIndices.push_back(m_polygonVertices[i2]); + m_polygonVertices.removeAt(i1); + m_polygonPoints.removeAt(i1); + m_polygonAngles.removeAt(i1); + } + } + } + +private: + static bool pointInTriangle(const Vector2 &p, const Vector2 &a, const Vector2 &b, const Vector2 &c) { + return triangleArea(a, b, p) >= kAreaEpsilon && triangleArea(b, c, p) >= kAreaEpsilon && triangleArea(c, a, p) >= kAreaEpsilon; + } + + Array<int> m_polygonVertices; + Array<float> m_polygonAngles; + Array<Vector2> m_polygonPoints; +}; + +class UniformGrid2 { public: - void reset(const Vector2 *positions, const uint32_t *indices = nullptr, uint32_t reserveEdgeCount = 0) - { + // indices are optional. + void reset(ConstArrayView<Vector2> positions, ConstArrayView<uint32_t> indices = ConstArrayView<uint32_t>(), uint32_t reserveEdgeCount = 0) { m_edges.clear(); if (reserveEdgeCount > 0) m_edges.reserve(reserveEdgeCount); @@ -4062,14 +3369,12 @@ public: m_cellDataOffsets.clear(); } - void append(uint32_t edge) - { + void append(uint32_t edge) { XA_DEBUG_ASSERT(m_cellDataOffsets.isEmpty()); m_edges.push_back(edge); } - bool intersect(Vector2 v1, Vector2 v2, float epsilon) - { + bool intersect(Vector2 v1, Vector2 v2, float epsilon) { const uint32_t edgeCount = m_edges.size(); bool bruteForce = edgeCount <= 20; if (!bruteForce && m_cellDataOffsets.isEmpty()) @@ -4096,8 +3401,7 @@ public: } // If edges is empty, checks for intersection with all edges in the grid. - bool intersect(float epsilon, ConstArrayView<uint32_t> edges = ConstArrayView<uint32_t>(), ConstArrayView<uint32_t> ignoreEdges = ConstArrayView<uint32_t>()) - { + bool intersect(float epsilon, ConstArrayView<uint32_t> edges = ConstArrayView<uint32_t>(), ConstArrayView<uint32_t> ignoreEdges = ConstArrayView<uint32_t>()) { bool bruteForce = m_edges.size() <= 20; if (!bruteForce && m_cellDataOffsets.isEmpty()) bruteForce = !createGrid(); @@ -4167,8 +3471,7 @@ public: } #if XA_DEBUG_EXPORT_BOUNDARY_GRID - void debugExport(const char *filename) - { + void debugExport(const char *filename) { Array<uint8_t> image; image.resize(m_gridWidth * m_gridHeight * 3); for (uint32_t y = 0; y < m_gridHeight; y++) { @@ -4190,8 +3493,7 @@ public: #endif private: - bool createGrid() - { + bool createGrid() { // Compute edge extents. Min will be the grid origin. const uint32_t edgeCount = m_edges.size(); Extents2 edgeExtents; @@ -4202,14 +3504,14 @@ private: edgeExtents.add(edgePosition1(edge)); } m_gridOrigin = edgeExtents.min; - // Size grid to approximately one edge per cell. + // Size grid to approximately one edge per cell in the largest dimension. const Vector2 extentsSize(edgeExtents.max - edgeExtents.min); - m_cellSize = min(extentsSize.x, extentsSize.y) / sqrtf((float)edgeCount); + m_cellSize = max(extentsSize.x, extentsSize.y) / (float)clamp(edgeCount, 32u, 512u); if (m_cellSize <= 0.0f) return false; m_gridWidth = uint32_t(ceilf(extentsSize.x / m_cellSize)); m_gridHeight = uint32_t(ceilf(extentsSize.y / m_cellSize)); - if (m_gridWidth == 0 || m_gridHeight == 0) + if (m_gridWidth <= 1 || m_gridHeight <= 1) return false; // Insert edges into cells. m_cellDataOffsets.resize(m_gridWidth * m_gridHeight); @@ -4243,8 +3545,7 @@ private: return true; } - void computePotentialEdges(Vector2 p1, Vector2 p2) - { + void computePotentialEdges(Vector2 p1, Vector2 p2) { m_potentialEdges.clear(); traverse(p1, p2); for (uint32_t j = 0; j < m_traversedCellOffsets.size(); j++) { @@ -4262,10 +3563,9 @@ private: } // "A Fast Voxel Traversal Algorithm for Ray Tracing" - void traverse(Vector2 p1, Vector2 p2) - { + void traverse(Vector2 p1, Vector2 p2) { const Vector2 dir = p2 - p1; - const Vector2 normal = normalizeSafe(dir, Vector2(0.0f), kEpsilon); + const Vector2 normal = normalizeSafe(dir, Vector2(0.0f)); const int stepX = dir.x >= 0 ? 1 : -1; const int stepY = dir.y >= 0 ? 1 : -1; const uint32_t firstCell[2] = { cellX(p1.x), cellY(p1.y) }; @@ -4284,14 +3584,12 @@ private: if (normal.x > kEpsilon || normal.x < -kEpsilon) { tMaxX = (distToNextCellX * stepX) / normal.x; tDeltaX = (m_cellSize * stepX) / normal.x; - } - else + } else tMaxX = tDeltaX = FLT_MAX; if (normal.y > kEpsilon || normal.y < -kEpsilon) { tMaxY = (distToNextCellY * stepY) / normal.y; tDeltaY = (m_cellSize * stepY) / normal.y; - } - else + } else tMaxY = tDeltaY = FLT_MAX; m_traversedCellOffsets.clear(); m_traversedCellOffsets.push_back(firstCell[0] + firstCell[1] * m_gridWidth); @@ -4318,34 +3616,29 @@ private: } } - uint32_t cellX(float x) const - { + uint32_t cellX(float x) const { return min((uint32_t)max(0.0f, (x - m_gridOrigin.x) / m_cellSize), m_gridWidth - 1u); } - uint32_t cellY(float y) const - { + uint32_t cellY(float y) const { return min((uint32_t)max(0.0f, (y - m_gridOrigin.y) / m_cellSize), m_gridHeight - 1u); } - Vector2 edgePosition0(uint32_t edge) const - { + Vector2 edgePosition0(uint32_t edge) const { return m_positions[vertexAt(meshEdgeIndex0(edge))]; } - Vector2 edgePosition1(uint32_t edge) const - { + Vector2 edgePosition1(uint32_t edge) const { return m_positions[vertexAt(meshEdgeIndex1(edge))]; } - uint32_t vertexAt(uint32_t index) const - { - return m_indices ? m_indices[index] : index; + uint32_t vertexAt(uint32_t index) const { + return m_indices.length > 0 ? m_indices[index] : index; } Array<uint32_t> m_edges; - const Vector2 *m_positions; - const uint32_t *m_indices; // Optional + ConstArrayView<Vector2> m_positions; + ConstArrayView<uint32_t> m_indices; // Optional. Empty if unused. float m_cellSize; Vector2 m_gridOrigin; uint32_t m_gridWidth, m_gridHeight; // in cells @@ -4355,26 +3648,25 @@ private: Array<uint32_t> m_traversedCellOffsets; }; -struct UvMeshChart -{ +struct UvMeshChart { Array<uint32_t> faces; Array<uint32_t> indices; uint32_t material; }; -struct UvMesh -{ +struct UvMesh { UvMeshDecl decl; + BitArray faceIgnore; + Array<uint32_t> faceMaterials; Array<uint32_t> indices; + Array<Vector2> texcoords; // Copied from input and never modified, UvMeshInstance::texcoords are. Used to restore UvMeshInstance::texcoords so packing can be run multiple times. Array<UvMeshChart *> charts; Array<uint32_t> vertexToChartMap; }; -struct UvMeshInstance -{ +struct UvMeshInstance { UvMesh *mesh; Array<Vector2> texcoords; - bool rotateCharts; }; /* @@ -4420,27 +3712,30 @@ struct UvMeshInstance * FRANCE */ namespace opennl { -#define NL_NEW(T) XA_ALLOC(MemTag::OpenNL, T) -#define NL_NEW_ARRAY(T,NB) XA_ALLOC_ARRAY(MemTag::OpenNL, T, NB) -#define NL_RENEW_ARRAY(T,x,NB) XA_REALLOC(MemTag::OpenNL, x, T, NB) -#define NL_DELETE(x) XA_FREE(x); x = nullptr -#define NL_DELETE_ARRAY(x) XA_FREE(x); x = nullptr -#define NL_CLEAR(x, T) memset(x, 0, sizeof(T)); -#define NL_CLEAR_ARRAY(T,x,NB) memset(x, 0, (size_t)(NB)*sizeof(T)) -#define NL_NEW_VECTOR(dim) XA_ALLOC_ARRAY(MemTag::OpenNL, double, dim) -#define NL_DELETE_VECTOR(ptr) XA_FREE(ptr) +#define NL_NEW(T) XA_ALLOC(MemTag::OpenNL, T) +#define NL_NEW_ARRAY(T, NB) XA_ALLOC_ARRAY(MemTag::OpenNL, T, NB) +#define NL_RENEW_ARRAY(T, x, NB) XA_REALLOC(MemTag::OpenNL, x, T, NB) +#define NL_DELETE(x) \ + XA_FREE(x); \ + x = nullptr +#define NL_DELETE_ARRAY(x) \ + XA_FREE(x); \ + x = nullptr +#define NL_CLEAR(x, T) memset(x, 0, sizeof(T)); +#define NL_CLEAR_ARRAY(T, x, NB) memset(x, 0, (size_t)(NB) * sizeof(T)) +#define NL_NEW_VECTOR(dim) XA_ALLOC_ARRAY(MemTag::OpenNL, double, dim) +#define NL_DELETE_VECTOR(ptr) XA_FREE(ptr) struct NLMatrixStruct; -typedef NLMatrixStruct * NLMatrix; +typedef NLMatrixStruct *NLMatrix; typedef void (*NLDestroyMatrixFunc)(NLMatrix M); -typedef void (*NLMultMatrixVectorFunc)(NLMatrix M, const double* x, double* y); +typedef void (*NLMultMatrixVectorFunc)(NLMatrix M, const double *x, double *y); #define NL_MATRIX_SPARSE_DYNAMIC 0x1001 -#define NL_MATRIX_CRS 0x1002 -#define NL_MATRIX_OTHER 0x1006 +#define NL_MATRIX_CRS 0x1002 +#define NL_MATRIX_OTHER 0x1006 -struct NLMatrixStruct -{ +struct NLMatrixStruct { uint32_t m; uint32_t n; uint32_t type; @@ -4450,39 +3745,35 @@ struct NLMatrixStruct /* Dynamic arrays for sparse row/columns */ -struct NLCoeff -{ +struct NLCoeff { uint32_t index; double value; }; -struct NLRowColumn -{ +struct NLRowColumn { uint32_t size; uint32_t capacity; - NLCoeff* coeff; + NLCoeff *coeff; }; /* Compressed Row Storage */ -struct NLCRSMatrix -{ +struct NLCRSMatrix { uint32_t m; uint32_t n; uint32_t type; NLDestroyMatrixFunc destroy_func; NLMultMatrixVectorFunc mult_func; - double* val; - uint32_t* rowptr; - uint32_t* colind; + double *val; + uint32_t *rowptr; + uint32_t *colind; uint32_t nslices; - uint32_t* sliceptr; + uint32_t *sliceptr; }; /* SparseMatrix data structure */ -struct NLSparseMatrix -{ +struct NLSparseMatrix { uint32_t m; uint32_t n; uint32_t type; @@ -4490,25 +3781,23 @@ struct NLSparseMatrix NLMultMatrixVectorFunc mult_func; uint32_t diag_size; uint32_t diag_capacity; - NLRowColumn* row; - NLRowColumn* column; - double* diag; + NLRowColumn *row; + NLRowColumn *column; + double *diag; uint32_t row_capacity; uint32_t column_capacity; }; /* NLContext data structure */ -struct NLBufferBinding -{ - void* base_address; +struct NLBufferBinding { + void *base_address; uint32_t stride; }; -#define NL_BUFFER_ITEM(B,i) *(double*)((void*)((char*)((B).base_address)+((i)*(B).stride))) +#define NL_BUFFER_ITEM(B, i) *(double *)((void *)((char *)((B).base_address) + ((i) * (B).stride))) -struct NLContext -{ +struct NLContext { NLBufferBinding *variable_buffer; double *variable_value; bool *variable_is_locked; @@ -4532,35 +3821,30 @@ struct NLContext double error; }; -static void nlDeleteMatrix(NLMatrix M) -{ +static void nlDeleteMatrix(NLMatrix M) { if (!M) return; M->destroy_func(M); NL_DELETE(M); } -static void nlMultMatrixVector(NLMatrix M, const double* x, double* y) -{ +static void nlMultMatrixVector(NLMatrix M, const double *x, double *y) { M->mult_func(M, x, y); } -static void nlRowColumnConstruct(NLRowColumn* c) -{ +static void nlRowColumnConstruct(NLRowColumn *c) { c->size = 0; c->capacity = 0; c->coeff = nullptr; } -static void nlRowColumnDestroy(NLRowColumn* c) -{ +static void nlRowColumnDestroy(NLRowColumn *c) { NL_DELETE_ARRAY(c->coeff); c->size = 0; c->capacity = 0; } -static void nlRowColumnGrow(NLRowColumn* c) -{ +static void nlRowColumnGrow(NLRowColumn *c) { if (c->capacity != 0) { c->capacity = 2 * c->capacity; c->coeff = NL_RENEW_ARRAY(NLCoeff, c->coeff, c->capacity); @@ -4571,8 +3855,7 @@ static void nlRowColumnGrow(NLRowColumn* c) } } -static void nlRowColumnAdd(NLRowColumn* c, uint32_t index, double value) -{ +static void nlRowColumnAdd(NLRowColumn *c, uint32_t index, double value) { for (uint32_t i = 0; i < c->size; i++) { if (c->coeff[i].index == index) { c->coeff[i].value += value; @@ -4587,8 +3870,7 @@ static void nlRowColumnAdd(NLRowColumn* c, uint32_t index, double value) } /* Does not check whether the index already exists */ -static void nlRowColumnAppend(NLRowColumn* c, uint32_t index, double value) -{ +static void nlRowColumnAppend(NLRowColumn *c, uint32_t index, double value) { if (c->size == c->capacity) nlRowColumnGrow(c); c->coeff[c->size].index = index; @@ -4596,32 +3878,27 @@ static void nlRowColumnAppend(NLRowColumn* c, uint32_t index, double value) c->size++; } -static void nlRowColumnZero(NLRowColumn* c) -{ +static void nlRowColumnZero(NLRowColumn *c) { c->size = 0; } -static void nlRowColumnClear(NLRowColumn* c) -{ +static void nlRowColumnClear(NLRowColumn *c) { c->size = 0; c->capacity = 0; NL_DELETE_ARRAY(c->coeff); } -static int nlCoeffCompare(const void* p1, const void* p2) -{ - return (((NLCoeff*)(p2))->index < ((NLCoeff*)(p1))->index); +static int nlCoeffCompare(const void *p1, const void *p2) { + return (((NLCoeff *)(p2))->index < ((NLCoeff *)(p1))->index); } -static void nlRowColumnSort(NLRowColumn* c) -{ +static void nlRowColumnSort(NLRowColumn *c) { qsort(c->coeff, c->size, sizeof(NLCoeff), nlCoeffCompare); } /* CRSMatrix data structure */ -static void nlCRSMatrixDestroy(NLCRSMatrix* M) -{ +static void nlCRSMatrixDestroy(NLCRSMatrix *M) { NL_DELETE_ARRAY(M->val); NL_DELETE_ARRAY(M->rowptr); NL_DELETE_ARRAY(M->colind); @@ -4631,8 +3908,7 @@ static void nlCRSMatrixDestroy(NLCRSMatrix* M) M->nslices = 0; } -static void nlCRSMatrixMultSlice(NLCRSMatrix* M, const double* x, double* y, uint32_t Ibegin, uint32_t Iend) -{ +static void nlCRSMatrixMultSlice(NLCRSMatrix *M, const double *x, double *y, uint32_t Ibegin, uint32_t Iend) { for (uint32_t i = Ibegin; i < Iend; ++i) { double sum = 0.0; for (uint32_t j = M->rowptr[i]; j < M->rowptr[i + 1]; ++j) @@ -4641,15 +3917,13 @@ static void nlCRSMatrixMultSlice(NLCRSMatrix* M, const double* x, double* y, uin } } -static void nlCRSMatrixMult(NLCRSMatrix* M, const double* x, double* y) -{ +static void nlCRSMatrixMult(NLCRSMatrix *M, const double *x, double *y) { int nslices = (int)(M->nslices); for (int slice = 0; slice < nslices; ++slice) nlCRSMatrixMultSlice(M, x, y, M->sliceptr[slice], M->sliceptr[slice + 1]); } -static void nlCRSMatrixConstruct(NLCRSMatrix* M, uint32_t m, uint32_t n, uint32_t nnz, uint32_t nslices) -{ +static void nlCRSMatrixConstruct(NLCRSMatrix *M, uint32_t m, uint32_t n, uint32_t nnz, uint32_t nslices) { M->m = m; M->n = n; M->type = NL_MATRIX_CRS; @@ -4668,22 +3942,19 @@ static void nlCRSMatrixConstruct(NLCRSMatrix* M, uint32_t m, uint32_t n, uint32_ /* SparseMatrix data structure */ -static void nlSparseMatrixDestroyRowColumns(NLSparseMatrix* M) -{ +static void nlSparseMatrixDestroyRowColumns(NLSparseMatrix *M) { for (uint32_t i = 0; i < M->m; i++) nlRowColumnDestroy(&(M->row[i])); NL_DELETE_ARRAY(M->row); } -static void nlSparseMatrixDestroy(NLSparseMatrix* M) -{ +static void nlSparseMatrixDestroy(NLSparseMatrix *M) { XA_DEBUG_ASSERT(M->type == NL_MATRIX_SPARSE_DYNAMIC); nlSparseMatrixDestroyRowColumns(M); NL_DELETE_ARRAY(M->diag); } -static void nlSparseMatrixAdd(NLSparseMatrix* M, uint32_t i, uint32_t j, double value) -{ +static void nlSparseMatrixAdd(NLSparseMatrix *M, uint32_t i, uint32_t j, double value) { XA_DEBUG_ASSERT(i >= 0 && i <= M->m - 1); XA_DEBUG_ASSERT(j >= 0 && j <= M->n - 1); if (i == j) @@ -4692,24 +3963,21 @@ static void nlSparseMatrixAdd(NLSparseMatrix* M, uint32_t i, uint32_t j, double } /* Returns the number of non-zero coefficients */ -static uint32_t nlSparseMatrixNNZ(NLSparseMatrix* M) -{ +static uint32_t nlSparseMatrixNNZ(NLSparseMatrix *M) { uint32_t nnz = 0; for (uint32_t i = 0; i < M->m; i++) nnz += M->row[i].size; return nnz; } -static void nlSparseMatrixSort(NLSparseMatrix* M) -{ +static void nlSparseMatrixSort(NLSparseMatrix *M) { for (uint32_t i = 0; i < M->m; i++) nlRowColumnSort(&(M->row[i])); } /* SparseMatrix x Vector routines, internal helper routines */ -static void nlSparseMatrix_mult_rows(NLSparseMatrix* A, const double* x, double* y) -{ +static void nlSparseMatrix_mult_rows(NLSparseMatrix *A, const double *x, double *y) { /* * Note: OpenMP does not like unsigned ints * (causes some floating point exceptions), @@ -4717,8 +3985,8 @@ static void nlSparseMatrix_mult_rows(NLSparseMatrix* A, const double* x, double* * indices. */ int m = (int)(A->m); - NLCoeff* c = nullptr; - NLRowColumn* Ri = nullptr; + NLCoeff *c = nullptr; + NLRowColumn *Ri = nullptr; for (int i = 0; i < m; i++) { Ri = &(A->row[i]); y[i] = 0; @@ -4729,14 +3997,12 @@ static void nlSparseMatrix_mult_rows(NLSparseMatrix* A, const double* x, double* } } -static void nlSparseMatrixMult(NLSparseMatrix* A, const double* x, double* y) -{ +static void nlSparseMatrixMult(NLSparseMatrix *A, const double *x, double *y) { XA_DEBUG_ASSERT(A->type == NL_MATRIX_SPARSE_DYNAMIC); nlSparseMatrix_mult_rows(A, x, y); } -static void nlSparseMatrixConstruct(NLSparseMatrix* M, uint32_t m, uint32_t n) -{ +static void nlSparseMatrixConstruct(NLSparseMatrix *M, uint32_t m, uint32_t n) { M->m = m; M->n = n; M->type = NL_MATRIX_SPARSE_DYNAMIC; @@ -4756,24 +4022,23 @@ static void nlSparseMatrixConstruct(NLSparseMatrix* M, uint32_t m, uint32_t n) NL_CLEAR_ARRAY(double, M->diag, M->diag_size); } -static NLMatrix nlCRSMatrixNewFromSparseMatrix(NLSparseMatrix* M) -{ +static NLMatrix nlCRSMatrixNewFromSparseMatrix(NLSparseMatrix *M) { uint32_t nnz = nlSparseMatrixNNZ(M); uint32_t nslices = 8; /* TODO: get number of cores */ uint32_t slice, cur_bound, cur_NNZ, cur_row; uint32_t k; uint32_t slice_size = nnz / nslices; - NLCRSMatrix* CRS = NL_NEW(NLCRSMatrix); + NLCRSMatrix *CRS = NL_NEW(NLCRSMatrix); NL_CLEAR(CRS, NLCRSMatrix); nlCRSMatrixConstruct(CRS, M->m, M->n, nnz, nslices); nlSparseMatrixSort(M); /* Convert matrix to CRS format */ k = 0; for (uint32_t i = 0; i < M->m; ++i) { - NLRowColumn* Ri = &(M->row[i]); + NLRowColumn *Ri = &(M->row[i]); CRS->rowptr[i] = k; for (uint32_t ij = 0; ij < Ri->size; ij++) { - NLCoeff* c = &(Ri->coeff[ij]); + NLCoeff *c = &(Ri->coeff[ij]); CRS->val[k] = c->value; CRS->colind[k] = c->index; ++k; @@ -4799,19 +4064,17 @@ static NLMatrix nlCRSMatrixNewFromSparseMatrix(NLSparseMatrix* M) return (NLMatrix)CRS; } -static void nlMatrixCompress(NLMatrix* M) -{ +static void nlMatrixCompress(NLMatrix *M) { NLMatrix CRS = nullptr; if ((*M)->type != NL_MATRIX_SPARSE_DYNAMIC) return; - CRS = nlCRSMatrixNewFromSparseMatrix((NLSparseMatrix*)*M); + CRS = nlCRSMatrixNewFromSparseMatrix((NLSparseMatrix *)*M); nlDeleteMatrix(*M); *M = CRS; } -static NLContext *nlNewContext() -{ - NLContext* result = NL_NEW(NLContext); +static NLContext *nlNewContext() { + NLContext *result = NL_NEW(NLContext); NL_CLEAR(result, NLContext); result->max_iterations = 100; result->threshold = 1e-6; @@ -4820,8 +4083,7 @@ static NLContext *nlNewContext() return result; } -static void nlDeleteContext(NLContext *context) -{ +static void nlDeleteContext(NLContext *context) { nlDeleteMatrix(context->M); context->M = nullptr; nlDeleteMatrix(context->P); @@ -4839,22 +4101,19 @@ static void nlDeleteContext(NLContext *context) NL_DELETE(context); } -static double ddot(int n, const double *x, const double *y) -{ +static double ddot(int n, const double *x, const double *y) { double sum = 0.0; for (int i = 0; i < n; i++) sum += x[i] * y[i]; return sum; } -static void daxpy(int n, double a, const double *x, double *y) -{ +static void daxpy(int n, double a, const double *x, double *y) { for (int i = 0; i < n; i++) y[i] = a * x[i] + y[i]; } -static void dscal(int n, double a, double *x) -{ +static void dscal(int n, double a, double *x) { for (int i = 0; i < n; i++) x[i] *= a; } @@ -4877,17 +4136,16 @@ static void dscal(int n, double a, double *x) * versions of matrix x vector product (CPU/GPU, sparse/dense ...) */ -static uint32_t nlSolveSystem_PRE_CG(NLMatrix M, NLMatrix P, double* b, double* x, double eps, uint32_t max_iter, double *sq_bnorm, double *sq_rnorm) -{ - int N = (int)M->n; - double* r = NL_NEW_VECTOR(N); - double* d = NL_NEW_VECTOR(N); - double* h = NL_NEW_VECTOR(N); +static uint32_t nlSolveSystem_PRE_CG(NLMatrix M, NLMatrix P, double *b, double *x, double eps, uint32_t max_iter, double *sq_bnorm, double *sq_rnorm) { + int N = (int)M->n; + double *r = NL_NEW_VECTOR(N); + double *d = NL_NEW_VECTOR(N); + double *h = NL_NEW_VECTOR(N); double *Ad = h; uint32_t its = 0; double rh, alpha, beta; double b_square = ddot(N, b, b); - double err = eps * eps*b_square; + double err = eps * eps * b_square; double curr_err; nlMultMatrixVector(M, x, r); daxpy(N, -1., b, r); @@ -4917,13 +4175,12 @@ static uint32_t nlSolveSystem_PRE_CG(NLMatrix M, NLMatrix P, double* b, double* return its; } -static uint32_t nlSolveSystemIterative(NLContext *context, NLMatrix M, NLMatrix P, double* b_in, double* x_in, double eps, uint32_t max_iter) -{ +static uint32_t nlSolveSystemIterative(NLContext *context, NLMatrix M, NLMatrix P, double *b_in, double *x_in, double eps, uint32_t max_iter) { uint32_t result = 0; double rnorm = 0.0; double bnorm = 0.0; - double* b = b_in; - double* x = x_in; + double *b = b_in; + double *x = x_in; XA_DEBUG_ASSERT(M->m == M->n); double sq_bnorm, sq_rnorm; result = nlSolveSystem_PRE_CG(M, P, b, x, eps, max_iter, &sq_bnorm, &sq_rnorm); @@ -4938,10 +4195,9 @@ static uint32_t nlSolveSystemIterative(NLContext *context, NLMatrix M, NLMatrix return result; } -static bool nlSolveIterative(NLContext *context) -{ - double* b = context->b; - double* x = context->x; +static bool nlSolveIterative(NLContext *context) { + double *b = context->b; + double *x = context->x; uint32_t n = context->n; NLMatrix M = context->M; NLMatrix P = context->P; @@ -4953,34 +4209,30 @@ static bool nlSolveIterative(NLContext *context) return true; } -struct NLJacobiPreconditioner -{ +struct NLJacobiPreconditioner { uint32_t m; uint32_t n; uint32_t type; NLDestroyMatrixFunc destroy_func; NLMultMatrixVectorFunc mult_func; - double* diag_inv; + double *diag_inv; }; -static void nlJacobiPreconditionerDestroy(NLJacobiPreconditioner* M) -{ +static void nlJacobiPreconditionerDestroy(NLJacobiPreconditioner *M) { NL_DELETE_ARRAY(M->diag_inv); } -static void nlJacobiPreconditionerMult(NLJacobiPreconditioner* M, const double* x, double* y) -{ +static void nlJacobiPreconditionerMult(NLJacobiPreconditioner *M, const double *x, double *y) { for (uint32_t i = 0; i < M->n; ++i) y[i] = x[i] * M->diag_inv[i]; } -static NLMatrix nlNewJacobiPreconditioner(NLMatrix M_in) -{ - NLSparseMatrix* M = nullptr; - NLJacobiPreconditioner* result = nullptr; +static NLMatrix nlNewJacobiPreconditioner(NLMatrix M_in) { + NLSparseMatrix *M = nullptr; + NLJacobiPreconditioner *result = nullptr; XA_DEBUG_ASSERT(M_in->type == NL_MATRIX_SPARSE_DYNAMIC); XA_DEBUG_ASSERT(M_in->m == M_in->n); - M = (NLSparseMatrix*)M_in; + M = (NLSparseMatrix *)M_in; result = NL_NEW(NLJacobiPreconditioner); NL_CLEAR(result, NLJacobiPreconditioner); result->m = M->m; @@ -4998,8 +4250,7 @@ static NLMatrix nlNewJacobiPreconditioner(NLMatrix M_in) #define NL_NB_VARIABLES 0x101 #define NL_MAX_ITERATIONS 0x103 -static void nlSolverParameteri(NLContext *context, uint32_t pname, int param) -{ +static void nlSolverParameteri(NLContext *context, uint32_t pname, int param) { if (pname == NL_NB_VARIABLES) { XA_DEBUG_ASSERT(param > 0); context->nb_variables = (uint32_t)param; @@ -5010,26 +4261,22 @@ static void nlSolverParameteri(NLContext *context, uint32_t pname, int param) } } -static void nlSetVariable(NLContext *context, uint32_t index, double value) -{ +static void nlSetVariable(NLContext *context, uint32_t index, double value) { XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1); NL_BUFFER_ITEM(context->variable_buffer[0], index) = value; } -static double nlGetVariable(NLContext *context, uint32_t index) -{ +static double nlGetVariable(NLContext *context, uint32_t index) { XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1); return NL_BUFFER_ITEM(context->variable_buffer[0], index); } -static void nlLockVariable(NLContext *context, uint32_t index) -{ +static void nlLockVariable(NLContext *context, uint32_t index) { XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1); context->variable_is_locked[index] = true; } -static void nlVariablesToVector(NLContext *context) -{ +static void nlVariablesToVector(NLContext *context) { uint32_t n = context->n; XA_DEBUG_ASSERT(context->x); for (uint32_t k = 0; k < context->nb_systems; ++k) { @@ -5044,8 +4291,7 @@ static void nlVariablesToVector(NLContext *context) } } -static void nlVectorToVariables(NLContext *context) -{ +static void nlVectorToVariables(NLContext *context) { uint32_t n = context->n; XA_DEBUG_ASSERT(context->x); for (uint32_t k = 0; k < context->nb_systems; ++k) { @@ -5060,8 +4306,7 @@ static void nlVectorToVariables(NLContext *context) } } -static void nlCoefficient(NLContext *context, uint32_t index, double value) -{ +static void nlCoefficient(NLContext *context, uint32_t index, double value) { XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1); if (context->variable_is_locked[index]) { /* @@ -5078,12 +4323,11 @@ static void nlCoefficient(NLContext *context, uint32_t index, double value) } } -#define NL_SYSTEM 0x0 -#define NL_MATRIX 0x1 -#define NL_ROW 0x2 +#define NL_SYSTEM 0x0 +#define NL_MATRIX 0x1 +#define NL_ROW 0x2 -static void nlBegin(NLContext *context, uint32_t prim) -{ +static void nlBegin(NLContext *context, uint32_t prim) { if (prim == NL_SYSTEM) { XA_DEBUG_ASSERT(context->nb_variables > 0); context->variable_buffer = NL_NEW_ARRAY(NLBufferBinding, context->nb_systems); @@ -5092,8 +4336,8 @@ static void nlBegin(NLContext *context, uint32_t prim) NL_CLEAR_ARRAY(double, context->variable_value, context->nb_variables * context->nb_systems); for (uint32_t k = 0; k < context->nb_systems; ++k) { context->variable_buffer[k].base_address = - context->variable_value + - k * context->nb_variables; + context->variable_value + + k * context->nb_variables; context->variable_buffer[k].stride = sizeof(double); } context->variable_is_locked = NL_NEW_ARRAY(bool, context->nb_variables); @@ -5116,11 +4360,11 @@ static void nlBegin(NLContext *context, uint32_t prim) context->max_iterations = n * 5; context->M = (NLMatrix)(NL_NEW(NLSparseMatrix)); NL_CLEAR(context->M, NLSparseMatrix); - nlSparseMatrixConstruct((NLSparseMatrix*)(context->M), n, n); - context->x = NL_NEW_ARRAY(double, n*context->nb_systems); - NL_CLEAR_ARRAY(double, context->x, n*context->nb_systems); - context->b = NL_NEW_ARRAY(double, n*context->nb_systems); - NL_CLEAR_ARRAY(double, context->b, n*context->nb_systems); + nlSparseMatrixConstruct((NLSparseMatrix *)(context->M), n, n); + context->x = NL_NEW_ARRAY(double, n * context->nb_systems); + NL_CLEAR_ARRAY(double, context->x, n * context->nb_systems); + context->b = NL_NEW_ARRAY(double, n * context->nb_systems); + NL_CLEAR_ARRAY(double, context->b, n * context->nb_systems); nlVariablesToVector(context); nlRowColumnConstruct(&context->af); nlRowColumnConstruct(&context->al); @@ -5131,16 +4375,15 @@ static void nlBegin(NLContext *context, uint32_t prim) } } -static void nlEnd(NLContext *context, uint32_t prim) -{ +static void nlEnd(NLContext *context, uint32_t prim) { if (prim == NL_MATRIX) { nlRowColumnClear(&context->af); nlRowColumnClear(&context->al); } else if (prim == NL_ROW) { - NLRowColumn* af = &context->af; - NLRowColumn* al = &context->al; - NLSparseMatrix* M = (NLSparseMatrix*)context->M; - double* b = context->b; + NLRowColumn *af = &context->af; + NLRowColumn *al = &context->al; + NLSparseMatrix *M = (NLSparseMatrix *)context->M; + double *b = context->b; uint32_t nf = af->size; uint32_t nl = al->size; uint32_t n = context->n; @@ -5161,14 +4404,13 @@ static void nlEnd(NLContext *context, uint32_t prim) S += al->coeff[jj].value * NL_BUFFER_ITEM(context->variable_buffer[k], j); } for (uint32_t jj = 0; jj < nf; jj++) - b[k*n + af->coeff[jj].index] -= af->coeff[jj].value * S; + b[k * n + af->coeff[jj].index] -= af->coeff[jj].value * S; } context->current_row++; } } -static bool nlSolve(NLContext *context) -{ +static bool nlSolve(NLContext *context) { nlDeleteMatrix(context->P); context->P = nlNewJacobiPreconditioner(context->M); nlMatrixCompress(&context->M); @@ -5179,11 +4421,9 @@ static bool nlSolve(NLContext *context) } // namespace opennl namespace raster { -class ClippedTriangle -{ +class ClippedTriangle { public: - ClippedTriangle(const Vector2 &a, const Vector2 &b, const Vector2 &c) - { + ClippedTriangle(const Vector2 &a, const Vector2 &b, const Vector2 &c) { m_numVertices = 3; m_activeVertexBuffer = 0; m_verticesA[0] = a; @@ -5194,20 +4434,20 @@ public: m_area = 0; } - void clipHorizontalPlane(float offset, float clipdirection) - { - Vector2 *v = m_vertexBuffers[m_activeVertexBuffer]; + void clipHorizontalPlane(float offset, float clipdirection) { + Vector2 *v = m_vertexBuffers[m_activeVertexBuffer]; m_activeVertexBuffer ^= 1; Vector2 *v2 = m_vertexBuffers[m_activeVertexBuffer]; v[m_numVertices] = v[0]; - float dy2, dy1 = offset - v[0].y; - int dy2in, dy1in = clipdirection * dy1 >= 0; - uint32_t p = 0; + float dy2, dy1 = offset - v[0].y; + int dy2in, dy1in = clipdirection * dy1 >= 0; + uint32_t p = 0; for (uint32_t k = 0; k < m_numVertices; k++) { - dy2 = offset - v[k + 1].y; + dy2 = offset - v[k + 1].y; dy2in = clipdirection * dy2 >= 0; - if (dy1in) v2[p++] = v[k]; - if ( dy1in + dy2in == 1 ) { // not both in/out + if (dy1in) + v2[p++] = v[k]; + if (dy1in + dy2in == 1) { // not both in/out float dx = v[k + 1].x - v[k].x; float dy = v[k + 1].y - v[k].y; v2[p++] = Vector2(v[k].x + dy1 * (dx / dy), offset); @@ -5218,20 +4458,20 @@ public: m_numVertices = p; } - void clipVerticalPlane(float offset, float clipdirection) - { - Vector2 *v = m_vertexBuffers[m_activeVertexBuffer]; + void clipVerticalPlane(float offset, float clipdirection) { + Vector2 *v = m_vertexBuffers[m_activeVertexBuffer]; m_activeVertexBuffer ^= 1; Vector2 *v2 = m_vertexBuffers[m_activeVertexBuffer]; v[m_numVertices] = v[0]; - float dx2, dx1 = offset - v[0].x; - int dx2in, dx1in = clipdirection * dx1 >= 0; - uint32_t p = 0; + float dx2, dx1 = offset - v[0].x; + int dx2in, dx1in = clipdirection * dx1 >= 0; + uint32_t p = 0; for (uint32_t k = 0; k < m_numVertices; k++) { dx2 = offset - v[k + 1].x; dx2in = clipdirection * dx2 >= 0; - if (dx1in) v2[p++] = v[k]; - if ( dx1in + dx2in == 1 ) { // not both in/out + if (dx1in) + v2[p++] = v[k]; + if (dx1in + dx2in == 1) { // not both in/out float dx = v[k + 1].x - v[k].x; float dy = v[k + 1].y - v[k].y; v2[p++] = Vector2(offset, v[k].y + dx1 * (dy / dx)); @@ -5242,9 +4482,8 @@ public: m_numVertices = p; } - void computeArea() - { - Vector2 *v = m_vertexBuffers[m_activeVertexBuffer]; + void computeArea() { + Vector2 *v = m_vertexBuffers[m_activeVertexBuffer]; v[m_numVertices] = v[0]; m_area = 0; float centroidx = 0, centroidy = 0; @@ -5258,8 +4497,7 @@ public: m_area = 0.5f * fabsf(m_area); } - void clipAABox(float x0, float y0, float x1, float y1) - { + void clipAABox(float x0, float y0, float x1, float y1) { clipVerticalPlane(x0, -1); clipHorizontalPlane(y0, -1); clipVerticalPlane(x1, 1); @@ -5267,8 +4505,7 @@ public: computeArea(); } - float area() const - { + float area() const { return m_area; } @@ -5285,10 +4522,9 @@ private: typedef bool (*SamplingCallback)(void *param, int x, int y); /// A triangle for rasterization. -struct Triangle -{ - Triangle(const Vector2 &_v0, const Vector2 &_v1, const Vector2 &_v2) : v1(_v0), v2(_v2), v3(_v1) - { +struct Triangle { + Triangle(const Vector2 &_v0, const Vector2 &_v1, const Vector2 &_v2) : + v1(_v0), v2(_v2), v3(_v1), n1(0.0f), n2(0.0f), n3(0.0f) { // make sure every triangle is front facing. flipBackface(); // Compute deltas. @@ -5296,8 +4532,7 @@ struct Triangle computeUnitInwardNormals(); } - bool isValid() - { + bool isValid() { const Vector2 e0 = v3 - v1; const Vector2 e1 = v2 - v1; const float area = e0.y * e1.x - e1.y * e0.x; @@ -5305,18 +4540,17 @@ struct Triangle } // extents has to be multiple of BK_SIZE!! - bool drawAA(const Vector2 &extents, SamplingCallback cb, void *param) - { - const float PX_INSIDE = 1.0f/sqrtf(2.0f); - const float PX_OUTSIDE = -1.0f/sqrtf(2.0f); + bool drawAA(const Vector2 &extents, SamplingCallback cb, void *param) { + const float PX_INSIDE = 1.0f / sqrtf(2.0f); + const float PX_OUTSIDE = -1.0f / sqrtf(2.0f); const float BK_SIZE = 8; - const float BK_INSIDE = sqrtf(BK_SIZE*BK_SIZE/2.0f); - const float BK_OUTSIDE = -sqrtf(BK_SIZE*BK_SIZE/2.0f); + const float BK_INSIDE = sqrtf(BK_SIZE * BK_SIZE / 2.0f); + const float BK_OUTSIDE = -sqrtf(BK_SIZE * BK_SIZE / 2.0f); // Bounding rectangle float minx = floorf(max(min3(v1.x, v2.x, v3.x), 0.0f)); float miny = floorf(max(min3(v1.y, v2.y, v3.y), 0.0f)); - float maxx = ceilf( min(max3(v1.x, v2.x, v3.x), extents.x - 1.0f)); - float maxy = ceilf( min(max3(v1.y, v2.y, v3.y), extents.y - 1.0f)); + float maxx = ceilf(min(max3(v1.x, v2.x, v3.x), extents.x - 1.0f)); + float maxy = ceilf(min(max3(v1.y, v2.y, v3.y), extents.y - 1.0f)); // There's no reason to align the blocks to the viewport, instead we align them to the origin of the triangle bounds. minx = floorf(minx); miny = floorf(miny); @@ -5341,9 +4575,10 @@ struct Triangle float bC = C2 + n2.x * xc + n2.y * yc; float cC = C3 + n3.x * xc + n3.y * yc; // Skip block when outside an edge - if ( (aC <= BK_OUTSIDE) || (bC <= BK_OUTSIDE) || (cC <= BK_OUTSIDE) ) continue; + if ((aC <= BK_OUTSIDE) || (bC <= BK_OUTSIDE) || (cC <= BK_OUTSIDE)) + continue; // Accept whole block when totally covered - if ( (aC >= BK_INSIDE) && (bC >= BK_INSIDE) && (cC >= BK_INSIDE) ) { + if ((aC >= BK_INSIDE) && (bC >= BK_INSIDE) && (cC >= BK_INSIDE)) { for (float y = y0; y < y0 + BK_SIZE; y++) { for (float x = x0; x < x0 + BK_SIZE; x++) { if (!cb(param, (int)x, (int)y)) @@ -5386,10 +4621,9 @@ struct Triangle } private: - void flipBackface() - { + void flipBackface() { // check if triangle is backfacing, if so, swap two vertices - if ( ((v3.x - v1.x) * (v2.y - v1.y) - (v3.y - v1.y) * (v2.x - v1.x)) < 0 ) { + if (((v3.x - v1.x) * (v2.y - v1.y) - (v3.y - v1.y) * (v2.x - v1.x)) < 0) { Vector2 hv = v1; v1 = v2; v2 = hv; // swap pos @@ -5397,8 +4631,7 @@ private: } // compute unit inward normals for each edge. - void computeUnitInwardNormals() - { + void computeUnitInwardNormals() { n1 = v1 - v2; n1 = Vector2(-n1.y, n1.x); n1 = n1 * (1.0f / sqrtf(dot(n1, n1))); @@ -5416,8 +4649,7 @@ private: }; // Process the given triangle. Returns false if rasterization was interrupted by the callback. -static bool drawTriangle(const Vector2 &extents, const Vector2 v[3], SamplingCallback cb, void *param) -{ +static bool drawTriangle(const Vector2 &extents, const Vector2 v[3], SamplingCallback cb, void *param) { Triangle tri(v[0], v[1], v[2]); // @@ It would be nice to have a conservative drawing mode that enlarges the triangle extents by one texel and is able to handle degenerate triangles. // @@ Maybe the simplest thing to do would be raster triangle edges. @@ -5432,22 +4664,19 @@ namespace segment { // - Insertion is o(n) // - Smallest element goes at the end, so that popping it is o(1). -struct CostQueue -{ - CostQueue(uint32_t size = UINT32_MAX) : m_maxSize(size), m_pairs(MemTag::SegmentAtlasChartCandidates) {} +struct CostQueue { + CostQueue(uint32_t size = UINT32_MAX) : + m_maxSize(size), m_pairs(MemTag::SegmentAtlasChartCandidates) {} - float peekCost() const - { + float peekCost() const { return m_pairs.back().cost; } - uint32_t peekFace() const - { + uint32_t peekFace() const { return m_pairs.back().face; } - void push(float cost, uint32_t face) - { + void push(float cost, uint32_t face) { const Pair p = { cost, face }; if (m_pairs.isEmpty() || cost < peekCost()) m_pairs.push_back(p); @@ -5464,29 +4693,25 @@ struct CostQueue } } - uint32_t pop() - { + uint32_t pop() { XA_DEBUG_ASSERT(!m_pairs.isEmpty()); uint32_t f = m_pairs.back().face; m_pairs.pop_back(); return f; } - XA_INLINE void clear() - { + XA_INLINE void clear() { m_pairs.clear(); } - XA_INLINE uint32_t count() const - { + XA_INLINE uint32_t count() const { return m_pairs.size(); } private: const uint32_t m_maxSize; - struct Pair - { + struct Pair { float cost; uint32_t face; }; @@ -5494,25 +4719,27 @@ private: Array<Pair> m_pairs; }; -struct AtlasData -{ +struct AtlasData { ChartOptions options; const Mesh *mesh = nullptr; Array<float> edgeDihedralAngles; Array<float> edgeLengths; Array<float> faceAreas; + Array<float> faceUvAreas; // Can be negative. Array<Vector3> faceNormals; BitArray isFaceInChart; - AtlasData() : edgeDihedralAngles(MemTag::SegmentAtlasMeshData), edgeLengths(MemTag::SegmentAtlasMeshData), faceAreas(MemTag::SegmentAtlasMeshData), faceNormals(MemTag::SegmentAtlasMeshData) {} + AtlasData() : + edgeDihedralAngles(MemTag::SegmentAtlasMeshData), edgeLengths(MemTag::SegmentAtlasMeshData), faceAreas(MemTag::SegmentAtlasMeshData), faceNormals(MemTag::SegmentAtlasMeshData) {} - void compute() - { + void compute() { const uint32_t faceCount = mesh->faceCount(); const uint32_t edgeCount = mesh->edgeCount(); edgeDihedralAngles.resize(edgeCount); edgeLengths.resize(edgeCount); faceAreas.resize(faceCount); + if (options.useInputMeshUvs) + faceUvAreas.resize(faceCount); faceNormals.resize(faceCount); isFaceInChart.resize(faceCount); isFaceInChart.zeroOutMemory(); @@ -5526,6 +4753,8 @@ struct AtlasData } faceAreas[f] = mesh->computeFaceArea(f); XA_DEBUG_ASSERT(faceAreas[f] > 0.0f); + if (options.useInputMeshUvs) + faceUvAreas[f] = mesh->computeFaceParametricArea(f); faceNormals[f] = mesh->computeFaceNormal(f); } for (uint32_t face = 0; face < faceCount; face++) { @@ -5543,19 +4772,109 @@ struct AtlasData } }; +// If MeshDecl::vertexUvData is set on input meshes, find charts by floodfilling faces in world/model space without crossing UV seams. +struct OriginalUvCharts { + OriginalUvCharts(AtlasData &data) : + m_data(data) {} + uint32_t chartCount() const { return m_charts.size(); } + const Basis &chartBasis(uint32_t chartIndex) const { return m_chartBasis[chartIndex]; } + + ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const { + const Chart &chart = m_charts[chartIndex]; + return ConstArrayView<uint32_t>(&m_chartFaces[chart.firstFace], chart.faceCount); + } + + void compute() { + m_charts.clear(); + m_chartFaces.clear(); + const Mesh *mesh = m_data.mesh; + const uint32_t faceCount = mesh->faceCount(); + for (uint32_t f = 0; f < faceCount; f++) { + if (m_data.isFaceInChart.get(f)) + continue; + if (isZero(m_data.faceUvAreas[f], kAreaEpsilon)) + continue; // Face must have valid UVs. + // Found an unassigned face, create a new chart. + Chart chart; + chart.firstFace = m_chartFaces.size(); + chart.faceCount = 1; + m_chartFaces.push_back(f); + m_data.isFaceInChart.set(f); + floodfillFaces(chart); + m_charts.push_back(chart); + } + // Compute basis for each chart. + m_chartBasis.resize(m_charts.size()); + for (uint32_t c = 0; c < m_charts.size(); c++) { + const Chart &chart = m_charts[c]; + m_tempPoints.resize(chart.faceCount * 3); + for (uint32_t f = 0; f < chart.faceCount; f++) { + const uint32_t face = m_chartFaces[chart.firstFace + f]; + for (uint32_t i = 0; i < 3; i++) + m_tempPoints[f * 3 + i] = m_data.mesh->position(m_data.mesh->vertexAt(face * 3 + i)); + } + Fit::computeBasis(m_tempPoints, &m_chartBasis[c]); + } + } + +private: + struct Chart { + uint32_t firstFace, faceCount; + }; + + void floodfillFaces(Chart &chart) { + const bool isFaceAreaNegative = m_data.faceUvAreas[m_chartFaces[chart.firstFace]] < 0.0f; + for (;;) { + bool newFaceAdded = false; + const uint32_t faceCount = chart.faceCount; + for (uint32_t f = 0; f < faceCount; f++) { + const uint32_t sourceFace = m_chartFaces[chart.firstFace + f]; + for (Mesh::FaceEdgeIterator edgeIt(m_data.mesh, sourceFace); !edgeIt.isDone(); edgeIt.advance()) { + const uint32_t face = edgeIt.oppositeFace(); + if (face == UINT32_MAX) + continue; // Boundary edge. + if (m_data.isFaceInChart.get(face)) + continue; // Already assigned to a chart. + if (isZero(m_data.faceUvAreas[face], kAreaEpsilon)) + continue; // Face must have valid UVs. + if ((m_data.faceUvAreas[face] < 0.0f) != isFaceAreaNegative) + continue; // Face winding is opposite of the first chart face. + const Vector2 &uv0 = m_data.mesh->texcoord(edgeIt.vertex0()); + const Vector2 &uv1 = m_data.mesh->texcoord(edgeIt.vertex1()); + const Vector2 &ouv0 = m_data.mesh->texcoord(m_data.mesh->vertexAt(meshEdgeIndex0(edgeIt.oppositeEdge()))); + const Vector2 &ouv1 = m_data.mesh->texcoord(m_data.mesh->vertexAt(meshEdgeIndex1(edgeIt.oppositeEdge()))); + if (!equal(uv0, ouv1, m_data.mesh->epsilon()) || !equal(uv1, ouv0, m_data.mesh->epsilon())) + continue; // UVs must match exactly. + m_chartFaces.push_back(face); + chart.faceCount++; + m_data.isFaceInChart.set(face); + newFaceAdded = true; + } + } + if (!newFaceAdded) + break; + } + } + + AtlasData &m_data; + Array<Chart> m_charts; + Array<Basis> m_chartBasis; + Array<uint32_t> m_chartFaces; + Array<Vector3> m_tempPoints; +}; + #if XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS static uint32_t s_planarRegionsCurrentRegion; static uint32_t s_planarRegionsCurrentVertex; #endif -struct PlanarCharts -{ - PlanarCharts(AtlasData &data) : m_data(data), m_nextRegionFace(MemTag::SegmentAtlasPlanarRegions), m_faceToRegionId(MemTag::SegmentAtlasPlanarRegions) {} +struct PlanarCharts { + PlanarCharts(AtlasData &data) : + m_data(data), m_nextRegionFace(MemTag::SegmentAtlasPlanarRegions), m_faceToRegionId(MemTag::SegmentAtlasPlanarRegions) {} const Basis &chartBasis(uint32_t chartIndex) const { return m_chartBasis[chartIndex]; } uint32_t chartCount() const { return m_charts.size(); } - - ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const - { + + ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const { const Chart &chart = m_charts[chartIndex]; return ConstArrayView<uint32_t>(&m_chartFaces[chart.firstFace], chart.faceCount); } @@ -5564,8 +4883,7 @@ struct PlanarCharts uint32_t nextRegionFace(uint32_t face) const { return m_nextRegionFace[face]; } float regionArea(uint32_t region) const { return m_regionAreas[region]; } - void compute() - { + void compute() { const uint32_t faceCount = m_data.mesh->faceCount(); // Precompute regions of coplanar incident faces. m_regionFirstFace.clear(); @@ -5581,6 +4899,8 @@ struct PlanarCharts for (uint32_t f = 0; f < faceCount; f++) { if (m_nextRegionFace[f] != f) continue; // Already assigned. + if (m_data.isFaceInChart.get(f)) + continue; // Already in a chart. faceStack.clear(); faceStack.push_back(f); for (;;) { @@ -5595,6 +4915,8 @@ struct PlanarCharts continue; if (m_nextRegionFace[oface] != oface) continue; // Already assigned. + if (m_data.isFaceInChart.get(oface)) + continue; // Already in a chart. if (!equal(dot(m_data.faceNormals[face], m_data.faceNormals[oface]), 1.0f, kEpsilon)) continue; // Not coplanar. const uint32_t next = m_nextRegionFace[face]; @@ -5632,8 +4954,11 @@ struct PlanarCharts // Precompute planar region areas. m_regionAreas.resize(regionCount); m_regionAreas.zeroOutMemory(); - for (uint32_t f = 0; f < faceCount; f++) + for (uint32_t f = 0; f < faceCount; f++) { + if (m_faceToRegionId[f] == UINT32_MAX) + continue; m_regionAreas[m_faceToRegionId[f]] += m_data.faceAreas[f]; + } // Create charts from suitable planar regions. // The dihedral angle of all boundary edges must be >= 90 degrees. m_charts.clear(); @@ -5658,8 +4983,7 @@ struct PlanarCharts if (!createChart) break; face = m_nextRegionFace[face]; - } - while (face != firstRegionFace); + } while (face != firstRegionFace); // Create a chart. if (createChart) { Chart chart; @@ -5671,15 +4995,13 @@ struct PlanarCharts m_chartFaces.push_back(face); chart.faceCount++; face = m_nextRegionFace[face]; - } - while (face != firstRegionFace); + } while (face != firstRegionFace); m_charts.push_back(chart); } } // Compute basis for each chart using the first face normal (all faces have the same normal). m_chartBasis.resize(m_charts.size()); - for (uint32_t c = 0; c < m_charts.size(); c++) - { + for (uint32_t c = 0; c < m_charts.size(); c++) { const uint32_t face = m_chartFaces[m_charts[c].firstFace]; Basis &basis = m_chartBasis[c]; basis.normal = m_data.faceNormals[face]; @@ -5689,8 +5011,7 @@ struct PlanarCharts } private: - struct Chart - { + struct Chart { uint32_t firstFace, faceCount; }; @@ -5704,12 +5025,11 @@ private: Array<Basis> m_chartBasis; }; -struct ClusteredCharts -{ - ClusteredCharts(AtlasData &data, const PlanarCharts &planarCharts) : m_data(data), m_planarCharts(planarCharts), m_texcoords(MemTag::SegmentAtlasMeshData), m_bestTriangles(10), m_placingSeeds(false) {} +struct ClusteredCharts { + ClusteredCharts(AtlasData &data, const PlanarCharts &planarCharts) : + m_data(data), m_planarCharts(planarCharts), m_texcoords(MemTag::SegmentAtlasMeshData), m_bestTriangles(10), m_placingSeeds(false) {} - ~ClusteredCharts() - { + ~ClusteredCharts() { const uint32_t chartCount = m_charts.size(); for (uint32_t i = 0; i < chartCount; i++) { m_charts[i]->~Chart(); @@ -5721,8 +5041,7 @@ struct ClusteredCharts ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const { return m_charts[chartIndex]->faces; } const Basis &chartBasis(uint32_t chartIndex) const { return m_charts[chartIndex]->basis; } - void compute() - { + void compute() { const uint32_t faceCount = m_data.mesh->faceCount(); m_facesLeft = 0; for (uint32_t i = 0; i < faceCount; i++) { @@ -5768,9 +5087,9 @@ struct ClusteredCharts } private: - struct Chart - { - Chart() : faces(MemTag::SegmentAtlasChartFaces) {} + struct Chart { + Chart() : + faces(MemTag::SegmentAtlasChartFaces) {} int id = -1; Basis basis; // Best fit normal. @@ -5784,8 +5103,7 @@ private: uint32_t seed; }; - void placeSeeds(float threshold) - { + void placeSeeds(float threshold) { XA_PROFILE_START(clusteredChartsPlaceSeeds) m_placingSeeds = true; // Instead of using a predefiened number of seeds: @@ -5801,8 +5119,7 @@ private: } // Returns true if any of the charts can grow more. - void growCharts(float threshold) - { + void growCharts(float threshold) { XA_PROFILE_START(clusteredChartsGrow) for (;;) { if (m_facesLeft == 0) @@ -5848,8 +5165,7 @@ private: XA_PROFILE_END(clusteredChartsGrow) } - void resetCharts() - { + void resetCharts() { XA_PROFILE_START(clusteredChartsReset) const uint32_t faceCount = m_data.mesh->faceCount(); for (uint32_t i = 0; i < faceCount; i++) { @@ -5880,8 +5196,7 @@ private: XA_PROFILE_END(clusteredChartsReset) } - bool relocateSeeds() - { + bool relocateSeeds() { XA_PROFILE_START(clusteredChartsRelocateSeeds) bool anySeedChanged = false; const uint32_t chartCount = m_charts.size(); @@ -5894,8 +5209,7 @@ private: return anySeedChanged; } - void fillHoles(float threshold) - { + void fillHoles(float threshold) { XA_PROFILE_START(clusteredChartsFillHoles) while (m_facesLeft > 0) createChart(threshold); @@ -5903,8 +5217,7 @@ private: } #if XA_MERGE_CHARTS - void mergeCharts() - { + void mergeCharts() { XA_PROFILE_START(clusteredChartsMerge) const uint32_t chartCount = m_charts.size(); // Merge charts progressively until there's none left to merge. @@ -5964,7 +5277,7 @@ private: // Merge if chart2 has a single face. // chart1 must have more than 1 face. // chart2 area must be <= 10% of chart1 area. - if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && chart->faces.size() > 1 && chart2->faces.size() == 1 && chart2->area <= chart->area * 0.1f) + if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && chart->faces.size() > 1 && chart2->faces.size() == 1 && chart2->area <= chart->area * 0.1f) goto merge; // Merge if chart2 has two faces (probably a quad), and chart1 bounds at least 2 of its edges. if (chart2->faces.size() == 2 && m_sharedBoundaryEdgeCountNoSeams[cc] >= 2) @@ -5972,8 +5285,8 @@ private: // Merge if chart2 is wholely inside chart1, ignoring seams. if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && equal(m_sharedBoundaryLengthsNoSeams[cc], chart2->boundaryLength, kEpsilon)) goto merge; - if (m_sharedBoundaryLengths[cc] > 0.2f * max(0.0f, chart->boundaryLength - externalBoundaryLength) || - m_sharedBoundaryLengths[cc] > 0.75f * chart2->boundaryLength) + if (m_sharedBoundaryLengths[cc] > 0.2f * max(0.0f, chart->boundaryLength - externalBoundaryLength) || + m_sharedBoundaryLengths[cc] > 0.75f * chart2->boundaryLength) goto merge; continue; merge: @@ -6011,8 +5324,7 @@ private: #endif private: - void createChart(float threshold) - { + void createChart(float threshold) { Chart *chart = XA_NEW(MemTag::Default, Chart); chart->id = (int)m_charts.size(); m_charts.push_back(chart); @@ -6043,15 +5355,13 @@ private: } } - bool isChartBoundaryEdge(const Chart *chart, uint32_t edge) const - { + bool isChartBoundaryEdge(const Chart *chart, uint32_t edge) const { const uint32_t oppositeEdge = m_data.mesh->oppositeEdge(edge); const uint32_t oppositeFace = meshEdgeFace(oppositeEdge); return oppositeEdge == UINT32_MAX || m_faceCharts[oppositeFace] != chart->id; } - bool computeChartBasis(Chart *chart, Basis *basis) - { + bool computeChartBasis(Chart *chart, Basis *basis) { const uint32_t faceCount = chart->faces.size(); m_tempPoints.resize(chart->faces.size() * 3); for (uint32_t i = 0; i < faceCount; i++) { @@ -6059,11 +5369,10 @@ private: for (uint32_t j = 0; j < 3; j++) m_tempPoints[i * 3 + j] = m_data.mesh->position(m_data.mesh->vertexAt(f * 3 + j)); } - return Fit::computeBasis(m_tempPoints.data(), m_tempPoints.size(), basis); + return Fit::computeBasis(m_tempPoints, basis); } - bool isFaceFlipped(uint32_t face) const - { + bool isFaceFlipped(uint32_t face) const { const Vector2 &v1 = m_texcoords[face * 3 + 0]; const Vector2 &v2 = m_texcoords[face * 3 + 1]; const Vector2 &v3 = m_texcoords[face * 3 + 2]; @@ -6071,8 +5380,7 @@ private: return parametricArea < 0.0f; } - void parameterizeChart(const Chart *chart) - { + void parameterizeChart(const Chart *chart) { const uint32_t faceCount = chart->faces.size(); for (uint32_t i = 0; i < faceCount; i++) { const uint32_t face = chart->faces[i]; @@ -6085,8 +5393,7 @@ private: } // m_faceCharts for the chart faces must be set to the chart ID. Needed to compute boundary edges. - bool isChartParameterizationValid(const Chart *chart) - { + bool isChartParameterizationValid(const Chart *chart) { const uint32_t faceCount = chart->faces.size(); // Check for flipped faces in the parameterization. OK if all are flipped. uint32_t flippedFaceCount = 0; @@ -6099,7 +5406,7 @@ private: // Check for boundary intersection in the parameterization. XA_PROFILE_START(clusteredChartsPlaceSeedsBoundaryIntersection) XA_PROFILE_START(clusteredChartsGrowBoundaryIntersection) - m_boundaryGrid.reset(m_texcoords.data()); + m_boundaryGrid.reset(m_texcoords); for (uint32_t i = 0; i < faceCount; i++) { const uint32_t f = chart->faces[i]; for (uint32_t j = 0; j < 3; j++) { @@ -6120,15 +5427,14 @@ private: return true; } - bool addFaceToChart(Chart *chart, uint32_t face) - { + bool addFaceToChart(Chart *chart, uint32_t face) { XA_DEBUG_ASSERT(!m_data.isFaceInChart.get(face)); const uint32_t oldFaceCount = chart->faces.size(); const bool firstFace = oldFaceCount == 0; // Append the face and any coplanar connected faces to the chart faces array. chart->faces.push_back(face); uint32_t coplanarFace = m_planarCharts.nextRegionFace(face); - while (coplanarFace != face) { + while (coplanarFace != face) { XA_DEBUG_ASSERT(!m_data.isFaceInChart.get(coplanarFace)); chart->faces.push_back(coplanarFace); coplanarFace = m_planarCharts.nextRegionFace(coplanarFace); @@ -6140,7 +5446,7 @@ private: // Use the first face normal. // Use any edge as the tangent vector. basis.normal = m_data.faceNormals[face]; - basis.tangent = normalize(m_data.mesh->position(m_data.mesh->vertexAt(face * 3 + 0)) - m_data.mesh->position(m_data.mesh->vertexAt(face * 3 + 1)), kEpsilon); + basis.tangent = normalize(m_data.mesh->position(m_data.mesh->vertexAt(face * 3 + 0)) - m_data.mesh->position(m_data.mesh->vertexAt(face * 3 + 1))); basis.bitangent = cross(basis.normal, basis.tangent); } else { // Use best fit normal. @@ -6199,8 +5505,7 @@ private: } // Returns true if the seed has changed. - bool relocateSeed(Chart *chart) - { + bool relocateSeed(Chart *chart) { // Find the first N triangles that fit the proxy best. const uint32_t faceCount = chart->faces.size(); m_bestTriangles.clear(); @@ -6230,8 +5535,7 @@ private: } // Cost is combined metrics * weights. - float computeCost(Chart *chart, uint32_t face) const - { + float computeCost(Chart *chart, uint32_t face) const { // Estimate boundary length and area: const float newChartArea = computeArea(chart, face); const float newBoundaryLength = computeBoundaryLength(chart, face); @@ -6267,28 +5571,25 @@ private: // Returns a value in [0-1]. // 0 if face normal is coplanar to the chart's best fit normal. // 1 if face normal is perpendicular. - float computeNormalDeviationMetric(Chart *chart, uint32_t face) const - { + float computeNormalDeviationMetric(Chart *chart, uint32_t face) const { // All faces in coplanar regions have the same normal, can use any face. const Vector3 faceNormal = m_data.faceNormals[face]; // Use plane fitting metric for now: return min(1.0f - dot(faceNormal, chart->basis.normal), 1.0f); // @@ normal deviations should be weighted by face area } - float computeRoundnessMetric(Chart *chart, float newBoundaryLength, float newChartArea) const - { + float computeRoundnessMetric(Chart *chart, float newBoundaryLength, float newChartArea) const { const float oldRoundness = square(chart->boundaryLength) / chart->area; const float newRoundness = square(newBoundaryLength) / newChartArea; return 1.0f - oldRoundness / newRoundness; } - float computeStraightnessMetric(Chart *chart, uint32_t firstFace) const - { + float computeStraightnessMetric(Chart *chart, uint32_t firstFace) const { float l_out = 0.0f; // Length of firstFace planar region boundary that doesn't border the chart. float l_in = 0.0f; // Length that does border the chart. const uint32_t planarRegionId = m_planarCharts.regionIdFromFace(firstFace); uint32_t face = firstFace; - for (;;) { + for (;;) { for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) { const float l = m_data.edgeLengths[it.edge()]; if (it.isBoundary()) { @@ -6305,7 +5606,6 @@ private: break; } #if 1 - XA_DEBUG_ASSERT(l_in != 0.0f); // Candidate face must be adjacent to chart. @@ This is not true if the input mesh has zero-length edges. float ratio = (l_out - l_in) / (l_out + l_in); return min(ratio, 0.0f); // Only use the straightness metric to close gaps. #else @@ -6313,8 +5613,7 @@ private: #endif } - bool isNormalSeam(uint32_t edge) const - { + bool isNormalSeam(uint32_t edge) const { const uint32_t oppositeEdge = m_data.mesh->oppositeEdge(edge); if (oppositeEdge == UINT32_MAX) return false; // boundary edge @@ -6334,11 +5633,10 @@ private: return !equal(m_data.faceNormals[f0], m_data.faceNormals[f1], kNormalEpsilon); } - float computeNormalSeamMetric(Chart *chart, uint32_t firstFace) const - { + float computeNormalSeamMetric(Chart *chart, uint32_t firstFace) const { float seamFactor = 0.0f, totalLength = 0.0f; uint32_t face = firstFace; - for (;;) { + for (;;) { for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) { if (it.isBoundary()) continue; @@ -6375,11 +5673,10 @@ private: return seamFactor / totalLength; } - float computeTextureSeamMetric(Chart *chart, uint32_t firstFace) const - { + float computeTextureSeamMetric(Chart *chart, uint32_t firstFace) const { float seamLength = 0.0f, totalLength = 0.0f; uint32_t face = firstFace; - for (;;) { + for (;;) { for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) { if (it.isBoundary()) continue; @@ -6402,11 +5699,10 @@ private: return seamLength / totalLength; } - float computeArea(Chart *chart, uint32_t firstFace) const - { + float computeArea(Chart *chart, uint32_t firstFace) const { float area = chart->area; uint32_t face = firstFace; - for (;;) { + for (;;) { area += m_data.faceAreas[face]; face = m_planarCharts.nextRegionFace(face); if (face == firstFace) @@ -6415,13 +5711,12 @@ private: return area; } - float computeBoundaryLength(Chart *chart, uint32_t firstFace) const - { + float computeBoundaryLength(Chart *chart, uint32_t firstFace) const { float boundaryLength = chart->boundaryLength; // Add new edges, subtract edges shared with the chart. const uint32_t planarRegionId = m_planarCharts.regionIdFromFace(firstFace); uint32_t face = firstFace; - for (;;) { + for (;;) { for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) { const float edgeLength = m_data.edgeLengths[it.edge()]; if (it.isBoundary()) { @@ -6437,11 +5732,10 @@ private: if (face == firstFace) break; } - return max(0.0f, boundaryLength); // @@ Hack! + return max(0.0f, boundaryLength); // @@ Hack! } - bool mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength) - { + bool mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength) { const uint32_t oldOwnerFaceCount = owner->faces.size(); const uint32_t chartFaceCount = chart->faces.size(); owner->faces.push_back(chart->faces); @@ -6499,33 +5793,53 @@ private: bool m_placingSeeds; }; -struct Atlas -{ - Atlas() : m_planarCharts(m_data), m_clusteredCharts(m_data, m_planarCharts) {} +struct ChartGeneratorType { + enum Enum { + OriginalUv, + Planar, + Clustered, + Piecewise + }; +}; - uint32_t chartCount() const - { - return m_planarCharts.chartCount() + m_clusteredCharts.chartCount(); +struct Atlas { + Atlas() : + m_originalUvCharts(m_data), m_planarCharts(m_data), m_clusteredCharts(m_data, m_planarCharts) {} + + uint32_t chartCount() const { + return m_originalUvCharts.chartCount() + m_planarCharts.chartCount() + m_clusteredCharts.chartCount(); } - ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const - { + ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const { + if (chartIndex < m_originalUvCharts.chartCount()) + return m_originalUvCharts.chartFaces(chartIndex); + chartIndex -= m_originalUvCharts.chartCount(); if (chartIndex < m_planarCharts.chartCount()) return m_planarCharts.chartFaces(chartIndex); chartIndex -= m_planarCharts.chartCount(); return m_clusteredCharts.chartFaces(chartIndex); } - const Basis &chartBasis(uint32_t chartIndex) const - { + const Basis &chartBasis(uint32_t chartIndex) const { + if (chartIndex < m_originalUvCharts.chartCount()) + return m_originalUvCharts.chartBasis(chartIndex); + chartIndex -= m_originalUvCharts.chartCount(); if (chartIndex < m_planarCharts.chartCount()) return m_planarCharts.chartBasis(chartIndex); chartIndex -= m_planarCharts.chartCount(); return m_clusteredCharts.chartBasis(chartIndex); } - void reset(const Mesh *mesh, const ChartOptions &options) - { + ChartGeneratorType::Enum chartGeneratorType(uint32_t chartIndex) const { + if (chartIndex < m_originalUvCharts.chartCount()) + return ChartGeneratorType::OriginalUv; + chartIndex -= m_originalUvCharts.chartCount(); + if (chartIndex < m_planarCharts.chartCount()) + return ChartGeneratorType::Planar; + return ChartGeneratorType::Clustered; + } + + void reset(const Mesh *mesh, const ChartOptions &options) { XA_PROFILE_START(buildAtlasInit) m_data.options = options; m_data.mesh = mesh; @@ -6533,8 +5847,12 @@ struct Atlas XA_PROFILE_END(buildAtlasInit) } - void compute() - { + void compute() { + if (m_data.options.useInputMeshUvs) { + XA_PROFILE_START(originalUvCharts) + m_originalUvCharts.compute(); + XA_PROFILE_END(originalUvCharts) + } XA_PROFILE_START(planarCharts) m_planarCharts.compute(); XA_PROFILE_END(planarCharts) @@ -6545,17 +5863,143 @@ struct Atlas private: AtlasData m_data; + OriginalUvCharts m_originalUvCharts; PlanarCharts m_planarCharts; ClusteredCharts m_clusteredCharts; }; +struct ComputeUvMeshChartsTaskArgs { + UvMesh *mesh; + Progress *progress; +}; + +// Charts are found by floodfilling faces without crossing UV seams. +struct ComputeUvMeshChartsTask { + ComputeUvMeshChartsTask(ComputeUvMeshChartsTaskArgs *args) : + m_mesh(args->mesh), m_progress(args->progress), m_uvToEdgeMap(MemTag::Default, m_mesh->indices.size()), m_faceAssigned(m_mesh->indices.size() / 3) {} + + void run() { + const uint32_t vertexCount = m_mesh->texcoords.size(); + const uint32_t indexCount = m_mesh->indices.size(); + const uint32_t faceCount = indexCount / 3; + // A vertex can only be assigned to one chart. + m_mesh->vertexToChartMap.resize(vertexCount); + m_mesh->vertexToChartMap.fill(UINT32_MAX); + // Map vertex UV to edge. Face is then edge / 3. + for (uint32_t i = 0; i < indexCount; i++) + m_uvToEdgeMap.add(m_mesh->texcoords[m_mesh->indices[i]]); + // Find charts. + m_faceAssigned.zeroOutMemory(); + for (uint32_t f = 0; f < faceCount; f++) { + if (m_progress->cancel) + return; + m_progress->increment(1); + // Found an unassigned face, see if it can be added. + const uint32_t chartIndex = m_mesh->charts.size(); + if (!canAddFaceToChart(chartIndex, f)) + continue; + // Face is OK, create a new chart with the face. + UvMeshChart *chart = XA_NEW(MemTag::Default, UvMeshChart); + m_mesh->charts.push_back(chart); + chart->material = m_mesh->faceMaterials.isEmpty() ? 0 : m_mesh->faceMaterials[f]; + addFaceToChart(chartIndex, f); + // Walk incident faces and assign them to the chart. + uint32_t f2 = 0; + for (;;) { + bool newFaceAssigned = false; + const uint32_t faceCount2 = chart->faces.size(); + for (; f2 < faceCount2; f2++) { + const uint32_t face = chart->faces[f2]; + for (uint32_t i = 0; i < 3; i++) { + // Add any valid faces with colocal UVs to the chart. + const Vector2 &uv = m_mesh->texcoords[m_mesh->indices[face * 3 + i]]; + uint32_t edge = m_uvToEdgeMap.get(uv); + while (edge != UINT32_MAX) { + const uint32_t newFace = edge / 3; + if (canAddFaceToChart(chartIndex, newFace)) { + addFaceToChart(chartIndex, newFace); + newFaceAssigned = true; + } + edge = m_uvToEdgeMap.getNext(uv, edge); + } + } + } + if (!newFaceAssigned) + break; + } + } + } + +private: + // The chart at chartIndex doesn't have to exist yet. + bool canAddFaceToChart(uint32_t chartIndex, uint32_t face) const { + if (m_faceAssigned.get(face)) + return false; // Already assigned to a chart. + if (m_mesh->faceIgnore.get(face)) + return false; // Face is ignored (zero area or nan UVs). + if (!m_mesh->faceMaterials.isEmpty() && chartIndex < m_mesh->charts.size()) { + if (m_mesh->faceMaterials[face] != m_mesh->charts[chartIndex]->material) + return false; // Materials don't match. + } + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = m_mesh->indices[face * 3 + i]; + if (m_mesh->vertexToChartMap[vertex] != UINT32_MAX && m_mesh->vertexToChartMap[vertex] != chartIndex) + return false; // Vertex already assigned to another chart. + } + return true; + } + + void addFaceToChart(uint32_t chartIndex, uint32_t face) { + UvMeshChart *chart = m_mesh->charts[chartIndex]; + m_faceAssigned.set(face); + chart->faces.push_back(face); + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = m_mesh->indices[face * 3 + i]; + m_mesh->vertexToChartMap[vertex] = chartIndex; + chart->indices.push_back(vertex); + } + } + + UvMesh *const m_mesh; + Progress *const m_progress; + HashMap<Vector2> m_uvToEdgeMap; // Face is edge / 3. + BitArray m_faceAssigned; +}; + +static void runComputeUvMeshChartsTask(void * /*groupUserData*/, void *taskUserData) { + XA_PROFILE_START(computeChartsThread) + ComputeUvMeshChartsTask task((ComputeUvMeshChartsTaskArgs *)taskUserData); + task.run(); + XA_PROFILE_END(computeChartsThread) +} + +static bool computeUvMeshCharts(TaskScheduler *taskScheduler, ArrayView<UvMesh *> meshes, ProgressFunc progressFunc, void *progressUserData) { + uint32_t totalFaceCount = 0; + for (uint32_t i = 0; i < meshes.length; i++) + totalFaceCount += meshes[i]->indices.size() / 3; + Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, totalFaceCount); + TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(nullptr, meshes.length); + Array<ComputeUvMeshChartsTaskArgs> taskArgs; + taskArgs.resize(meshes.length); + for (uint32_t i = 0; i < meshes.length; i++) { + ComputeUvMeshChartsTaskArgs &args = taskArgs[i]; + args.mesh = meshes[i]; + args.progress = &progress; + Task task; + task.userData = &args; + task.func = runComputeUvMeshChartsTask; + taskScheduler->run(taskGroup, task); + } + taskScheduler->wait(&taskGroup); + return !progress.cancel; +} + } // namespace segment namespace param { // Fast sweep in 3 directions -static bool findApproximateDiameterVertices(Mesh *mesh, uint32_t *a, uint32_t *b) -{ +static bool findApproximateDiameterVertices(Mesh *mesh, uint32_t *a, uint32_t *b) { XA_DEBUG_ASSERT(a != nullptr); XA_DEBUG_ASSERT(b != nullptr); const uint32_t vertexCount = mesh->vertexCount(); @@ -6612,10 +6056,9 @@ static bool findApproximateDiameterVertices(Mesh *mesh, uint32_t *a, uint32_t *b // From OpenNL LSCM example. // Computes the coordinates of the vertices of a triangle in a local 2D orthonormal basis of the triangle's plane. -static void projectTriangle(Vector3 p0, Vector3 p1, Vector3 p2, Vector2 *z0, Vector2 *z1, Vector2 *z2, float epsilon) -{ - Vector3 X = normalize(p1 - p0, epsilon); - Vector3 Z = normalize(cross(X, p2 - p0), epsilon); +static void projectTriangle(Vector3 p0, Vector3 p1, Vector3 p2, Vector2 *z0, Vector2 *z1, Vector2 *z2) { + Vector3 X = normalize(p1 - p0); + Vector3 Z = normalize(cross(X, p2 - p0)); Vector3 Y = cross(Z, X); Vector3 &O = p0; *z0 = Vector2(0, 0); @@ -6623,8 +6066,83 @@ static void projectTriangle(Vector3 p0, Vector3 p1, Vector3 p2, Vector2 *z0, Vec *z2 = Vector2(dot(p2 - O, X), dot(p2 - O, Y)); } -static bool computeLeastSquaresConformalMap(Mesh *mesh) -{ +// Conformal relations from Brecht Van Lommel (based on ABF): + +static float vec_angle_cos(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3) { + Vector3 d1 = v1 - v2; + Vector3 d2 = v3 - v2; + return clamp(dot(d1, d2) / (length(d1) * length(d2)), -1.0f, 1.0f); +} + +static float vec_angle(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3) { + float dot = vec_angle_cos(v1, v2, v3); + return acosf(dot); +} + +static void triangle_angles(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, float *a1, float *a2, float *a3) { + *a1 = vec_angle(v3, v1, v2); + *a2 = vec_angle(v1, v2, v3); + *a3 = kPi - *a2 - *a1; +} + +static bool setup_abf_relations(opennl::NLContext *context, int id0, int id1, int id2, const Vector3 &p0, const Vector3 &p1, const Vector3 &p2) { + // @@ IC: Wouldn't it be more accurate to return cos and compute 1-cos^2? + // It does indeed seem to be a little bit more robust. + // @@ Need to revisit this more carefully! + float a0, a1, a2; + triangle_angles(p0, p1, p2, &a0, &a1, &a2); + if (a0 == 0.0f || a1 == 0.0f || a2 == 0.0f) + return false; + float s0 = sinf(a0); + float s1 = sinf(a1); + float s2 = sinf(a2); + if (s1 > s0 && s1 > s2) { + swap(s1, s2); + swap(s0, s1); + swap(a1, a2); + swap(a0, a1); + swap(id1, id2); + swap(id0, id1); + } else if (s0 > s1 && s0 > s2) { + swap(s0, s2); + swap(s0, s1); + swap(a0, a2); + swap(a0, a1); + swap(id0, id2); + swap(id0, id1); + } + float c0 = cosf(a0); + float ratio = (s2 == 0.0f) ? 1.0f : s1 / s2; + float cosine = c0 * ratio; + float sine = s0 * ratio; + // Note : 2*id + 0 --> u + // 2*id + 1 --> v + int u0_id = 2 * id0 + 0; + int v0_id = 2 * id0 + 1; + int u1_id = 2 * id1 + 0; + int v1_id = 2 * id1 + 1; + int u2_id = 2 * id2 + 0; + int v2_id = 2 * id2 + 1; + // Real part + opennl::nlBegin(context, NL_ROW); + opennl::nlCoefficient(context, u0_id, cosine - 1.0f); + opennl::nlCoefficient(context, v0_id, -sine); + opennl::nlCoefficient(context, u1_id, -cosine); + opennl::nlCoefficient(context, v1_id, sine); + opennl::nlCoefficient(context, u2_id, 1); + opennl::nlEnd(context, NL_ROW); + // Imaginary part + opennl::nlBegin(context, NL_ROW); + opennl::nlCoefficient(context, u0_id, sine); + opennl::nlCoefficient(context, v0_id, cosine - 1.0f); + opennl::nlCoefficient(context, u1_id, -sine); + opennl::nlCoefficient(context, v1_id, -cosine); + opennl::nlCoefficient(context, v2_id, 1); + opennl::nlEnd(context, NL_ROW); + return true; +} + +static bool computeLeastSquaresConformalMap(Mesh *mesh) { uint32_t lockedVertex0, lockedVertex1; if (!findApproximateDiameterVertices(mesh, &lockedVertex0, &lockedVertex1)) { // Mesh has no boundaries. @@ -6635,55 +6153,57 @@ static bool computeLeastSquaresConformalMap(Mesh *mesh) opennl::nlSolverParameteri(context, NL_NB_VARIABLES, int(2 * vertexCount)); opennl::nlSolverParameteri(context, NL_MAX_ITERATIONS, int(5 * vertexCount)); opennl::nlBegin(context, NL_SYSTEM); - const Vector2 *texcoords = mesh->texcoords(); + ArrayView<Vector2> texcoords = mesh->texcoords(); for (uint32_t i = 0; i < vertexCount; i++) { opennl::nlSetVariable(context, 2 * i, texcoords[i].x); opennl::nlSetVariable(context, 2 * i + 1, texcoords[i].y); if (i == lockedVertex0 || i == lockedVertex1) { opennl::nlLockVariable(context, 2 * i); opennl::nlLockVariable(context, 2 * i + 1); - } + } } opennl::nlBegin(context, NL_MATRIX); const uint32_t faceCount = mesh->faceCount(); - const Vector3 *positions = mesh->positions(); - const uint32_t *indices = mesh->indices(); + ConstArrayView<Vector3> positions = mesh->positions(); + ConstArrayView<uint32_t> indices = mesh->indices(); for (uint32_t f = 0; f < faceCount; f++) { const uint32_t v0 = indices[f * 3 + 0]; const uint32_t v1 = indices[f * 3 + 1]; const uint32_t v2 = indices[f * 3 + 2]; - Vector2 z0, z1, z2; - projectTriangle(positions[v0], positions[v1], positions[v2], &z0, &z1, &z2, mesh->epsilon()); - double a = z1.x - z0.x; - double b = z1.y - z0.y; - double c = z2.x - z0.x; - double d = z2.y - z0.y; - XA_DEBUG_ASSERT(b == 0.0); - // Note : 2*id + 0 --> u - // 2*id + 1 --> v - uint32_t u0_id = 2 * v0; - uint32_t v0_id = 2 * v0 + 1; - uint32_t u1_id = 2 * v1; - uint32_t v1_id = 2 * v1 + 1; - uint32_t u2_id = 2 * v2; - uint32_t v2_id = 2 * v2 + 1; - // Note : b = 0 - // Real part - opennl::nlBegin(context, NL_ROW); - opennl::nlCoefficient(context, u0_id, -a+c) ; - opennl::nlCoefficient(context, v0_id, b-d) ; - opennl::nlCoefficient(context, u1_id, -c) ; - opennl::nlCoefficient(context, v1_id, d) ; - opennl::nlCoefficient(context, u2_id, a); - opennl::nlEnd(context, NL_ROW); - // Imaginary part - opennl::nlBegin(context, NL_ROW); - opennl::nlCoefficient(context, u0_id, -b+d); - opennl::nlCoefficient(context, v0_id, -a+c); - opennl::nlCoefficient(context, u1_id, -d); - opennl::nlCoefficient(context, v1_id, -c); - opennl::nlCoefficient(context, v2_id, a); - opennl::nlEnd(context, NL_ROW); + if (!setup_abf_relations(context, v0, v1, v2, positions[v0], positions[v1], positions[v2])) { + Vector2 z0, z1, z2; + projectTriangle(positions[v0], positions[v1], positions[v2], &z0, &z1, &z2); + double a = z1.x - z0.x; + double b = z1.y - z0.y; + double c = z2.x - z0.x; + double d = z2.y - z0.y; + XA_DEBUG_ASSERT(b == 0.0); + // Note : 2*id + 0 --> u + // 2*id + 1 --> v + uint32_t u0_id = 2 * v0; + uint32_t v0_id = 2 * v0 + 1; + uint32_t u1_id = 2 * v1; + uint32_t v1_id = 2 * v1 + 1; + uint32_t u2_id = 2 * v2; + uint32_t v2_id = 2 * v2 + 1; + // Note : b = 0 + // Real part + opennl::nlBegin(context, NL_ROW); + opennl::nlCoefficient(context, u0_id, -a + c); + opennl::nlCoefficient(context, v0_id, b - d); + opennl::nlCoefficient(context, u1_id, -c); + opennl::nlCoefficient(context, v1_id, d); + opennl::nlCoefficient(context, u2_id, a); + opennl::nlEnd(context, NL_ROW); + // Imaginary part + opennl::nlBegin(context, NL_ROW); + opennl::nlCoefficient(context, u0_id, -b + d); + opennl::nlCoefficient(context, v0_id, -a + c); + opennl::nlCoefficient(context, u1_id, -d); + opennl::nlCoefficient(context, v1_id, -c); + opennl::nlCoefficient(context, v2_id, a); + opennl::nlEnd(context, NL_ROW); + } } opennl::nlEnd(context, NL_MATRIX); opennl::nlEnd(context, NL_SYSTEM); @@ -6694,7 +6214,7 @@ static bool computeLeastSquaresConformalMap(Mesh *mesh) for (uint32_t i = 0; i < vertexCount; i++) { const double u = opennl::nlGetVariable(context, 2 * i); const double v = opennl::nlGetVariable(context, 2 * i + 1); - mesh->texcoord(i) = Vector2((float)u, (float)v); + texcoords[i] = Vector2((float)u, (float)v); XA_DEBUG_ASSERT(!isNan(mesh->texcoord(i).x)); XA_DEBUG_ASSERT(!isNan(mesh->texcoord(i).y)); } @@ -6702,30 +6222,26 @@ static bool computeLeastSquaresConformalMap(Mesh *mesh) return true; } -#if XA_RECOMPUTE_CHARTS -struct PiecewiseParam -{ - void reset(const Mesh *mesh, uint32_t faceCount) - { +struct PiecewiseParam { + void reset(const Mesh *mesh) { m_mesh = mesh; - m_faceCount = faceCount; + const uint32_t faceCount = m_mesh->faceCount(); const uint32_t vertexCount = m_mesh->vertexCount(); m_texcoords.resize(vertexCount); - m_patch.reserve(m_faceCount); - m_candidates.reserve(m_faceCount); - m_faceInAnyPatch.resize(m_faceCount); + m_patch.reserve(faceCount); + m_candidates.reserve(faceCount); + m_faceInAnyPatch.resize(faceCount); m_faceInAnyPatch.zeroOutMemory(); - m_faceInvalid.resize(m_faceCount); - m_faceInPatch.resize(m_faceCount); + m_faceInvalid.resize(faceCount); + m_faceInPatch.resize(faceCount); m_vertexInPatch.resize(vertexCount); - m_faceToCandidate.resize(m_faceCount); + m_faceToCandidate.resize(faceCount); } ConstArrayView<uint32_t> chartFaces() const { return m_patch; } - const Vector2 *texcoords() const { return m_texcoords.data(); } + ConstArrayView<Vector2> texcoords() const { return m_texcoords; } - bool computeChart() - { + bool computeChart() { // Clear per-patch state. m_patch.clear(); m_candidates.clear(); @@ -6734,8 +6250,9 @@ struct PiecewiseParam m_faceInPatch.zeroOutMemory(); m_vertexInPatch.zeroOutMemory(); // Add the seed face (first unassigned face) to the patch. + const uint32_t faceCount = m_mesh->faceCount(); uint32_t seed = UINT32_MAX; - for (uint32_t f = 0; f < m_faceCount; f++) { + for (uint32_t f = 0; f < faceCount; f++) { if (m_faceInAnyPatch.get(f)) continue; seed = f; @@ -6749,7 +6266,7 @@ struct PiecewiseParam } addFaceToPatch(seed); // Initialize the boundary grid. - m_boundaryGrid.reset(m_texcoords.data(), m_mesh->indices()); + m_boundaryGrid.reset(m_texcoords, m_mesh->indices()); for (Mesh::FaceEdgeIterator it(m_mesh, seed); !it.isDone(); it.advance()) m_boundaryGrid.append(it.edge()); break; @@ -6793,22 +6310,34 @@ struct PiecewiseParam break; } } + // Check for zero area and flipped faces (using area). + for (CandidateIterator it(bestCandidate); !it.isDone(); it.advance()) { + const Vector2 a = m_texcoords[m_mesh->vertexAt(it.current()->face * 3 + 0)]; + const Vector2 b = m_texcoords[m_mesh->vertexAt(it.current()->face * 3 + 1)]; + const Vector2 c = m_texcoords[m_mesh->vertexAt(it.current()->face * 3 + 2)]; + const float area = triangleArea(a, b, c); + if (area <= 0.0f) { + invalid = true; + break; + } + } // Check for boundary intersection. if (!invalid) { XA_PROFILE_START(parameterizeChartsPiecewiseBoundaryIntersection) // Test candidate edges that would form part of the new patch boundary. // Ignore boundary edges that would become internal if the candidate faces were added to the patch. - Array<uint32_t> newBoundaryEdges, ignoreEdges; + m_newBoundaryEdges.clear(); + m_ignoreBoundaryEdges.clear(); for (CandidateIterator candidateIt(bestCandidate); !candidateIt.isDone(); candidateIt.advance()) { for (Mesh::FaceEdgeIterator it(m_mesh, candidateIt.current()->face); !it.isDone(); it.advance()) { const uint32_t oface = it.oppositeFace(); - if (oface == UINT32_MAX || oface >= m_faceCount || !m_faceInPatch.get(oface)) - newBoundaryEdges.push_back(it.edge()); - if (oface != UINT32_MAX && oface < m_faceCount && m_faceInPatch.get(oface)) - ignoreEdges.push_back(it.oppositeEdge()); + if (oface == UINT32_MAX || !m_faceInPatch.get(oface)) + m_newBoundaryEdges.push_back(it.edge()); + if (oface != UINT32_MAX && m_faceInPatch.get(oface)) + m_ignoreBoundaryEdges.push_back(it.oppositeEdge()); } } - invalid = m_boundaryGrid.intersect(m_mesh->epsilon(), newBoundaryEdges, ignoreEdges); + invalid = m_boundaryGrid.intersect(m_mesh->epsilon(), m_newBoundaryEdges, m_ignoreBoundaryEdges); XA_PROFILE_END(parameterizeChartsPiecewiseBoundaryIntersection) } if (invalid) { @@ -6826,11 +6355,11 @@ struct PiecewiseParam removeLinkedCandidates(bestCandidate); // Reset the grid with all edges on the patch boundary. XA_PROFILE_START(parameterizeChartsPiecewiseBoundaryIntersection) - m_boundaryGrid.reset(m_texcoords.data(), m_mesh->indices()); + m_boundaryGrid.reset(m_texcoords, m_mesh->indices()); for (uint32_t i = 0; i < m_patch.size(); i++) { for (Mesh::FaceEdgeIterator it(m_mesh, m_patch[i]); !it.isDone(); it.advance()) { const uint32_t oface = it.oppositeFace(); - if (oface == UINT32_MAX || oface >= m_faceCount || !m_faceInPatch.get(oface)) + if (oface == UINT32_MAX || !m_faceInPatch.get(oface)) m_boundaryGrid.append(it.edge()); } } @@ -6841,8 +6370,7 @@ struct PiecewiseParam } private: - struct Candidate - { + struct Candidate { uint32_t face, vertex; Candidate *prev, *next; // The previous/next candidate with the same vertex. Vector2 position; @@ -6852,10 +6380,14 @@ private: float patchVertexOrient; }; - struct CandidateIterator - { - CandidateIterator(Candidate *head) : m_current(head) { XA_DEBUG_ASSERT(!head->prev); } - void advance() { if (m_current != nullptr) { m_current = m_current->next; } } + struct CandidateIterator { + CandidateIterator(Candidate *head) : + m_current(head) { XA_DEBUG_ASSERT(!head->prev); } + void advance() { + if (m_current != nullptr) { + m_current = m_current->next; + } + } bool isDone() const { return !m_current; } Candidate *current() { return m_current; } @@ -6864,7 +6396,6 @@ private: }; const Mesh *m_mesh; - uint32_t m_faceCount; Array<Vector2> m_texcoords; BitArray m_faceInAnyPatch; // Face is in a previous chart patch or the current patch. Array<Candidate *> m_candidates; // Incident faces to the patch. @@ -6873,9 +6404,9 @@ private: BitArray m_faceInPatch, m_vertexInPatch; // Face/vertex is in the current patch. BitArray m_faceInvalid; // Face cannot be added to the patch - flipped, cost too high or causes boundary intersection. UniformGrid2 m_boundaryGrid; + Array<uint32_t> m_newBoundaryEdges, m_ignoreBoundaryEdges; // Temp arrays used when testing for boundary intersection. - void addFaceToPatch(uint32_t face) - { + void addFaceToPatch(uint32_t face) { XA_DEBUG_ASSERT(!m_faceInPatch.get(face)); XA_DEBUG_ASSERT(!m_faceInAnyPatch.get(face)); m_patch.push_back(face); @@ -6884,7 +6415,7 @@ private: // Find new candidate faces on the patch incident to the newly added face. for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { const uint32_t oface = it.oppositeFace(); - if (oface == UINT32_MAX || oface >= m_faceCount || m_faceInAnyPatch.get(oface) || m_faceToCandidate[oface]) + if (oface == UINT32_MAX || m_faceInAnyPatch.get(oface) || m_faceToCandidate[oface]) continue; // Found an active edge on the patch front. // Find the free vertex (the vertex that isn't on the active edge). @@ -6900,12 +6431,14 @@ private: } } XA_DEBUG_ASSERT(freeVertex != UINT32_MAX); - // If the free vertex is already in the patch, the face is enclosed by the patch. Add the face to the patch - don't need to assign texcoords. - /*if (m_vertexInPatch.get(freeVertex)) { + if (m_vertexInPatch.get(freeVertex)) { +#if 0 + // If the free vertex is already in the patch, the face is enclosed by the patch. Add the face to the patch - don't need to assign texcoords. freeVertex = UINT32_MAX; - addFaceToPatch(oface, false); + addFaceToPatch(oface); +#endif continue; - }*/ + } // Check this here rather than above so faces enclosed by the patch are always added. if (m_faceInvalid.get(oface)) continue; @@ -6913,8 +6446,7 @@ private: } } - void addCandidateFace(uint32_t patchEdge, float patchVertexOrient, uint32_t face, uint32_t edge, uint32_t freeVertex) - { + void addCandidateFace(uint32_t patchEdge, float patchVertexOrient, uint32_t face, uint32_t edge, uint32_t freeVertex) { XA_DEBUG_ASSERT(!m_faceToCandidate[face]); Vector2 texcoords[3]; orthoProjectFace(face, texcoords); @@ -6960,8 +6492,10 @@ private: uv.x = x + texcoords[localVertex0].x; uv.y = y + texcoords[localVertex0].y; } - if (isNan(texcoords[localFreeVertex].x) || isNan(texcoords[localFreeVertex].y)) + if (isNan(texcoords[localFreeVertex].x) || isNan(texcoords[localFreeVertex].y)) { + m_faceInvalid.set(face); return; + } // Check for local overlap (flipped triangle). // The patch face vertex that isn't on the active edge and the free vertex should be oriented on opposite sides to the active edge. const float freeVertexOrient = orientToEdge(m_texcoords[vertex0], m_texcoords[vertex1], texcoords[localFreeVertex]); @@ -6975,12 +6509,10 @@ private: return; } const float cost = fabsf(stretch - 1.0f); -#if 0 - if (cost > 0.25f) { + if (cost > 0.5f) { m_faceInvalid.set(face); return; } -#endif // Add the candidate. Candidate *candidate = XA_ALLOC(MemTag::Default, Candidate); candidate->face = face; @@ -7017,8 +6549,7 @@ private: it.current()->maxCost = maxCost; } - Candidate *linkedCandidateHead(Candidate *candidate) - { + Candidate *linkedCandidateHead(Candidate *candidate) { Candidate *current = candidate; for (;;) { if (!current->prev) @@ -7028,8 +6559,7 @@ private: return current; } - void removeLinkedCandidates(Candidate *head) - { + void removeLinkedCandidates(Candidate *head) { XA_DEBUG_ASSERT(!head->prev); Candidate *current = head; while (current) { @@ -7046,10 +6576,9 @@ private: } } - void orthoProjectFace(uint32_t face, Vector2 *texcoords) const - { - const Vector3 normal = m_mesh->computeFaceNormal(face); - const Vector3 tangent = normalize(m_mesh->position(m_mesh->vertexAt(face * 3 + 1)) - m_mesh->position(m_mesh->vertexAt(face * 3 + 0)), kEpsilon); + void orthoProjectFace(uint32_t face, Vector2 *texcoords) const { + const Vector3 normal = -m_mesh->computeFaceNormal(face); + const Vector3 tangent = normalize(m_mesh->position(m_mesh->vertexAt(face * 3 + 1)) - m_mesh->position(m_mesh->vertexAt(face * 3 + 0))); const Vector3 bitangent = cross(normal, tangent); for (uint32_t i = 0; i < 3; i++) { const Vector3 &pos = m_mesh->position(m_mesh->vertexAt(face * 3 + i)); @@ -7057,16 +6586,14 @@ private: } } - float parametricArea(const Vector2 *texcoords) const - { + float parametricArea(const Vector2 *texcoords) const { const Vector2 &v1 = texcoords[0]; const Vector2 &v2 = texcoords[1]; const Vector2 &v3 = texcoords[2]; return ((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f; } - float computeStretch(Vector3 p1, Vector3 p2, Vector3 p3, Vector2 t1, Vector2 t2, Vector2 t3) const - { + float computeStretch(Vector3 p1, Vector3 p2, Vector3 p3, Vector2 t1, Vector2 t2, Vector2 t3) const { float parametricArea = ((t2.y - t1.y) * (t3.x - t1.x) - (t3.y - t1.y) * (t2.x - t1.x)) * 0.5f; if (isZero(parametricArea, kAreaEpsilon)) return FLT_MAX; @@ -7080,16 +6607,13 @@ private: } // Return value is positive if the point is one side of the edge, negative if on the other side. - float orientToEdge(Vector2 edgeVertex0, Vector2 edgeVertex1, Vector2 point) const - { + float orientToEdge(Vector2 edgeVertex0, Vector2 edgeVertex1, Vector2 point) const { return (edgeVertex0.x - point.x) * (edgeVertex1.y - point.y) - (edgeVertex0.y - point.y) * (edgeVertex1.x - point.x); } }; -#endif // Estimate quality of existing parameterization. -struct Quality -{ +struct Quality { // computeBoundaryIntersection bool boundaryIntersection = false; @@ -7106,8 +6630,7 @@ struct Quality float conformalMetric = 0.0f; float authalicMetric = 0.0f; - void computeBoundaryIntersection(const Mesh *mesh, UniformGrid2 &boundaryGrid) - { + void computeBoundaryIntersection(const Mesh *mesh, UniformGrid2 &boundaryGrid) { const Array<uint32_t> &boundaryEdges = mesh->boundaryEdges(); const uint32_t boundaryEdgeCount = boundaryEdges.size(); boundaryGrid.reset(mesh->texcoords(), mesh->indices(), boundaryEdgeCount); @@ -7123,11 +6646,11 @@ struct Quality #endif } - void computeFlippedFaces(const Mesh *mesh, uint32_t faceCount, Array<uint32_t> *flippedFaces) - { + void computeFlippedFaces(const Mesh *mesh, Array<uint32_t> *flippedFaces) { totalTriangleCount = flippedTriangleCount = zeroAreaTriangleCount = 0; if (flippedFaces) flippedFaces->clear(); + const uint32_t faceCount = mesh->faceCount(); for (uint32_t f = 0; f < faceCount; f++) { Vector2 texcoord[3]; for (int i = 0; i < 3; i++) { @@ -7159,8 +6682,7 @@ struct Quality flippedFaces->clear(); flippedTriangleCount = 0; } - if (flippedTriangleCount > totalTriangleCount / 2) - { + if (flippedTriangleCount > totalTriangleCount / 2) { // If more than half the triangles are flipped, reverse the flipped / not flipped classification. flippedTriangleCount = totalTriangleCount - flippedTriangleCount; if (flippedFaces) { @@ -7182,10 +6704,10 @@ struct Quality } } - void computeMetrics(const Mesh *mesh, uint32_t faceCount) - { + void computeMetrics(const Mesh *mesh) { totalGeometricArea = totalParametricArea = 0.0f; stretchMetric = maxStretchMetric = conformalMetric = authalicMetric = 0.0f; + const uint32_t faceCount = mesh->faceCount(); for (uint32_t f = 0; f < faceCount; f++) { Vector3 pos[3]; Vector2 texcoord[3]; @@ -7214,7 +6736,7 @@ struct Quality const float a = dot(Ss, Ss); // E const float b = dot(Ss, St); // F const float c = dot(St, St); // G - // Compute eigen-values of the first fundamental form: + // Compute eigen-values of the first fundamental form: const float sigma1 = sqrtf(0.5f * max(0.0f, a + c - sqrtf(square(a - c) + 4 * square(b)))); // gamma uppercase, min eigenvalue. const float sigma2 = sqrtf(0.5f * max(0.0f, a + c + sqrtf(square(a - c) + 4 * square(b)))); // gamma lowercase, max eigenvalue. XA_ASSERT(sigma2 > sigma1 || equal(sigma1, sigma2, kEpsilon)); @@ -7245,347 +6767,261 @@ struct Quality if (totalGeometricArea > 0.0f) { const float normFactor = sqrtf(totalParametricArea / totalGeometricArea); stretchMetric = sqrtf(stretchMetric / totalGeometricArea) * normFactor; - maxStretchMetric *= normFactor; + maxStretchMetric *= normFactor; conformalMetric = sqrtf(conformalMetric / totalGeometricArea); authalicMetric = sqrtf(authalicMetric / totalGeometricArea); } } }; -struct ChartWarningFlags -{ - enum Enum - { - CloseHolesFailed = 1<<1, - FixTJunctionsDuplicatedEdge = 1<<2, - FixTJunctionsFailed = 1<<3, - TriangulateDuplicatedEdge = 1<<4, - }; -}; - -struct ChartCtorBuffers -{ +struct ChartCtorBuffers { Array<uint32_t> chartMeshIndices; Array<uint32_t> unifiedMeshIndices; - Array<uint32_t> boundaryLoops; }; -class Chart -{ +class Chart { public: - Chart(ChartCtorBuffers &buffers, const ParameterizeOptions &options, const Basis &basis, ConstArrayView<uint32_t> faces, const Mesh *sourceMesh, uint32_t chartGroupId, uint32_t chartId) : m_basis(basis), m_mesh(nullptr), m_unifiedMesh(nullptr), m_unmodifiedUnifiedMesh(nullptr), m_type(ChartType::LSCM), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0), m_isInvalid(false) - { + Chart(const Basis &basis, segment::ChartGeneratorType::Enum generatorType, ConstArrayView<uint32_t> faces, const Mesh *sourceMesh, uint32_t chartGroupId, uint32_t chartId) : + m_basis(basis), m_unifiedMesh(nullptr), m_type(ChartType::LSCM), m_generatorType(generatorType), m_tjunctionCount(0), m_originalVertexCount(0), m_isInvalid(false) { XA_UNUSED(chartGroupId); XA_UNUSED(chartId); m_faceToSourceFaceMap.copyFrom(faces.data, faces.length); const uint32_t approxVertexCount = min(faces.length * 3, sourceMesh->vertexCount()); - m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), approxVertexCount, faces.length); m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), approxVertexCount, faces.length); HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToUnifiedVertexMap(MemTag::Mesh, approxVertexCount), sourceVertexToChartVertexMap(MemTag::Mesh, approxVertexCount); - // Add vertices. - const uint32_t faceCount = m_initialFaceCount = faces.length; + m_originalIndices.resize(faces.length * 3); + // Add geometry. + const uint32_t faceCount = faces.length; for (uint32_t f = 0; f < faceCount; f++) { + uint32_t unifiedIndices[3]; for (uint32_t i = 0; i < 3; i++) { const uint32_t sourceVertex = sourceMesh->vertexAt(m_faceToSourceFaceMap[f] * 3 + i); - const uint32_t sourceUnifiedVertex = sourceMesh->firstColocal(sourceVertex); + uint32_t sourceUnifiedVertex = sourceMesh->firstColocalVertex(sourceVertex); + if (m_generatorType == segment::ChartGeneratorType::OriginalUv && sourceVertex != sourceUnifiedVertex) { + // Original UVs: don't unify vertices with different UVs; we want to preserve UVs. + if (!equal(sourceMesh->texcoord(sourceVertex), sourceMesh->texcoord(sourceUnifiedVertex), sourceMesh->epsilon())) + sourceUnifiedVertex = sourceVertex; + } uint32_t unifiedVertex = sourceVertexToUnifiedVertexMap.get(sourceUnifiedVertex); if (unifiedVertex == UINT32_MAX) { unifiedVertex = sourceVertexToUnifiedVertexMap.add(sourceUnifiedVertex); - m_unifiedMesh->addVertex(sourceMesh->position(sourceVertex)); + m_unifiedMesh->addVertex(sourceMesh->position(sourceVertex), Vector3(0.0f), sourceMesh->texcoord(sourceVertex)); } if (sourceVertexToChartVertexMap.get(sourceVertex) == UINT32_MAX) { sourceVertexToChartVertexMap.add(sourceVertex); m_vertexToSourceVertexMap.push_back(sourceVertex); m_chartVertexToUnifiedVertexMap.push_back(unifiedVertex); - m_mesh->addVertex(sourceMesh->position(sourceVertex), Vector3(0.0f), sourceMesh->texcoord(sourceVertex)); + m_originalVertexCount++; } - } - } - // Add faces. - for (uint32_t f = 0; f < faceCount; f++) { - uint32_t indices[3], unifiedIndices[3]; - for (uint32_t i = 0; i < 3; i++) { - const uint32_t sourceVertex = sourceMesh->vertexAt(m_faceToSourceFaceMap[f] * 3 + i); - const uint32_t sourceUnifiedVertex = sourceMesh->firstColocal(sourceVertex); - indices[i] = sourceVertexToChartVertexMap.get(sourceVertex); - XA_DEBUG_ASSERT(indices[i] != UINT32_MAX); + m_originalIndices[f * 3 + i] = sourceVertexToChartVertexMap.get(sourceVertex); + ; + XA_DEBUG_ASSERT(m_originalIndices[f * 3 + i] != UINT32_MAX); unifiedIndices[i] = sourceVertexToUnifiedVertexMap.get(sourceUnifiedVertex); XA_DEBUG_ASSERT(unifiedIndices[i] != UINT32_MAX); } - Mesh::AddFaceResult::Enum result = m_mesh->addFace(indices); - XA_UNUSED(result); - XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK); -#if XA_DEBUG - // Unifying colocals may create degenerate edges. e.g. if two triangle vertices are colocal. - for (int i = 0; i < 3; i++) { - const uint32_t index1 = unifiedIndices[i]; - const uint32_t index2 = unifiedIndices[(i + 1) % 3]; - XA_DEBUG_ASSERT(index1 != index2); - } -#endif - result = m_unifiedMesh->addFace(unifiedIndices); - XA_UNUSED(result); - XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK); + m_unifiedMesh->addFace(unifiedIndices); } - m_mesh->createBoundaries(); // For AtlasPacker::computeBoundingBox - m_mesh->destroyEdgeMap(); // Only needed it for createBoundaries. m_unifiedMesh->createBoundaries(); - if (meshIsPlanar(*m_unifiedMesh)) { + if (m_generatorType == segment::ChartGeneratorType::Planar) { m_type = ChartType::Planar; return; } - m_unifiedMesh->linkBoundaries(); -#if XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION - m_unifiedMesh->writeObjFile("debug_before_fix_tjunction.obj"); -#endif - bool duplicatedEdge = false, failed = false; - if (options.fixTJunctions) { - XA_PROFILE_START(fixChartMeshTJunctions) - Mesh *fixedUnifiedMesh = meshFixTJunctions(*m_unifiedMesh, &duplicatedEdge, &failed, &m_fixedTJunctionsCount); - XA_PROFILE_END(fixChartMeshTJunctions) - if (fixedUnifiedMesh) { - if (duplicatedEdge) - m_warningFlags |= ChartWarningFlags::FixTJunctionsDuplicatedEdge; - if (failed) - m_warningFlags |= ChartWarningFlags::FixTJunctionsFailed; - m_unmodifiedUnifiedMesh = m_unifiedMesh; - m_unifiedMesh = fixedUnifiedMesh; - m_unifiedMesh->createBoundaries(); - m_unifiedMesh->linkBoundaries(); - m_initialFaceCount = m_unifiedMesh->faceCount(); // Fixing t-junctions rewrites faces. - } - } - if (options.closeHoles) { - // See if there are any holes that need closing. - Array<uint32_t> &boundaryLoops = buffers.boundaryLoops; - meshGetBoundaryLoops(*m_unifiedMesh, boundaryLoops); - if (boundaryLoops.size() > 1) { -#if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR - const uint32_t faceCountBeforeHolesClosed = m_unifiedMesh->faceCount(); +#if XA_CHECK_T_JUNCTIONS + m_tjunctionCount = meshCheckTJunctions(*m_unifiedMesh); +#if XA_DEBUG_EXPORT_OBJ_TJUNCTION + if (m_tjunctionCount > 0) { + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_chart_%03u_tjunction.obj", sourceMesh->id(), chartGroupId, chartId); + m_unifiedMesh->writeObjFile(filename); + } #endif - // Closing the holes is not always the best solution and does not fix all the problems. - // We need to do some analysis of the holes and the genus to: - // - Find cuts that reduce genus. - // - Find cuts to connect holes. - // - Use minimal spanning trees or seamster. - XA_PROFILE_START(closeChartMeshHoles) - uint32_t holeCount = 0; -#if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR - Array<uint32_t> holeFaceCounts; - failed = !meshCloseHoles(m_unifiedMesh, boundaryLoops, m_basis.normal, &holeFaceCounts); -#else - failed = !meshCloseHoles(m_unifiedMesh, boundaryLoops, m_basis.normal, &holeCount, nullptr); #endif - XA_PROFILE_END(closeChartMeshHoles) - m_unifiedMesh->createBoundaries(); - m_unifiedMesh->linkBoundaries(); - meshGetBoundaryLoops(*m_unifiedMesh, boundaryLoops); - if (failed || boundaryLoops.size() > 1) - m_warningFlags |= ChartWarningFlags::CloseHolesFailed; - m_closedHolesCount = holeCount; -#if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR - if (m_warningFlags & ChartWarningFlags::CloseHolesFailed) { - char filename[256]; - XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_chart_%03u_close_holes_error.obj", sourceMesh->id(), chartGroupId, chartId); - FILE *file; - XA_FOPEN(file, filename, "w"); - if (file) { - m_unifiedMesh->writeObjVertices(file); - fprintf(file, "s off\n"); - fprintf(file, "o object\n"); - for (uint32_t i = 0; i < faceCountBeforeHolesClosed; i++) - m_unifiedMesh->writeObjFace(file, i); - uint32_t face = faceCountBeforeHolesClosed; - for (uint32_t i = 0; i < holeFaceCounts.size(); i++) { - fprintf(file, "s off\n"); - fprintf(file, "o hole%u\n", i); - for (uint32_t j = 0; j < holeFaceCounts[i]; j++) { - m_unifiedMesh->writeObjFace(file, face); - face++; - } - } - m_unifiedMesh->writeObjBoundaryEges(file); - m_unifiedMesh->writeObjLinkedBoundaries(file); - fclose(file); - } - } -#endif - } - } } -#if XA_RECOMPUTE_CHARTS - Chart(ChartCtorBuffers &buffers, const Chart *parent, const Mesh *parentMesh, ConstArrayView<uint32_t> faces, const Vector2 *texcoords, const Mesh *sourceMesh) : m_mesh(nullptr), m_unifiedMesh(nullptr), m_unmodifiedUnifiedMesh(nullptr), m_type(ChartType::Piecewise), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0), m_isInvalid(false) - { - const uint32_t faceCount = m_initialFaceCount = faces.length; + Chart(ChartCtorBuffers &buffers, const Chart *parent, const Mesh *parentMesh, ConstArrayView<uint32_t> faces, ConstArrayView<Vector2> texcoords, const Mesh *sourceMesh) : + m_unifiedMesh(nullptr), m_type(ChartType::Piecewise), m_generatorType(segment::ChartGeneratorType::Piecewise), m_tjunctionCount(0), m_originalVertexCount(0), m_isInvalid(false) { + const uint32_t faceCount = faces.length; m_faceToSourceFaceMap.resize(faceCount); for (uint32_t i = 0; i < faceCount; i++) m_faceToSourceFaceMap[i] = parent->m_faceToSourceFaceMap[faces[i]]; // Map faces to parent chart source mesh. // Copy face indices. - m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), m_faceToSourceFaceMap.size() * 3, m_faceToSourceFaceMap.size()); Array<uint32_t> &chartMeshIndices = buffers.chartMeshIndices; chartMeshIndices.resize(sourceMesh->vertexCount()); chartMeshIndices.fillBytes(0xff); + m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), m_faceToSourceFaceMap.size() * 3, m_faceToSourceFaceMap.size()); + HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToUnifiedVertexMap(MemTag::Mesh, m_faceToSourceFaceMap.size() * 3); // Add vertices. for (uint32_t f = 0; f < faceCount; f++) { for (uint32_t i = 0; i < 3; i++) { const uint32_t vertex = sourceMesh->vertexAt(m_faceToSourceFaceMap[f] * 3 + i); + const uint32_t sourceUnifiedVertex = sourceMesh->firstColocalVertex(vertex); const uint32_t parentVertex = parentMesh->vertexAt(faces[f] * 3 + i); - if (chartMeshIndices[vertex] == (uint32_t)~0) { - chartMeshIndices[vertex] = m_mesh->vertexCount(); + uint32_t unifiedVertex = sourceVertexToUnifiedVertexMap.get(sourceUnifiedVertex); + if (unifiedVertex == UINT32_MAX) { + unifiedVertex = sourceVertexToUnifiedVertexMap.add(sourceUnifiedVertex); + m_unifiedMesh->addVertex(sourceMesh->position(vertex), Vector3(0.0f), texcoords[parentVertex]); + } + if (chartMeshIndices[vertex] == UINT32_MAX) { + chartMeshIndices[vertex] = m_originalVertexCount; + m_originalVertexCount++; m_vertexToSourceVertexMap.push_back(vertex); - m_mesh->addVertex(sourceMesh->position(vertex), Vector3(0.0f), texcoords[parentVertex]); + m_chartVertexToUnifiedVertexMap.push_back(unifiedVertex); } } } // Add faces. + m_originalIndices.resize(faceCount * 3); for (uint32_t f = 0; f < faceCount; f++) { - uint32_t indices[3]; + uint32_t unifiedIndices[3]; for (uint32_t i = 0; i < 3; i++) { const uint32_t vertex = sourceMesh->vertexAt(m_faceToSourceFaceMap[f] * 3 + i); - indices[i] = chartMeshIndices[vertex]; + m_originalIndices[f * 3 + i] = chartMeshIndices[vertex]; + const uint32_t unifiedVertex = sourceMesh->firstColocalVertex(vertex); + unifiedIndices[i] = sourceVertexToUnifiedVertexMap.get(unifiedVertex); } - Mesh::AddFaceResult::Enum result = m_mesh->addFace(indices); - XA_UNUSED(result); - XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK); + m_unifiedMesh->addFace(unifiedIndices); } - m_mesh->createBoundaries(); // For AtlasPacker::computeBoundingBox - m_mesh->destroyEdgeMap(); // Only needed it for createBoundaries. + m_unifiedMesh->createBoundaries(); // Need to store texcoords for backup/restore so packing can be run multiple times. backupTexcoords(); } -#endif - ~Chart() - { - if (m_mesh) { - m_mesh->~Mesh(); - XA_FREE(m_mesh); + ~Chart() { + if (m_unifiedMesh) { + m_unifiedMesh->~Mesh(); + XA_FREE(m_unifiedMesh); + m_unifiedMesh = nullptr; } - destroyUnifiedMesh(); } bool isInvalid() const { return m_isInvalid; } - ChartType::Enum type() const { return m_type; } - uint32_t warningFlags() const { return m_warningFlags; } - uint32_t closedHolesCount() const { return m_closedHolesCount; } - uint32_t fixedTJunctionsCount() const { return m_fixedTJunctionsCount; } + ChartType type() const { return m_type; } + segment::ChartGeneratorType::Enum generatorType() const { return m_generatorType; } + uint32_t tjunctionCount() const { return m_tjunctionCount; } const Quality &quality() const { return m_quality; } - uint32_t initialFaceCount() const { return m_initialFaceCount; } #if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION const Array<uint32_t> ¶mFlippedFaces() const { return m_paramFlippedFaces; } #endif uint32_t mapFaceToSourceFace(uint32_t i) const { return m_faceToSourceFaceMap[i]; } uint32_t mapChartVertexToSourceVertex(uint32_t i) const { return m_vertexToSourceVertexMap[i]; } - const Mesh *mesh() const { return m_mesh; } - Mesh *mesh() { return m_mesh; } const Mesh *unifiedMesh() const { return m_unifiedMesh; } - const Mesh *unmodifiedUnifiedMesh() const { return m_unmodifiedUnifiedMesh; } + Mesh *unifiedMesh() { return m_unifiedMesh; } - void parameterize(const ParameterizeOptions &options, UniformGrid2 &boundaryGrid) - { - XA_PROFILE_START(parameterizeChartsOrthogonal) - { + // Vertex count of the chart mesh before unifying vertices. + uint32_t originalVertexCount() const { return m_originalVertexCount; } + + uint32_t originalVertexToUnifiedVertex(uint32_t v) const { return m_chartVertexToUnifiedVertexMap[v]; } + + ConstArrayView<uint32_t> originalVertices() const { return m_originalIndices; } + + void parameterize(const ChartOptions &options, UniformGrid2 &boundaryGrid) { + const uint32_t unifiedVertexCount = m_unifiedMesh->vertexCount(); + if (m_generatorType == segment::ChartGeneratorType::OriginalUv) { + } else { // Project vertices to plane. - const uint32_t vertexCount = m_unifiedMesh->vertexCount(); - for (uint32_t i = 0; i < vertexCount; i++) + XA_PROFILE_START(parameterizeChartsOrthogonal) + for (uint32_t i = 0; i < unifiedVertexCount; i++) m_unifiedMesh->texcoord(i) = Vector2(dot(m_basis.tangent, m_unifiedMesh->position(i)), dot(m_basis.bitangent, m_unifiedMesh->position(i))); - } - XA_PROFILE_END(parameterizeChartsOrthogonal) - // Computing charts checks for flipped triangles and boundary intersection. Don't need to do that again here if chart is planar. - if (m_type != ChartType::Planar) { - XA_PROFILE_START(parameterizeChartsEvaluateQuality) - m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); - m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, nullptr); - m_quality.computeMetrics(m_unifiedMesh, m_initialFaceCount); - XA_PROFILE_END(parameterizeChartsEvaluateQuality) - // Use orthogonal parameterization if quality is acceptable. - if (!m_quality.boundaryIntersection && m_quality.flippedTriangleCount == 0 && m_quality.totalGeometricArea > 0.0f && m_quality.stretchMetric <= 1.1f && m_quality.maxStretchMetric <= 1.25f) - m_type = ChartType::Ortho; - } - if (m_type == ChartType::LSCM) { - XA_PROFILE_START(parameterizeChartsLSCM) - if (options.func) { - options.func(&m_unifiedMesh->position(0).x, &m_unifiedMesh->texcoord(0).x, m_unifiedMesh->vertexCount(), m_unifiedMesh->indices(), m_unifiedMesh->indexCount()); - } - else - computeLeastSquaresConformalMap(m_unifiedMesh); - XA_PROFILE_END(parameterizeChartsLSCM) - XA_PROFILE_START(parameterizeChartsEvaluateQuality) - m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); + XA_PROFILE_END(parameterizeChartsOrthogonal) + // Computing charts checks for flipped triangles and boundary intersection. Don't need to do that again here if chart is planar. + if (m_type != ChartType::Planar && m_generatorType != segment::ChartGeneratorType::OriginalUv) { + XA_PROFILE_START(parameterizeChartsEvaluateQuality) + m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); + m_quality.computeFlippedFaces(m_unifiedMesh, nullptr); + m_quality.computeMetrics(m_unifiedMesh); + XA_PROFILE_END(parameterizeChartsEvaluateQuality) + // Use orthogonal parameterization if quality is acceptable. + if (!m_quality.boundaryIntersection && m_quality.flippedTriangleCount == 0 && m_quality.zeroAreaTriangleCount == 0 && m_quality.totalGeometricArea > 0.0f && m_quality.stretchMetric <= 1.1f && m_quality.maxStretchMetric <= 1.25f) + m_type = ChartType::Ortho; + } + if (m_type == ChartType::LSCM) { + XA_PROFILE_START(parameterizeChartsLSCM) + if (options.paramFunc) { + options.paramFunc(&m_unifiedMesh->position(0).x, &m_unifiedMesh->texcoord(0).x, m_unifiedMesh->vertexCount(), m_unifiedMesh->indices().data, m_unifiedMesh->indexCount()); + } else + computeLeastSquaresConformalMap(m_unifiedMesh); + XA_PROFILE_END(parameterizeChartsLSCM) + XA_PROFILE_START(parameterizeChartsEvaluateQuality) + m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); #if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION - m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, &m_paramFlippedFaces); + m_quality.computeFlippedFaces(m_unifiedMesh, &m_paramFlippedFaces); #else - m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, nullptr); + m_quality.computeFlippedFaces(m_unifiedMesh, nullptr); #endif - // Don't need to call computeMetrics here, that's only used in evaluateOrthoQuality to determine if quality is acceptable enough to use ortho projection. - if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0) - m_isInvalid = true; - XA_PROFILE_END(parameterizeChartsEvaluateQuality) + // Don't need to call computeMetrics here, that's only used in evaluateOrthoQuality to determine if quality is acceptable enough to use ortho projection. + if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0 || m_quality.zeroAreaTriangleCount > 0) + m_isInvalid = true; + XA_PROFILE_END(parameterizeChartsEvaluateQuality) + } } + if (options.fixWinding && m_unifiedMesh->computeFaceParametricArea(0) < 0.0f) { + for (uint32_t i = 0; i < unifiedVertexCount; i++) + m_unifiedMesh->texcoord(i).x *= -1.0f; + } +#if XA_CHECK_PARAM_WINDING + const uint32_t faceCount = m_unifiedMesh->faceCount(); + uint32_t flippedCount = 0; + for (uint32_t i = 0; i < faceCount; i++) { + const float area = m_unifiedMesh->computeFaceParametricArea(i); + if (area < 0.0f) + flippedCount++; + } + if (flippedCount == faceCount) { + XA_PRINT_WARNING("param: all faces flipped\n"); + } else if (flippedCount > 0) { + XA_PRINT_WARNING("param: %u / %u faces flipped\n", flippedCount, faceCount); + } +#endif + #if XA_DEBUG_ALL_CHARTS_INVALID m_isInvalid = true; #endif - // Transfer parameterization from unified mesh to chart mesh. - const uint32_t vertexCount = m_mesh->vertexCount(); - for (uint32_t v = 0; v < vertexCount; v++) - m_mesh->texcoord(v) = m_unifiedMesh->texcoord(m_chartVertexToUnifiedVertexMap[v]); - // Can destroy unified mesh now. - // But not if the parameterization is invalid, the unified mesh will be needed for PiecewiseParameterization. - if (!m_isInvalid) - destroyUnifiedMesh(); // Need to store texcoords for backup/restore so packing can be run multiple times. backupTexcoords(); } - Vector2 computeParametricBounds() const - { + Vector2 computeParametricBounds() const { Vector2 minCorner(FLT_MAX, FLT_MAX); Vector2 maxCorner(-FLT_MAX, -FLT_MAX); - const uint32_t vertexCount = m_mesh->vertexCount(); + const uint32_t vertexCount = m_unifiedMesh->vertexCount(); for (uint32_t v = 0; v < vertexCount; v++) { - minCorner = min(minCorner, m_mesh->texcoord(v)); - maxCorner = max(maxCorner, m_mesh->texcoord(v)); + minCorner = min(minCorner, m_unifiedMesh->texcoord(v)); + maxCorner = max(maxCorner, m_unifiedMesh->texcoord(v)); } return (maxCorner - minCorner) * 0.5f; } - void restoreTexcoords() - { - memcpy(m_mesh->texcoords(), m_backupTexcoords.data(), m_mesh->vertexCount() * sizeof(Vector2)); +#if XA_CHECK_PIECEWISE_CHART_QUALITY + void evaluateQuality(UniformGrid2 &boundaryGrid) { + m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); +#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION + m_quality.computeFlippedFaces(m_unifiedMesh, &m_paramFlippedFaces); +#else + m_quality.computeFlippedFaces(m_unifiedMesh, nullptr); +#endif + if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0 || m_quality.zeroAreaTriangleCount > 0) + m_isInvalid = true; } +#endif -private: - void backupTexcoords() - { - m_backupTexcoords.resize(m_mesh->vertexCount()); - memcpy(m_backupTexcoords.data(), m_mesh->texcoords(), m_mesh->vertexCount() * sizeof(Vector2)); + void restoreTexcoords() { + memcpy(m_unifiedMesh->texcoords().data, m_backupTexcoords.data(), m_unifiedMesh->vertexCount() * sizeof(Vector2)); } - void destroyUnifiedMesh() - { - if (m_unifiedMesh) { - m_unifiedMesh->~Mesh(); - XA_FREE(m_unifiedMesh); - m_unifiedMesh = nullptr; - } - if (m_unmodifiedUnifiedMesh) { - m_unmodifiedUnifiedMesh->~Mesh(); - XA_FREE(m_unmodifiedUnifiedMesh); - m_unmodifiedUnifiedMesh = nullptr; - } - // Don't need this when unified meshes are destroyed. - m_chartVertexToUnifiedVertexMap.destroy(); +private: + void backupTexcoords() { + m_backupTexcoords.resize(m_unifiedMesh->vertexCount()); + memcpy(m_backupTexcoords.data(), m_unifiedMesh->texcoords().data, m_unifiedMesh->vertexCount() * sizeof(Vector2)); } Basis m_basis; - Mesh *m_mesh; Mesh *m_unifiedMesh; - Mesh *m_unmodifiedUnifiedMesh; // Unified mesh before fixing t-junctions. Null if no t-junctions were fixed - ChartType::Enum m_type; - uint32_t m_warningFlags; - uint32_t m_initialFaceCount; // Before fixing T-junctions and/or closing holes. - uint32_t m_closedHolesCount, m_fixedTJunctionsCount; + ChartType m_type; + segment::ChartGeneratorType::Enum m_generatorType; + uint32_t m_tjunctionCount; + + uint32_t m_originalVertexCount; + Array<uint32_t> m_originalIndices; // List of faces of the source mesh that belong to this chart. Array<uint32_t> m_faceToSourceFaceMap; @@ -7604,47 +7040,46 @@ private: bool m_isInvalid; }; -struct CreateAndParameterizeChartTaskArgs -{ - const Basis *basis; +struct CreateAndParameterizeChartTaskGroupArgs { + Progress *progress; ThreadLocal<UniformGrid2> *boundaryGrid; + ThreadLocal<ChartCtorBuffers> *chartBuffers; + const ChartOptions *options; + ThreadLocal<PiecewiseParam> *pp; +}; + +struct CreateAndParameterizeChartTaskArgs { + const Basis *basis; Chart *chart; // output Array<Chart *> charts; // output (if more than one chart) - ThreadLocal<ChartCtorBuffers> *chartBuffers; + segment::ChartGeneratorType::Enum chartGeneratorType; const Mesh *mesh; - const ParameterizeOptions *options; -#if XA_RECOMPUTE_CHARTS - ThreadLocal<PiecewiseParam> *pp; -#endif ConstArrayView<uint32_t> faces; uint32_t chartGroupId; uint32_t chartId; }; -static void runCreateAndParameterizeChartTask(void *userData) -{ - auto args = (CreateAndParameterizeChartTaskArgs *)userData; +static void runCreateAndParameterizeChartTask(void *groupUserData, void *taskUserData) { + XA_PROFILE_START(createChartMeshAndParameterizeThread) + auto groupArgs = (CreateAndParameterizeChartTaskGroupArgs *)groupUserData; + auto args = (CreateAndParameterizeChartTaskArgs *)taskUserData; XA_PROFILE_START(createChartMesh) - args->chart = XA_NEW_ARGS(MemTag::Default, Chart, args->chartBuffers->get(), *args->options, *args->basis, args->faces, args->mesh, args->chartGroupId, args->chartId); + args->chart = XA_NEW_ARGS(MemTag::Default, Chart, *args->basis, args->chartGeneratorType, args->faces, args->mesh, args->chartGroupId, args->chartId); XA_PROFILE_END(createChartMesh) - args->chart->parameterize(*args->options, args->boundaryGrid->get()); + XA_PROFILE_START(parameterizeCharts) + args->chart->parameterize(*groupArgs->options, groupArgs->boundaryGrid->get()); + XA_PROFILE_END(parameterizeCharts) #if XA_RECOMPUTE_CHARTS - if (!args->chart->isInvalid()) + if (!args->chart->isInvalid()) { + XA_PROFILE_END(createChartMeshAndParameterizeThread) return; + } // Recompute charts with invalid parameterizations. XA_PROFILE_START(parameterizeChartsRecompute) Chart *invalidChart = args->chart; - // Fixing t-junctions rewrites unified mesh faces, and we need to map faces back to input mesh. So use the unmodified unified mesh. - const Mesh *invalidMesh = invalidChart->unmodifiedUnifiedMesh(); - uint32_t faceCount = 0; - if (invalidMesh) { - faceCount = invalidMesh->faceCount(); - } else { - invalidMesh = invalidChart->unifiedMesh(); - faceCount = invalidChart->initialFaceCount(); // Not invalidMesh->faceCount(). Don't want faces added by hole closing. - } - PiecewiseParam &pp = args->pp->get(); - pp.reset(invalidMesh, faceCount); + const Mesh *invalidMesh = invalidChart->unifiedMesh(); + PiecewiseParam &pp = groupArgs->pp->get(); + pp.reset(invalidMesh); #if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS char filename[256]; XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_chart_%03u_recomputed.obj", args->mesh->id(), args->chartGroupId, args->chartId); @@ -7658,7 +7093,10 @@ static void runCreateAndParameterizeChartTask(void *userData) XA_PROFILE_END(parameterizeChartsPiecewise) if (!facesRemaining) break; - Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, args->chartBuffers->get(), invalidChart, invalidMesh, pp.chartFaces(), pp.texcoords(), args->mesh); + Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, groupArgs->chartBuffers->get(), invalidChart, invalidMesh, pp.chartFaces(), pp.texcoords(), args->mesh); +#if XA_CHECK_PIECEWISE_CHART_QUALITY + chart->evaluateQuality(args->boundaryGrid->get()); +#endif args->charts.push_back(chart); #if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS if (file) { @@ -7686,50 +7124,63 @@ static void runCreateAndParameterizeChartTask(void *userData) #endif XA_PROFILE_END(parameterizeChartsRecompute) #endif // XA_RECOMPUTE_CHARTS + XA_PROFILE_END(createChartMeshAndParameterizeThread) + // Update progress. + groupArgs->progress->increment(args->faces.length); } // Set of charts corresponding to mesh faces in the same face group. -class ChartGroup -{ +class ChartGroup { public: - ChartGroup(uint32_t id, const Mesh *sourceMesh, const MeshFaceGroups *sourceMeshFaceGroups, MeshFaceGroups::Handle faceGroup) : m_id(id), m_sourceMesh(sourceMesh), m_sourceMeshFaceGroups(sourceMeshFaceGroups), m_faceGroup(faceGroup), m_faceCount(0), m_paramAddedChartsCount(0), m_paramDeletedChartsCount(0) - { + ChartGroup(uint32_t id, const Mesh *sourceMesh, const MeshFaceGroups *sourceMeshFaceGroups, MeshFaceGroups::Handle faceGroup) : + m_id(id), m_sourceMesh(sourceMesh), m_sourceMeshFaceGroups(sourceMeshFaceGroups), m_faceGroup(faceGroup) { } - ~ChartGroup() - { + ~ChartGroup() { for (uint32_t i = 0; i < m_charts.size(); i++) { m_charts[i]->~Chart(); XA_FREE(m_charts[i]); } } - uint32_t segmentChartCount() const { return m_chartBasis.size(); } uint32_t chartCount() const { return m_charts.size(); } Chart *chartAt(uint32_t i) const { return m_charts[i]; } - uint32_t faceCount() const { return m_faceCount; } - uint32_t paramAddedChartsCount() const { return m_paramAddedChartsCount; } - uint32_t paramDeletedChartsCount() const { return m_paramDeletedChartsCount; } + uint32_t faceCount() const { return m_sourceMeshFaceGroups->faceCount(m_faceGroup); } - void computeChartFaces(const ChartOptions &options, segment::Atlas &atlas) - { + void computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, Progress *progress, segment::Atlas &atlas, ThreadLocal<UniformGrid2> *boundaryGrid, ThreadLocal<ChartCtorBuffers> *chartBuffers, ThreadLocal<PiecewiseParam> *piecewiseParam) { + // This function may be called multiple times, so destroy existing charts. + for (uint32_t i = 0; i < m_charts.size(); i++) { + m_charts[i]->~Chart(); + XA_FREE(m_charts[i]); + } // Create mesh from source mesh, using only the faces in this face group. XA_PROFILE_START(createChartGroupMesh) Mesh *mesh = createMesh(); XA_PROFILE_END(createChartGroupMesh) // Segment mesh into charts (arrays of faces). #if XA_DEBUG_SINGLE_CHART - m_chartBasis.resize(1); - Fit::computeBasis(&mesh->position(0), mesh->vertexCount(), &m_chartBasis[0]); - m_chartFaces.resize(1 + mesh->faceCount()); - m_chartFaces[0] = mesh->faceCount(); - for (uint32_t i = 0; i < m_chartFaces.size(); i++) - m_chartFaces[i + 1] = i; + XA_UNUSED(options); + XA_UNUSED(atlas); + const uint32_t chartCount = 1; + uint32_t offset; + Basis chartBasis; + Fit::computeBasis(&mesh->position(0), mesh->vertexCount(), &chartBasis); + Array<uint32_t> chartFaces; + chartFaces.resize(1 + mesh->faceCount()); + chartFaces[0] = mesh->faceCount(); + for (uint32_t i = 0; i < chartFaces.size() - 1; i++) + chartFaces[i + 1] = m_faceToSourceFaceMap[i]; + // Destroy mesh. + const uint32_t faceCount = mesh->faceCount(); + mesh->~Mesh(); + XA_FREE(mesh); #else XA_PROFILE_START(buildAtlas) atlas.reset(mesh, options); atlas.compute(); XA_PROFILE_END(buildAtlas) + // Update progress. + progress->increment(faceCount()); #if XA_DEBUG_EXPORT_OBJ_CHARTS char filename[256]; XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_charts.obj", m_sourceMesh->id(), m_id); @@ -7745,7 +7196,6 @@ public: mesh->writeObjFace(file, faces[f]); } mesh->writeObjBoundaryEges(file); - mesh->writeObjLinkedBoundaries(file); fclose(file); } #endif @@ -7754,65 +7204,57 @@ public: mesh->~Mesh(); XA_FREE(mesh); XA_PROFILE_START(copyChartFaces) - // Copy basis. - const uint32_t chartCount = atlas.chartCount(); - m_chartBasis.resize(chartCount); - for (uint32_t i = 0; i < chartCount; i++) - m_chartBasis[i] = atlas.chartBasis(i); + if (progress->cancel) + return; // Copy faces from segment::Atlas to m_chartFaces array with <chart 0 face count> <face 0> <face n> <chart 1 face count> etc. encoding. // segment::Atlas faces refer to the chart group mesh. Map them to the input mesh instead. - m_chartFaces.resize(chartCount + faceCount); + const uint32_t chartCount = atlas.chartCount(); + Array<uint32_t> chartFaces; + chartFaces.resize(chartCount + faceCount); uint32_t offset = 0; for (uint32_t i = 0; i < chartCount; i++) { ConstArrayView<uint32_t> faces = atlas.chartFaces(i); - m_chartFaces[offset++] = faces.length; + chartFaces[offset++] = faces.length; for (uint32_t j = 0; j < faces.length; j++) - m_chartFaces[offset++] = m_faceToSourceFaceMap[faces[j]]; + chartFaces[offset++] = m_faceToSourceFaceMap[faces[j]]; } XA_PROFILE_END(copyChartFaces) #endif - } - -#if XA_RECOMPUTE_CHARTS - void parameterizeCharts(TaskScheduler *taskScheduler, const ParameterizeOptions &options, ThreadLocal<UniformGrid2> *boundaryGrid, ThreadLocal<ChartCtorBuffers> *chartBuffers, ThreadLocal<PiecewiseParam> *piecewiseParam) -#else - void parameterizeCharts(TaskScheduler* taskScheduler, const ParameterizeOptions &options, ThreadLocal<UniformGrid2>* boundaryGrid, ThreadLocal<ChartCtorBuffers>* chartBuffers) -#endif - { - // This function may be called multiple times, so destroy existing charts. - for (uint32_t i = 0; i < m_charts.size(); i++) { - m_charts[i]->~Chart(); - XA_FREE(m_charts[i]); - } - m_paramAddedChartsCount = 0; - const uint32_t chartCount = m_chartBasis.size(); + XA_PROFILE_START(createChartMeshAndParameterizeReal) + CreateAndParameterizeChartTaskGroupArgs groupArgs; + groupArgs.progress = progress; + groupArgs.boundaryGrid = boundaryGrid; + groupArgs.chartBuffers = chartBuffers; + groupArgs.options = &options; + groupArgs.pp = piecewiseParam; + TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(&groupArgs, chartCount); Array<CreateAndParameterizeChartTaskArgs> taskArgs; taskArgs.resize(chartCount); taskArgs.runCtors(); // Has Array member. - TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount); - uint32_t offset = 0; + offset = 0; for (uint32_t i = 0; i < chartCount; i++) { CreateAndParameterizeChartTaskArgs &args = taskArgs[i]; - args.basis = &m_chartBasis[i]; - args.boundaryGrid = boundaryGrid; +#if XA_DEBUG_SINGLE_CHART + args.basis = &chartBasis; + args.isPlanar = false; +#else + args.basis = &atlas.chartBasis(i); + args.chartGeneratorType = atlas.chartGeneratorType(i); +#endif args.chart = nullptr; args.chartGroupId = m_id; args.chartId = i; - args.chartBuffers = chartBuffers; - const uint32_t faceCount = m_chartFaces[offset++]; - args.faces = ConstArrayView<uint32_t>(&m_chartFaces[offset], faceCount); - offset += faceCount; + const uint32_t chartFaceCount = chartFaces[offset++]; + args.faces = ConstArrayView<uint32_t>(&chartFaces[offset], chartFaceCount); + offset += chartFaceCount; args.mesh = m_sourceMesh; - args.options = &options; -#if XA_RECOMPUTE_CHARTS - args.pp = piecewiseParam; -#endif Task task; task.userData = &args; task.func = runCreateAndParameterizeChartTask; taskScheduler->run(taskGroup, task); } taskScheduler->wait(&taskGroup); + XA_PROFILE_END(createChartMeshAndParameterizeReal) #if XA_RECOMPUTE_CHARTS // Count charts. Skip invalid ones and include new ones added by recomputing. uint32_t newChartCount = 0; @@ -7830,7 +7272,6 @@ public: if (chart->isInvalid()) { chart->~Chart(); XA_FREE(chart); - m_paramDeletedChartsCount++; continue; } m_charts[current++] = chart; @@ -7838,10 +7279,8 @@ public: // Now add new charts. for (uint32_t i = 0; i < chartCount; i++) { CreateAndParameterizeChartTaskArgs &args = taskArgs[i]; - for (uint32_t j = 0; j < args.charts.size(); j++) { + for (uint32_t j = 0; j < args.charts.size(); j++) m_charts[current++] = args.charts[j]; - m_paramAddedChartsCount++; - } } #else // XA_RECOMPUTE_CHARTS m_charts.resize(chartCount); @@ -7852,15 +7291,14 @@ public: } private: - Mesh *createMesh() - { + Mesh *createMesh() { XA_DEBUG_ASSERT(m_faceGroup != MeshFaceGroups::kInvalid); // Create new mesh from the source mesh, using faces that belong to this group. m_faceToSourceFaceMap.reserve(m_sourceMeshFaceGroups->faceCount(m_faceGroup)); for (MeshFaceGroups::Iterator it(m_sourceMeshFaceGroups, m_faceGroup); !it.isDone(); it.advance()) m_faceToSourceFaceMap.push_back(it.face()); // Only initial meshes has ignored faces. The only flag we care about is HasNormals. - const uint32_t faceCount = m_faceCount = m_faceToSourceFaceMap.size(); + const uint32_t faceCount = m_faceToSourceFaceMap.size(); XA_DEBUG_ASSERT(faceCount > 0); const uint32_t approxVertexCount = min(faceCount * 3, m_sourceMesh->vertexCount()); Mesh *mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, m_sourceMesh->epsilon(), approxVertexCount, faceCount, m_sourceMesh->flags() & MeshFlags::HasNormals); @@ -7889,9 +7327,7 @@ private: XA_DEBUG_ASSERT(indices[i] != UINT32_MAX); } // Don't copy flags - ignored faces aren't used by chart groups, they are handled by InvalidMeshGeometry. - Mesh::AddFaceResult::Enum result = mesh->addFace(indices); - XA_UNUSED(result); - XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK); + mesh->addFace(indices); } XA_PROFILE_START(createChartGroupMeshColocals) mesh->createColocals(); @@ -7909,98 +7345,57 @@ private: } const uint32_t m_id; - const Mesh * const m_sourceMesh; - const MeshFaceGroups * const m_sourceMeshFaceGroups; + const Mesh *const m_sourceMesh; + const MeshFaceGroups *const m_sourceMeshFaceGroups; const MeshFaceGroups::Handle m_faceGroup; Array<uint32_t> m_faceToSourceFaceMap; // List of faces of the source mesh that belong to this chart group. - Array<Basis> m_chartBasis; // Copied from segment::Atlas. - Array<uint32_t> m_chartFaces; // Copied from segment::Atlas. Encoding: <chart 0 face count> <face 0> <face n> <chart 1 face count> etc. Array<Chart *> m_charts; - uint32_t m_faceCount; // Set by createMesh(). Used for sorting. - uint32_t m_paramAddedChartsCount; // Number of new charts added by recomputing charts with invalid parameterizations. - uint32_t m_paramDeletedChartsCount; // Number of charts with invalid parameterizations that were deleted, after charts were recomputed. -}; - -// References invalid faces and vertices in a mesh. -struct InvalidMeshGeometry -{ - // Invalid faces have the face groups MeshFaceGroups::kInvalid. - void extract(const Mesh *mesh, const MeshFaceGroups *meshFaceGroups) - { - // Copy invalid faces. - m_faces.clear(); - const uint32_t meshFaceCount = mesh->faceCount(); - for (uint32_t f = 0; f < meshFaceCount; f++) { - if (meshFaceGroups->groupAt(f) == MeshFaceGroups::kInvalid) - m_faces.push_back(f); - } - // Create *unique* list of vertices of invalid faces. - const uint32_t faceCount = m_faces.size(); - m_indices.resize(faceCount * 3); - const uint32_t approxVertexCount = min(faceCount * 3, mesh->vertexCount()); - m_vertexToSourceVertexMap.clear(); - m_vertexToSourceVertexMap.reserve(approxVertexCount); - HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToVertexMap(MemTag::Mesh, approxVertexCount); - for (uint32_t f = 0; f < faceCount; f++) { - const uint32_t face = m_faces[f]; - for (uint32_t i = 0; i < 3; i++) { - const uint32_t vertex = mesh->vertexAt(face * 3 + i); - uint32_t newVertex = sourceVertexToVertexMap.get(vertex); - if (newVertex == UINT32_MAX) { - newVertex = sourceVertexToVertexMap.add(vertex); - m_vertexToSourceVertexMap.push_back(vertex); - } - m_indices[f * 3 + i] = newVertex; - } - } - } - - ConstArrayView<uint32_t> faces() const { return m_faces; } - ConstArrayView<uint32_t> indices() const { return m_indices; } - ConstArrayView<uint32_t> vertices() const { return m_vertexToSourceVertexMap; } - -private: - Array<uint32_t> m_faces, m_indices; - Array<uint32_t> m_vertexToSourceVertexMap; // Map face vertices to vertices of the source mesh. }; -struct ChartGroupComputeChartFacesTaskArgs -{ +struct ChartGroupComputeChartsTaskGroupArgs { ThreadLocal<segment::Atlas> *atlas; - ChartGroup *chartGroup; const ChartOptions *options; Progress *progress; + TaskScheduler *taskScheduler; + ThreadLocal<UniformGrid2> *boundaryGrid; + ThreadLocal<ChartCtorBuffers> *chartBuffers; + ThreadLocal<PiecewiseParam> *piecewiseParam; }; -static void runChartGroupComputeChartFacesJob(void *userData) -{ - auto args = (ChartGroupComputeChartFacesTaskArgs *)userData; +static void runChartGroupComputeChartsTask(void *groupUserData, void *taskUserData) { + auto args = (ChartGroupComputeChartsTaskGroupArgs *)groupUserData; + auto chartGroup = (ChartGroup *)taskUserData; if (args->progress->cancel) return; XA_PROFILE_START(chartGroupComputeChartsThread) - args->chartGroup->computeChartFaces(*args->options, args->atlas->get()); + chartGroup->computeCharts(args->taskScheduler, *args->options, args->progress, args->atlas->get(), args->boundaryGrid, args->chartBuffers, args->piecewiseParam); XA_PROFILE_END(chartGroupComputeChartsThread) } -struct MeshComputeChartFacesTaskArgs -{ - Array<ChartGroup *> *chartGroups; // output - InvalidMeshGeometry *invalidMeshGeometry; // output +struct MeshComputeChartsTaskGroupArgs { ThreadLocal<segment::Atlas> *atlas; const ChartOptions *options; Progress *progress; - const Mesh *sourceMesh; TaskScheduler *taskScheduler; + ThreadLocal<UniformGrid2> *boundaryGrid; + ThreadLocal<ChartCtorBuffers> *chartBuffers; + ThreadLocal<PiecewiseParam> *piecewiseParam; +}; + +struct MeshComputeChartsTaskArgs { + const Mesh *sourceMesh; + Array<ChartGroup *> *chartGroups; // output + InvalidMeshGeometry *invalidMeshGeometry; // output }; #if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS static uint32_t s_faceGroupsCurrentVertex = 0; #endif -static void runMeshComputeChartFacesJob(void *userData) -{ - auto args = (MeshComputeChartFacesTaskArgs *)userData; - if (args->progress->cancel) +static void runMeshComputeChartsTask(void *groupUserData, void *taskUserData) { + auto groupArgs = (MeshComputeChartsTaskGroupArgs *)groupUserData; + auto args = (MeshComputeChartsTaskArgs *)taskUserData; + if (groupArgs->progress->cancel) return; XA_PROFILE_START(computeChartsThread) // Create face groups. @@ -8009,7 +7404,7 @@ static void runMeshComputeChartFacesJob(void *userData) meshFaceGroups->compute(); const uint32_t chartGroupCount = meshFaceGroups->groupCount(); XA_PROFILE_END(createFaceGroups) - if (args->progress->cancel) + if (groupArgs->progress->cancel) goto cleanup; #if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS { @@ -8053,33 +7448,41 @@ static void runMeshComputeChartFacesJob(void *userData) for (uint32_t i = 0; i < chartGroupCount; i++) (*args->chartGroups)[i] = XA_NEW_ARGS(MemTag::Default, ChartGroup, i, args->sourceMesh, meshFaceGroups, MeshFaceGroups::Handle(i)); // Extract invalid geometry via the invalid face group (MeshFaceGroups::kInvalid). - XA_PROFILE_START(extractInvalidMeshGeometry) - args->invalidMeshGeometry->extract(args->sourceMesh, meshFaceGroups); - XA_PROFILE_END(extractInvalidMeshGeometry) - // One task for each chart group - compute chart faces. + { + XA_PROFILE_START(extractInvalidMeshGeometry) + args->invalidMeshGeometry->extract(args->sourceMesh, meshFaceGroups); + XA_PROFILE_END(extractInvalidMeshGeometry) + } + // One task for each chart group - compute charts. { XA_PROFILE_START(chartGroupComputeChartsReal) - Array<ChartGroupComputeChartFacesTaskArgs> taskArgs; - taskArgs.resize(chartGroupCount); - for (uint32_t i = 0; i < chartGroupCount; i++) { - taskArgs[i].atlas = args->atlas; - taskArgs[i].chartGroup = (*args->chartGroups)[i]; - taskArgs[i].options = args->options; - taskArgs[i].progress = args->progress; - } - TaskGroupHandle taskGroup = args->taskScheduler->createTaskGroup(chartGroupCount); + // Sort chart groups by face count. + Array<float> chartGroupSortData; + chartGroupSortData.resize(chartGroupCount); + for (uint32_t i = 0; i < chartGroupCount; i++) + chartGroupSortData[i] = (float)(*args->chartGroups)[i]->faceCount(); + RadixSort chartGroupSort; + chartGroupSort.sort(chartGroupSortData); + // Larger chart groups are added first to reduce the chance of thread starvation. + ChartGroupComputeChartsTaskGroupArgs taskGroupArgs; + taskGroupArgs.atlas = groupArgs->atlas; + taskGroupArgs.options = groupArgs->options; + taskGroupArgs.progress = groupArgs->progress; + taskGroupArgs.taskScheduler = groupArgs->taskScheduler; + taskGroupArgs.boundaryGrid = groupArgs->boundaryGrid; + taskGroupArgs.chartBuffers = groupArgs->chartBuffers; + taskGroupArgs.piecewiseParam = groupArgs->piecewiseParam; + TaskGroupHandle taskGroup = groupArgs->taskScheduler->createTaskGroup(&taskGroupArgs, chartGroupCount); for (uint32_t i = 0; i < chartGroupCount; i++) { Task task; - task.userData = &taskArgs[i]; - task.func = runChartGroupComputeChartFacesJob; - args->taskScheduler->run(taskGroup, task); + task.userData = (*args->chartGroups)[chartGroupCount - i - 1]; + task.func = runChartGroupComputeChartsTask; + groupArgs->taskScheduler->run(taskGroup, task); } - args->taskScheduler->wait(&taskGroup); + groupArgs->taskScheduler->wait(&taskGroup); XA_PROFILE_END(chartGroupComputeChartsReal) } XA_PROFILE_END(computeChartsThread) - args->progress->value++; - args->progress->update(); cleanup: if (meshFaceGroups) { meshFaceGroups->~MeshFaceGroups(); @@ -8087,43 +7490,13 @@ cleanup: } } -struct ParameterizeChartsTaskArgs -{ - TaskScheduler *taskScheduler; - ChartGroup *chartGroup; - const ParameterizeOptions *options; - ThreadLocal<UniformGrid2> *boundaryGrid; - ThreadLocal<ChartCtorBuffers> *chartBuffers; -#if XA_RECOMPUTE_CHARTS - ThreadLocal<PiecewiseParam> *piecewiseParam; -#endif - Progress *progress; -}; - -static void runParameterizeChartsJob(void *userData) -{ - auto args = (ParameterizeChartsTaskArgs *)userData; - if (args->progress->cancel) - return; - XA_PROFILE_START(parameterizeChartsThread) -#if XA_RECOMPUTE_CHARTS - args->chartGroup->parameterizeCharts(args->taskScheduler, *args->options, args->boundaryGrid, args->chartBuffers, args->piecewiseParam); -#else - args->chartGroup->parameterizeCharts(args->taskScheduler, *args->options, args->boundaryGrid, args->chartBuffers); -#endif - XA_PROFILE_END(parameterizeChartsThread) - args->progress->value++; - args->progress->update(); -} - /// An atlas is a set of chart groups. -class Atlas -{ +class Atlas { public: - Atlas() : m_chartsComputed(false), m_chartsParameterized(false) {} + Atlas() : + m_chartsComputed(false) {} - ~Atlas() - { + ~Atlas() { for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) { for (uint32_t j = 0; j < m_meshChartGroups[i].size(); j++) { m_meshChartGroups[i][j]->~ChartGroup(); @@ -8137,22 +7510,25 @@ public: uint32_t meshCount() const { return m_meshes.size(); } const InvalidMeshGeometry &invalidMeshGeometry(uint32_t meshIndex) const { return m_invalidMeshGeometry[meshIndex]; } bool chartsComputed() const { return m_chartsComputed; } - bool chartsParameterized() const { return m_chartsParameterized; } uint32_t chartGroupCount(uint32_t mesh) const { return m_meshChartGroups[mesh].size(); } const ChartGroup *chartGroupAt(uint32_t mesh, uint32_t group) const { return m_meshChartGroups[mesh][group]; } - void addMesh(const Mesh *mesh) - { + void addMesh(const Mesh *mesh) { m_meshes.push_back(mesh); } - bool computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, ProgressFunc progressFunc, void *progressUserData) - { + bool computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, ProgressFunc progressFunc, void *progressUserData) { + XA_PROFILE_START(computeChartsReal) #if XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS segment::s_planarRegionsCurrentRegion = segment::s_planarRegionsCurrentVertex = 0; #endif + // Progress is per-face x 2 (1 for chart faces, 1 for parameterized chart faces). + const uint32_t meshCount = m_meshes.size(); + uint32_t totalFaceCount = 0; + for (uint32_t i = 0; i < meshCount; i++) + totalFaceCount += m_meshes[i]->faceCount(); + Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, totalFaceCount * 2); m_chartsComputed = false; - m_chartsParameterized = false; // Clear chart groups, since this function may be called multiple times. if (!m_meshChartGroups.isEmpty()) { for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) { @@ -8162,27 +7538,20 @@ public: } m_meshChartGroups[i].clear(); } - XA_ASSERT(m_meshChartGroups.size() == m_meshes.size()); // The number of meshes shouldn't have changed. + XA_ASSERT(m_meshChartGroups.size() == meshCount); // The number of meshes shouldn't have changed. } - m_meshChartGroups.resize(m_meshes.size()); + m_meshChartGroups.resize(meshCount); m_meshChartGroups.runCtors(); - m_invalidMeshGeometry.resize(m_meshes.size()); + m_invalidMeshGeometry.resize(meshCount); m_invalidMeshGeometry.runCtors(); // One task per mesh. - const uint32_t meshCount = m_meshes.size(); - Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, meshCount); - ThreadLocal<segment::Atlas> atlas; - Array<MeshComputeChartFacesTaskArgs> taskArgs; + Array<MeshComputeChartsTaskArgs> taskArgs; taskArgs.resize(meshCount); for (uint32_t i = 0; i < meshCount; i++) { - MeshComputeChartFacesTaskArgs &args = taskArgs[i]; - args.atlas = &atlas; + MeshComputeChartsTaskArgs &args = taskArgs[i]; + args.sourceMesh = m_meshes[i]; args.chartGroups = &m_meshChartGroups[i]; args.invalidMeshGeometry = &m_invalidMeshGeometry[i]; - args.options = &options; - args.progress = &progress; - args.sourceMesh = m_meshes[i]; - args.taskScheduler = taskScheduler; } // Sort meshes by indexCount. Array<float> meshSortData; @@ -8192,105 +7561,53 @@ public: RadixSort meshSort; meshSort.sort(meshSortData); // Larger meshes are added first to reduce the chance of thread starvation. - TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(meshCount); - for (uint32_t i = 0; i < meshCount; i++) { - Task task; - task.userData = &taskArgs[meshSort.ranks()[meshCount - i - 1]]; - task.func = runMeshComputeChartFacesJob; - taskScheduler->run(taskGroup, task); - } - taskScheduler->wait(&taskGroup); - if (progress.cancel) - return false; - m_chartsComputed = true; - return true; - } - - bool parameterizeCharts(TaskScheduler *taskScheduler, const ParameterizeOptions &options, ProgressFunc progressFunc, void *progressUserData) - { - m_chartsParameterized = false; - uint32_t chartGroupCount = 0; - for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) - chartGroupCount += m_meshChartGroups[i].size(); - Progress progress(ProgressCategory::ParameterizeCharts, progressFunc, progressUserData, chartGroupCount); + ThreadLocal<segment::Atlas> atlas; ThreadLocal<UniformGrid2> boundaryGrid; // For Quality boundary intersection. ThreadLocal<ChartCtorBuffers> chartBuffers; -#if XA_RECOMPUTE_CHARTS ThreadLocal<PiecewiseParam> piecewiseParam; -#endif - Array<ParameterizeChartsTaskArgs> taskArgs; - taskArgs.resize(chartGroupCount); - { - uint32_t k = 0; - for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) { - const uint32_t count = m_meshChartGroups[i].size(); - for (uint32_t j = 0; j < count; j++) { - ParameterizeChartsTaskArgs &args = taskArgs[k]; - args.taskScheduler = taskScheduler; - args.chartGroup = m_meshChartGroups[i][j]; - args.options = &options; - args.boundaryGrid = &boundaryGrid; - args.chartBuffers = &chartBuffers; -#if XA_RECOMPUTE_CHARTS - args.piecewiseParam = &piecewiseParam; -#endif - args.progress = &progress; - k++; - } - } - } - // Sort chart groups by face count. - Array<float> chartGroupSortData; - chartGroupSortData.resize(chartGroupCount); - { - uint32_t k = 0; - for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) { - const uint32_t count = m_meshChartGroups[i].size(); - for (uint32_t j = 0; j < count; j++) { - chartGroupSortData[k++] = (float)m_meshChartGroups[i][j]->faceCount(); - } - } - } - RadixSort chartGroupSort; - chartGroupSort.sort(chartGroupSortData); - // Larger chart groups are added first to reduce the chance of thread starvation. - TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartGroupCount); - for (uint32_t i = 0; i < chartGroupCount; i++) { + MeshComputeChartsTaskGroupArgs taskGroupArgs; + taskGroupArgs.atlas = &atlas; + taskGroupArgs.options = &options; + taskGroupArgs.progress = &progress; + taskGroupArgs.taskScheduler = taskScheduler; + taskGroupArgs.boundaryGrid = &boundaryGrid; + taskGroupArgs.chartBuffers = &chartBuffers; + taskGroupArgs.piecewiseParam = &piecewiseParam; + TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(&taskGroupArgs, meshCount); + for (uint32_t i = 0; i < meshCount; i++) { Task task; - task.userData = &taskArgs[chartGroupSort.ranks()[chartGroupCount - i - 1]]; - task.func = runParameterizeChartsJob; + task.userData = &taskArgs[meshSort.ranks()[meshCount - i - 1]]; + task.func = runMeshComputeChartsTask; taskScheduler->run(taskGroup, task); } taskScheduler->wait(&taskGroup); + XA_PROFILE_END(computeChartsReal) if (progress.cancel) return false; - m_chartsParameterized = true; + m_chartsComputed = true; return true; } private: Array<const Mesh *> m_meshes; Array<InvalidMeshGeometry> m_invalidMeshGeometry; // 1 per mesh. - Array<Array<ChartGroup *> > m_meshChartGroups; + Array<Array<ChartGroup *>> m_meshChartGroups; bool m_chartsComputed; - bool m_chartsParameterized; }; } // namespace param namespace pack { -class AtlasImage -{ +class AtlasImage { public: - AtlasImage(uint32_t width, uint32_t height) : m_width(width), m_height(height) - { + AtlasImage(uint32_t width, uint32_t height) : + m_width(width), m_height(height) { m_data.resize(m_width * m_height); memset(m_data.data(), 0, sizeof(uint32_t) * m_data.size()); } - void resize(uint32_t width, uint32_t height) - { + void resize(uint32_t width, uint32_t height) { Array<uint32_t> data; data.resize(width * height); memset(data.data(), 0, sizeof(uint32_t) * data.size()); @@ -8301,8 +7618,7 @@ public: data.moveTo(m_data); } - void addChart(uint32_t chartIndex, const BitImage *image, const BitImage *imageBilinear, const BitImage *imagePadding, int atlas_w, int atlas_h, int offset_x, int offset_y) - { + void addChart(uint32_t chartIndex, const BitImage *image, const BitImage *imageBilinear, const BitImage *imagePadding, int atlas_w, int atlas_h, int offset_x, int offset_y) { const int w = image->width(); const int h = image->height(); for (int y = 0; y < h; y++) { @@ -8328,15 +7644,13 @@ public: } } - void copyTo(uint32_t *dest, uint32_t destWidth, uint32_t destHeight, int padding) const - { + void copyTo(uint32_t *dest, uint32_t destWidth, uint32_t destHeight, int padding) const { for (uint32_t y = 0; y < destHeight; y++) memcpy(&dest[y * destWidth], &m_data[padding + (y + padding) * m_width], destWidth * sizeof(uint32_t)); } #if XA_DEBUG_EXPORT_ATLAS_IMAGES - void writeTga(const char *filename, uint32_t width, uint32_t height) const - { + void writeTga(const char *filename, uint32_t width, uint32_t height) const { Array<uint8_t> image; image.resize(width * height * 3); for (uint32_t y = 0; y < height; y++) { @@ -8378,18 +7692,14 @@ private: Array<uint32_t> m_data; }; -struct Chart -{ +struct Chart { int32_t atlasIndex; uint32_t material; - uint32_t indexCount; - const uint32_t *indices; + ConstArrayView<uint32_t> indices; float parametricArea; float surfaceArea; - Vector2 *vertices; - uint32_t vertexCount; + ArrayView<Vector2> vertices; Array<uint32_t> uniqueVertices; - bool allowRotate; // bounding box Vector2 majorAxis, minorAxis, minCorner, maxCorner; // Mesh only @@ -8398,29 +7708,26 @@ struct Chart Array<uint32_t> faces; Vector2 &uniqueVertexAt(uint32_t v) { return uniqueVertices.isEmpty() ? vertices[v] : vertices[uniqueVertices[v]]; } - uint32_t uniqueVertexCount() const { return uniqueVertices.isEmpty() ? vertexCount : uniqueVertices.size(); } + uint32_t uniqueVertexCount() const { return uniqueVertices.isEmpty() ? vertices.length : uniqueVertices.size(); } }; -struct AddChartTaskArgs -{ - ThreadLocal<BoundingBox2D> *boundingBox; +struct AddChartTaskArgs { param::Chart *paramChart; Chart *chart; // out }; -static void runAddChartTask(void *userData) -{ +static void runAddChartTask(void *groupUserData, void *taskUserData) { XA_PROFILE_START(packChartsAddChartsThread) - auto args = (AddChartTaskArgs *)userData; + auto boundingBox = (ThreadLocal<BoundingBox2D> *)groupUserData; + auto args = (AddChartTaskArgs *)taskUserData; param::Chart *paramChart = args->paramChart; XA_PROFILE_START(packChartsAddChartsRestoreTexcoords) paramChart->restoreTexcoords(); XA_PROFILE_END(packChartsAddChartsRestoreTexcoords) - Mesh *mesh = paramChart->mesh(); + Mesh *mesh = paramChart->unifiedMesh(); Chart *chart = args->chart = XA_NEW(MemTag::Default, Chart); chart->atlasIndex = -1; chart->material = 0; - chart->indexCount = mesh->indexCount(); chart->indices = mesh->indices(); chart->parametricArea = mesh->computeParametricArea(); if (chart->parametricArea < kAreaEpsilon) { @@ -8430,17 +7737,15 @@ static void runAddChartTask(void *userData) } chart->surfaceArea = mesh->computeSurfaceArea(); chart->vertices = mesh->texcoords(); - chart->vertexCount = mesh->vertexCount(); - chart->allowRotate = true; chart->boundaryEdges = &mesh->boundaryEdges(); // Compute bounding box of chart. - BoundingBox2D &bb = args->boundingBox->get(); + BoundingBox2D &bb = boundingBox->get(); bb.clear(); - for (uint32_t v = 0; v < chart->vertexCount; v++) { + for (uint32_t v = 0; v < chart->vertices.length; v++) { if (mesh->isBoundaryVertex(v)) bb.appendBoundaryVertex(mesh->texcoord(v)); } - bb.compute(mesh->texcoords(), mesh->vertexCount()); + bb.compute(mesh->texcoords()); chart->majorAxis = bb.majorAxis; chart->minorAxis = bb.minorAxis; chart->minCorner = bb.minCorner; @@ -8448,10 +7753,8 @@ static void runAddChartTask(void *userData) XA_PROFILE_END(packChartsAddChartsThread) } -struct Atlas -{ - ~Atlas() - { +struct Atlas { + ~Atlas() { for (uint32_t i = 0; i < m_atlasImages.size(); i++) { m_atlasImages[i]->~AtlasImage(); XA_FREE(m_atlasImages[i]); @@ -8475,8 +7778,7 @@ struct Atlas const Array<AtlasImage *> &getImages() const { return m_atlasImages; } float getUtilization(uint32_t atlas) const { return m_utilization[atlas]; } - void addCharts(TaskScheduler *taskScheduler, param::Atlas *paramAtlas) - { + void addCharts(TaskScheduler *taskScheduler, param::Atlas *paramAtlas) { // Count charts. uint32_t chartCount = 0; for (uint32_t i = 0; i < paramAtlas->meshCount(); i++) { @@ -8489,11 +7791,11 @@ struct Atlas if (chartCount == 0) return; // Run one task per chart. + ThreadLocal<BoundingBox2D> boundingBox; + TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(&boundingBox, chartCount); Array<AddChartTaskArgs> taskArgs; taskArgs.resize(chartCount); - TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount); uint32_t chartIndex = 0; - ThreadLocal<BoundingBox2D> boundingBox; for (uint32_t i = 0; i < paramAtlas->meshCount(); i++) { const uint32_t chartGroupsCount = paramAtlas->chartGroupCount(i); for (uint32_t j = 0; j < chartGroupsCount; j++) { @@ -8501,7 +7803,6 @@ struct Atlas const uint32_t count = chartGroup->chartCount(); for (uint32_t k = 0; k < count; k++) { AddChartTaskArgs &args = taskArgs[chartIndex]; - args.boundingBox = &boundingBox; args.paramChart = chartGroup->chartAt(k); Task task; task.userData = &taskArgs[chartIndex]; @@ -8518,8 +7819,10 @@ struct Atlas m_charts[i] = taskArgs[i].chart; } - void addUvMeshCharts(UvMeshInstance *mesh) - { + void addUvMeshCharts(UvMeshInstance *mesh) { + // Copy texcoords from mesh. + mesh->texcoords.resize(mesh->mesh->texcoords.size()); + memcpy(mesh->texcoords.data(), mesh->mesh->texcoords.data(), mesh->texcoords.size() * sizeof(Vector2)); BitArray vertexUsed(mesh->texcoords.size()); BoundingBox2D boundingBox; for (uint32_t c = 0; c < mesh->mesh->charts.size(); c++) { @@ -8527,17 +7830,14 @@ struct Atlas Chart *chart = XA_NEW(MemTag::Default, Chart); chart->atlasIndex = -1; chart->material = uvChart->material; - chart->indexCount = uvChart->indices.size(); - chart->indices = uvChart->indices.data(); - chart->vertices = mesh->texcoords.data(); - chart->vertexCount = mesh->texcoords.size(); - chart->allowRotate = mesh->rotateCharts; + chart->indices = uvChart->indices; + chart->vertices = mesh->texcoords; chart->boundaryEdges = nullptr; chart->faces.resize(uvChart->faces.size()); memcpy(chart->faces.data(), uvChart->faces.data(), sizeof(uint32_t) * uvChart->faces.size()); // Find unique vertices. vertexUsed.zeroOutMemory(); - for (uint32_t i = 0; i < chart->indexCount; i++) { + for (uint32_t i = 0; i < chart->indices.length; i++) { const uint32_t vertex = chart->indices[i]; if (!vertexUsed.get(vertex)) { vertexUsed.set(vertex); @@ -8546,14 +7846,13 @@ struct Atlas } // Compute parametric and surface areas. chart->parametricArea = 0.0f; - for (uint32_t f = 0; f < chart->indexCount / 3; f++) { + for (uint32_t f = 0; f < chart->indices.length / 3; f++) { const Vector2 &v1 = chart->vertices[chart->indices[f * 3 + 0]]; const Vector2 &v2 = chart->vertices[chart->indices[f * 3 + 1]]; const Vector2 &v3 = chart->vertices[chart->indices[f * 3 + 2]]; chart->parametricArea += fabsf(triangleArea(v1, v2, v3)); } chart->parametricArea *= 0.5f; - chart->surfaceArea = chart->parametricArea; // Identical for UV meshes. if (chart->parametricArea < kAreaEpsilon) { // When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers. Vector2 minCorner(FLT_MAX, FLT_MAX); @@ -8565,6 +7864,9 @@ struct Atlas const Vector2 bounds = (maxCorner - minCorner) * 0.5f; chart->parametricArea = bounds.x * bounds.y; } + XA_DEBUG_ASSERT(isFinite(chart->parametricArea)); + XA_DEBUG_ASSERT(!isNan(chart->parametricArea)); + chart->surfaceArea = chart->parametricArea; // Identical for UV meshes. // Compute bounding box of chart. // Using all unique vertices for simplicity, can compute real boundaries if this is too slow. boundingBox.clear(); @@ -8580,8 +7882,7 @@ struct Atlas } // Pack charts in the smallest possible rectangle. - bool packCharts(const PackOptions &options, ProgressFunc progressFunc, void *progressUserData) - { + bool packCharts(const PackOptions &options, ProgressFunc progressFunc, void *progressUserData) { if (progressFunc) { if (!progressFunc(ProgressCategory::PackCharts, 0, progressUserData)) return false; @@ -8627,19 +7928,19 @@ struct Atlas // Compute chart scale float scale = 1.0f; if (chart->parametricArea != 0.0f) { - scale = (chart->surfaceArea / chart->parametricArea) * m_texelsPerUnit; + scale = sqrtf(chart->surfaceArea / chart->parametricArea) * m_texelsPerUnit; XA_ASSERT(isFinite(scale)); } // Translate, rotate and scale vertices. Compute extents. Vector2 minCorner(FLT_MAX, FLT_MAX); - if (!chart->allowRotate) { + if (!options.rotateChartsToAxis) { for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++) minCorner = min(minCorner, chart->uniqueVertexAt(i)); } Vector2 extents(0.0f); for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++) { Vector2 &texcoord = chart->uniqueVertexAt(i); - if (chart->allowRotate) { + if (options.rotateChartsToAxis) { const float x = dot(texcoord, chart->majorAxis); const float y = dot(texcoord, chart->minorAxis); texcoord.x = x; @@ -8750,27 +8051,27 @@ struct Atlas // Resize and clear (discard = true) chart images. // Leave room for padding at extents. chartImage.resize(ftoi_ceil(chartExtents[c].x) + options.padding, ftoi_ceil(chartExtents[c].y) + options.padding, true); - if (chart->allowRotate) + if (options.rotateCharts) chartImageRotated.resize(chartImage.height(), chartImage.width(), true); if (options.bilinear) { chartImageBilinear.resize(chartImage.width(), chartImage.height(), true); - if (chart->allowRotate) + if (options.rotateCharts) chartImageBilinearRotated.resize(chartImage.height(), chartImage.width(), true); } // Rasterize chart faces. - const uint32_t faceCount = chart->indexCount / 3; + const uint32_t faceCount = chart->indices.length / 3; for (uint32_t f = 0; f < faceCount; f++) { Vector2 vertices[3]; for (uint32_t v = 0; v < 3; v++) vertices[v] = chart->vertices[chart->indices[f * 3 + v]]; DrawTriangleCallbackArgs args; args.chartBitImage = &chartImage; - args.chartBitImageRotated = chart->allowRotate ? &chartImageRotated : nullptr; + args.chartBitImageRotated = options.rotateCharts ? &chartImageRotated : nullptr; raster::drawTriangle(Vector2((float)chartImage.width(), (float)chartImage.height()), vertices, drawTriangleCallback, &args); } // Expand chart by pixels sampled by bilinear interpolation. if (options.bilinear) - bilinearExpand(chart, &chartImage, &chartImageBilinear, chart->allowRotate ? &chartImageBilinearRotated : nullptr, boundaryEdgeGrid); + bilinearExpand(chart, &chartImage, &chartImageBilinear, options.rotateCharts ? &chartImageBilinearRotated : nullptr, boundaryEdgeGrid); // Expand chart by padding pixels (dilation). if (options.padding > 0) { // Copy into the same BitImage instances for every chart to avoid reallocating BitImage buffers (largest chart is packed first). @@ -8780,7 +8081,7 @@ struct Atlas else chartImage.copyTo(chartImagePadding); chartImagePadding.dilate(options.padding); - if (chart->allowRotate) { + if (options.rotateCharts) { if (options.bilinear) chartImageBilinearRotated.copyTo(chartImagePaddingRotated); else @@ -8815,23 +8116,25 @@ struct Atlas int best_x = 0, best_y = 0; int best_cw = 0, best_ch = 0; int best_r = 0; - for (;;) - { + for (;;) { +#if XA_DEBUG bool firstChartInBitImage = false; - XA_UNUSED(firstChartInBitImage); +#endif if (currentAtlas + 1 > m_bitImages.size()) { // Chart doesn't fit in the current bitImage, create a new one. BitImage *bi = XA_NEW_ARGS(MemTag::Default, BitImage, resolution, resolution); m_bitImages.push_back(bi); atlasSizes.push_back(Vector2i(0, 0)); +#if XA_DEBUG firstChartInBitImage = true; +#endif if (createImage) m_atlasImages.push_back(XA_NEW_ARGS(MemTag::Default, AtlasImage, resolution, resolution)); // Start positions are per-atlas, so create a new one of those too. chartStartPositions.push_back(Vector2i(0, 0)); } XA_PROFILE_START(packChartsFindLocation) - const bool foundLocation = findChartLocation(chartStartPositions[currentAtlas], options.bruteForce, m_bitImages[currentAtlas], chartImageToPack, chartImageToPackRotated, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, &best_x, &best_y, &best_cw, &best_ch, &best_r, options.blockAlign, maxResolution, chart->allowRotate); + const bool foundLocation = findChartLocation(options, chartStartPositions[currentAtlas], m_bitImages[currentAtlas], chartImageToPack, chartImageToPackRotated, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, &best_x, &best_y, &best_cw, &best_ch, &best_r, maxResolution); XA_PROFILE_END(packChartsFindLocation) XA_DEBUG_ASSERT(!(firstChartInBitImage && !foundLocation)); // Chart doesn't fit in an empty, newly allocated bitImage. Shouldn't happen, since charts are resized if they are too big to fit in the atlas. if (maxResolution == 0) { @@ -8849,8 +8152,7 @@ struct Atlas if (best_x + best_cw > atlasSizes[currentAtlas].x || best_y + best_ch > atlasSizes[currentAtlas].y) { for (uint32_t j = 0; j < chartStartPositions.size(); j++) chartStartPositions[j] = Vector2i(0, 0); - } - else { + } else { chartStartPositions[currentAtlas] = Vector2i(best_x, best_y); } } @@ -8897,7 +8199,7 @@ struct Atlas Vector2 &texcoord = chart->uniqueVertexAt(v); Vector2 t = texcoord; if (best_r) { - XA_DEBUG_ASSERT(chart->allowRotate); + XA_DEBUG_ASSERT(options.rotateCharts); swap(t.x, t.y); } texcoord.x = best_x + t.x; @@ -8938,8 +8240,7 @@ struct Atlas } if (m_utilization.size() > 1) { XA_PRINT(" %u: %f%% utilization\n", i, m_utilization[i] * 100.0f); - } - else { + } else { XA_PRINT(" %f%% utilization\n", m_utilization[i] * 100.0f); } } @@ -8958,28 +8259,22 @@ struct Atlas } private: - // IC: Brute force is slow, and random may take too much time to converge. We start inserting large charts in a small atlas. Using brute force is lame, because most of the space - // is occupied at this point. At the end we have many small charts and a large atlas with sparse holes. Finding those holes randomly is slow. A better approach would be to - // start stacking large charts as if they were tetris pieces. Once charts get small try to place them randomly. It may be interesting to try a intermediate strategy, first try - // along one axis and then try exhaustively along that axis. - bool findChartLocation(const Vector2i &startPosition, bool bruteForce, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, uint32_t maxResolution, bool allowRotate) - { + bool findChartLocation(const PackOptions &options, const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, uint32_t maxResolution) { const int attempts = 4096; - if (bruteForce || attempts >= w * h) - return findChartLocation_bruteForce(startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned, maxResolution, allowRotate); - return findChartLocation_random(atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, attempts, blockAligned, maxResolution, allowRotate); + if (options.bruteForce || attempts >= w * h) + return findChartLocation_bruteForce(options, startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, maxResolution); + return findChartLocation_random(options, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, attempts, maxResolution); } - bool findChartLocation_bruteForce(const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, uint32_t maxResolution, bool allowRotate) - { - const int stepSize = blockAligned ? 4 : 1; + bool findChartLocation_bruteForce(const PackOptions &options, const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, uint32_t maxResolution) { + const int stepSize = options.blockAlign ? 4 : 1; int best_metric = INT_MAX; // Try two different orientations. for (int r = 0; r < 2; r++) { int cw = chartBitImage->width(); int ch = chartBitImage->height(); if (r == 1) { - if (allowRotate) + if (options.rotateCharts) swap(cw, ch); else break; @@ -9016,15 +8311,14 @@ private: return best_metric != INT_MAX; } - bool findChartLocation_random(const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, int minTrialCount, bool blockAligned, uint32_t maxResolution, bool allowRotate) - { + bool findChartLocation_random(const PackOptions &options, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, int attempts, uint32_t maxResolution) { bool result = false; const int BLOCK_SIZE = 4; int best_metric = INT_MAX; - for (int i = 0; i < minTrialCount; i++) { + for (int i = 0; i < attempts; i++) { int cw = chartBitImage->width(); int ch = chartBitImage->height(); - int r = allowRotate ? m_rand.getRange(1) : 0; + int r = options.rotateCharts ? m_rand.getRange(1) : 0; if (r == 1) swap(cw, ch); // + 1 to extend atlas in case atlas full. We may want to use a higher number to increase probability of extending atlas. @@ -9037,7 +8331,7 @@ private: } int x = m_rand.getRange(xRange); int y = m_rand.getRange(yRange); - if (blockAligned) { + if (options.blockAlign) { x = align(x, BLOCK_SIZE); y = align(y, BLOCK_SIZE); if (maxResolution > 0 && (x > (int)maxResolution - cw || y > (int)maxResolution - ch)) @@ -9062,7 +8356,7 @@ private: *best_y = y; *best_w = cw; *best_h = ch; - *best_r = allowRotate ? r : 0; + *best_r = options.rotateCharts ? r : 0; if (area == w * h) { // Chart is completely inside, do not look at any other location. break; @@ -9072,8 +8366,7 @@ private: return result; } - void addChart(BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int atlas_w, int atlas_h, int offset_x, int offset_y, int r) - { + void addChart(BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int atlas_w, int atlas_h, int offset_x, int offset_y, int r) { XA_DEBUG_ASSERT(r == 0 || r == 1); const BitImage *image = r == 0 ? chartBitImage : chartBitImageRotated; const int w = image->width(); @@ -9096,15 +8389,14 @@ private: } } - void bilinearExpand(const Chart *chart, BitImage *source, BitImage *dest, BitImage *destRotated, UniformGrid2 &boundaryEdgeGrid) const - { + void bilinearExpand(const Chart *chart, BitImage *source, BitImage *dest, BitImage *destRotated, UniformGrid2 &boundaryEdgeGrid) const { boundaryEdgeGrid.reset(chart->vertices, chart->indices); if (chart->boundaryEdges) { const uint32_t edgeCount = chart->boundaryEdges->size(); for (uint32_t i = 0; i < edgeCount; i++) boundaryEdgeGrid.append((*chart->boundaryEdges)[i]); } else { - for (uint32_t i = 0; i < chart->indexCount; i++) + for (uint32_t i = 0; i < chart->indices.length; i++) boundaryEdgeGrid.append(i); } const int xOffsets[] = { -1, 0, 1, -1, 1, -1, 0, 1 }; @@ -9152,13 +8444,11 @@ private: } } - struct DrawTriangleCallbackArgs - { + struct DrawTriangleCallbackArgs { BitImage *chartBitImage, *chartBitImageRotated; }; - static bool drawTriangleCallback(void *param, int x, int y) - { + static bool drawTriangleCallback(void *param, int x, int y) { auto args = (DrawTriangleCallbackArgs *)param; args->chartBitImage->set(x, y); if (args->chartBitImageRotated) @@ -9180,8 +8470,14 @@ private: } // namespace pack } // namespace internal -struct Context -{ +// Used to map triangulated polygons back to polygons. +struct MeshPolygonMapping { + internal::Array<uint8_t> faceVertexCount; // Copied from MeshDecl::faceVertexCount. + internal::Array<uint32_t> triangleToPolygonMap; // Triangle index (mesh face index) to polygon index. + internal::Array<uint32_t> triangleToPolygonIndicesMap; // Triangle indices to polygon indices. +}; + +struct Context { Atlas atlas; internal::Progress *addMeshProgress = nullptr; internal::TaskGroupHandle addMeshTaskGroup; @@ -9190,20 +8486,20 @@ struct Context void *progressUserData = nullptr; internal::TaskScheduler *taskScheduler; internal::Array<internal::Mesh *> meshes; + internal::Array<MeshPolygonMapping *> meshPolygonMappings; internal::Array<internal::UvMesh *> uvMeshes; internal::Array<internal::UvMeshInstance *> uvMeshInstances; + bool uvMeshChartsComputed = false; }; -Atlas *Create() -{ +Atlas *Create() { Context *ctx = XA_NEW(internal::MemTag::Default, Context); memset(&ctx->atlas, 0, sizeof(Atlas)); ctx->taskScheduler = XA_NEW(internal::MemTag::Default, internal::TaskScheduler); return &ctx->atlas; } -static void DestroyOutputMeshes(Context *ctx) -{ +static void DestroyOutputMeshes(Context *ctx) { if (!ctx->atlas.meshes) return; for (int i = 0; i < (int)ctx->atlas.meshCount; i++) { @@ -9224,8 +8520,7 @@ static void DestroyOutputMeshes(Context *ctx) ctx->atlas.meshes = nullptr; } -void Destroy(Atlas *atlas) -{ +void Destroy(Atlas *atlas) { XA_DEBUG_ASSERT(atlas); Context *ctx = (Context *)atlas; if (atlas->utilization) @@ -9244,6 +8539,13 @@ void Destroy(Atlas *atlas) mesh->~Mesh(); XA_FREE(mesh); } + for (uint32_t i = 0; i < ctx->meshPolygonMappings.size(); i++) { + MeshPolygonMapping *mapping = ctx->meshPolygonMappings[i]; + if (mapping) { + mapping->~MeshPolygonMapping(); + XA_FREE(mapping); + } + } for (uint32_t i = 0; i < ctx->uvMeshes.size(); i++) { internal::UvMesh *mesh = ctx->uvMeshes[i]; for (uint32_t j = 0; j < mesh->charts.size(); j++) { @@ -9265,66 +8567,52 @@ void Destroy(Atlas *atlas) #endif } -struct AddMeshTaskArgs -{ - Context *ctx; - internal::Mesh *mesh; -}; - -static void runAddMeshTask(void *userData) -{ +static void runAddMeshTask(void *groupUserData, void *taskUserData) { XA_PROFILE_START(addMeshThread) - auto args = (AddMeshTaskArgs *)userData; // Responsible for freeing this. - internal::Mesh *mesh = args->mesh; - internal::Progress *progress = args->ctx->addMeshProgress; - if (progress->cancel) - goto cleanup; - { - XA_PROFILE_START(addMeshCreateColocals) - mesh->createColocals(); - XA_PROFILE_END(addMeshCreateColocals) + auto ctx = (Context *)groupUserData; + auto mesh = (internal::Mesh *)taskUserData; + internal::Progress *progress = ctx->addMeshProgress; + if (progress->cancel) { + XA_PROFILE_END(addMeshThread) + return; } - if (progress->cancel) - goto cleanup; - progress->value++; - progress->update(); -cleanup: - args->~AddMeshTaskArgs(); - XA_FREE(args); + XA_PROFILE_START(addMeshCreateColocals) + mesh->createColocals(); + XA_PROFILE_END(addMeshCreateColocals) + if (progress->cancel) { + XA_PROFILE_END(addMeshThread) + return; + } + progress->increment(1); XA_PROFILE_END(addMeshThread) } -static internal::Vector3 DecodePosition(const MeshDecl &meshDecl, uint32_t index) -{ +static internal::Vector3 DecodePosition(const MeshDecl &meshDecl, uint32_t index) { XA_DEBUG_ASSERT(meshDecl.vertexPositionData); XA_DEBUG_ASSERT(meshDecl.vertexPositionStride > 0); return *((const internal::Vector3 *)&((const uint8_t *)meshDecl.vertexPositionData)[meshDecl.vertexPositionStride * index]); } -static internal::Vector3 DecodeNormal(const MeshDecl &meshDecl, uint32_t index) -{ +static internal::Vector3 DecodeNormal(const MeshDecl &meshDecl, uint32_t index) { XA_DEBUG_ASSERT(meshDecl.vertexNormalData); XA_DEBUG_ASSERT(meshDecl.vertexNormalStride > 0); return *((const internal::Vector3 *)&((const uint8_t *)meshDecl.vertexNormalData)[meshDecl.vertexNormalStride * index]); } -static internal::Vector2 DecodeUv(const MeshDecl &meshDecl, uint32_t index) -{ +static internal::Vector2 DecodeUv(const MeshDecl &meshDecl, uint32_t index) { XA_DEBUG_ASSERT(meshDecl.vertexUvData); XA_DEBUG_ASSERT(meshDecl.vertexUvStride > 0); return *((const internal::Vector2 *)&((const uint8_t *)meshDecl.vertexUvData)[meshDecl.vertexUvStride * index]); } -static uint32_t DecodeIndex(IndexFormat::Enum format, const void *indexData, int32_t offset, uint32_t i) -{ +static uint32_t DecodeIndex(IndexFormat format, const void *indexData, int32_t offset, uint32_t i) { XA_DEBUG_ASSERT(indexData); if (format == IndexFormat::UInt16) return uint16_t((int32_t)((const uint16_t *)indexData)[i] + offset); return uint32_t((int32_t)((const uint32_t *)indexData)[i] + offset); } -AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint) -{ +AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint) { XA_DEBUG_ASSERT(atlas); if (!atlas) { XA_PRINT_WARNING("AddMesh: atlas is null.\n"); @@ -9337,33 +8625,36 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh } #if XA_PROFILE if (ctx->meshes.isEmpty()) - internal::s_profile.addMeshReal = clock(); + internal::s_profile.addMeshRealStart = std::chrono::high_resolution_clock::now(); #endif // Don't know how many times AddMesh will be called, so progress needs to adjusted each time. if (!ctx->addMeshProgress) { ctx->addMeshProgress = XA_NEW_ARGS(internal::MemTag::Default, internal::Progress, ProgressCategory::AddMesh, ctx->progressFunc, ctx->progressUserData, 1); - } - else { + } else { ctx->addMeshProgress->setMaxValue(internal::max(ctx->meshes.size() + 1, meshCountHint)); } XA_PROFILE_START(addMeshCopyData) const bool hasIndices = meshDecl.indexCount > 0; const uint32_t indexCount = hasIndices ? meshDecl.indexCount : meshDecl.vertexCount; - XA_PRINT("Adding mesh %d: %u vertices, %u triangles\n", ctx->meshes.size(), meshDecl.vertexCount, indexCount / 3); - // Expecting triangle faces. - if ((indexCount % 3) != 0) - return AddMeshError::InvalidIndexCount; - if (hasIndices) { - // Check if any index is out of range. - for (uint32_t i = 0; i < indexCount; i++) { - const uint32_t index = DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, i); - if (index >= meshDecl.vertexCount) - return AddMeshError::IndexOutOfRange; + uint32_t faceCount = indexCount / 3; + if (meshDecl.faceVertexCount) { + faceCount = meshDecl.faceCount; + XA_PRINT("Adding mesh %d: %u vertices, %u polygons\n", ctx->meshes.size(), meshDecl.vertexCount, faceCount); + for (uint32_t f = 0; f < faceCount; f++) { + if (meshDecl.faceVertexCount[f] < 3) + return AddMeshError::InvalidFaceVertexCount; } + } else { + XA_PRINT("Adding mesh %d: %u vertices, %u triangles\n", ctx->meshes.size(), meshDecl.vertexCount, faceCount); + // Expecting triangle faces unless otherwise specified. + if ((indexCount % 3) != 0) + return AddMeshError::InvalidIndexCount; } uint32_t meshFlags = internal::MeshFlags::HasIgnoredFaces; if (meshDecl.vertexNormalData) meshFlags |= internal::MeshFlags::HasNormals; + if (meshDecl.faceMaterialData) + meshFlags |= internal::MeshFlags::HasMaterials; internal::Mesh *mesh = XA_NEW_ARGS(internal::MemTag::Mesh, internal::Mesh, meshDecl.epsilon, meshDecl.vertexCount, indexCount / 3, meshFlags, ctx->meshes.size()); for (uint32_t i = 0; i < meshDecl.vertexCount; i++) { internal::Vector3 normal(0.0f); @@ -9374,17 +8665,42 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh texcoord = DecodeUv(meshDecl, i); mesh->addVertex(DecodePosition(meshDecl, i), normal, texcoord); } + MeshPolygonMapping *meshPolygonMapping = nullptr; + if (meshDecl.faceVertexCount) { + meshPolygonMapping = XA_NEW(internal::MemTag::Default, MeshPolygonMapping); + // Copy MeshDecl::faceVertexCount so it can be used later when building output meshes. + meshPolygonMapping->faceVertexCount.copyFrom(meshDecl.faceVertexCount, meshDecl.faceCount); + // There should be at least as many triangles as polygons. + meshPolygonMapping->triangleToPolygonMap.reserve(meshDecl.faceCount); + meshPolygonMapping->triangleToPolygonIndicesMap.reserve(meshDecl.indexCount); + } const uint32_t kMaxWarnings = 50; uint32_t warningCount = 0; - for (uint32_t i = 0; i < indexCount / 3; i++) { - uint32_t tri[3]; - for (int j = 0; j < 3; j++) - tri[j] = hasIndices ? DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, i * 3 + j) : i * 3 + j; + internal::Array<uint32_t> triIndices; + uint32_t firstFaceIndex = 0; + internal::Triangulator triangulator; + for (uint32_t face = 0; face < faceCount; face++) { + // Decode face indices. + const uint32_t faceVertexCount = meshDecl.faceVertexCount ? (uint32_t)meshDecl.faceVertexCount[face] : 3; + uint32_t polygon[UINT8_MAX]; + for (uint32_t i = 0; i < faceVertexCount; i++) { + if (hasIndices) { + polygon[i] = DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, face * faceVertexCount + i); + // Check if any index is out of range. + if (polygon[i] >= meshDecl.vertexCount) { + mesh->~Mesh(); + XA_FREE(mesh); + return AddMeshError::IndexOutOfRange; + } + } else { + polygon[i] = face * faceVertexCount + i; + } + } + // Ignore faces with degenerate or zero length edges. bool ignore = false; - // Check for degenerate or zero length edges. - for (int j = 0; j < 3; j++) { - const uint32_t index1 = tri[j]; - const uint32_t index2 = tri[(j + 1) % 3]; + for (uint32_t i = 0; i < faceVertexCount; i++) { + const uint32_t index1 = polygon[i]; + const uint32_t index2 = polygon[(i + 1) % 3]; if (index1 == index2) { ignore = true; if (++warningCount <= kMaxWarnings) @@ -9402,119 +8718,136 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh } // Ignore faces with any nan vertex attributes. if (!ignore) { - for (int j = 0; j < 3; j++) { - const internal::Vector3 &pos = mesh->position(tri[j]); + for (uint32_t i = 0; i < faceVertexCount; i++) { + const internal::Vector3 &pos = mesh->position(polygon[i]); if (internal::isNan(pos.x) || internal::isNan(pos.y) || internal::isNan(pos.z)) { if (++warningCount <= kMaxWarnings) - XA_PRINT(" NAN position in face: %d\n", i); + XA_PRINT(" NAN position in face: %d\n", face); ignore = true; break; } if (meshDecl.vertexNormalData) { - const internal::Vector3 &normal = mesh->normal(tri[j]); + const internal::Vector3 &normal = mesh->normal(polygon[i]); if (internal::isNan(normal.x) || internal::isNan(normal.y) || internal::isNan(normal.z)) { if (++warningCount <= kMaxWarnings) - XA_PRINT(" NAN normal in face: %d\n", i); + XA_PRINT(" NAN normal in face: %d\n", face); ignore = true; break; } } if (meshDecl.vertexUvData) { - const internal::Vector2 &uv = mesh->texcoord(tri[j]); + const internal::Vector2 &uv = mesh->texcoord(polygon[i]); if (internal::isNan(uv.x) || internal::isNan(uv.y)) { if (++warningCount <= kMaxWarnings) - XA_PRINT(" NAN texture coordinate in face: %d\n", i); + XA_PRINT(" NAN texture coordinate in face: %d\n", face); ignore = true; break; } } } } - const internal::Vector3 &a = mesh->position(tri[0]); - const internal::Vector3 &b = mesh->position(tri[1]); - const internal::Vector3 &c = mesh->position(tri[2]); - // Check for zero area faces. - float area = 0.0f; - if (!ignore) { - area = internal::length(internal::cross(b - a, c - a)) * 0.5f; - if (area <= internal::kAreaEpsilon) { - ignore = true; - if (++warningCount <= kMaxWarnings) - XA_PRINT(" Zero area face: %d, indices (%d %d %d), area is %f\n", i, tri[0], tri[1], tri[2], area); - } + // Triangulate if necessary. + triIndices.clear(); + if (faceVertexCount == 3) { + triIndices.push_back(polygon[0]); + triIndices.push_back(polygon[1]); + triIndices.push_back(polygon[2]); + } else { + triangulator.triangulatePolygon(mesh->positions(), internal::ConstArrayView<uint32_t>(polygon, faceVertexCount), triIndices); } + // Check for zero area faces. if (!ignore) { - if (internal::equal(a, b, meshDecl.epsilon) || internal::equal(a, c, meshDecl.epsilon) || internal::equal(b, c, meshDecl.epsilon)) { - ignore = true; - if (++warningCount <= kMaxWarnings) - XA_PRINT(" Degenerate face: %d, area is %f\n", i, area); + for (uint32_t i = 0; i < triIndices.size(); i += 3) { + const internal::Vector3 &a = mesh->position(triIndices[i + 0]); + const internal::Vector3 &b = mesh->position(triIndices[i + 1]); + const internal::Vector3 &c = mesh->position(triIndices[i + 2]); + const float area = internal::length(internal::cross(b - a, c - a)) * 0.5f; + if (area <= internal::kAreaEpsilon) { + ignore = true; + if (++warningCount <= kMaxWarnings) + XA_PRINT(" Zero area face: %d, area is %f\n", face, area); + break; + } } } - if (meshDecl.faceIgnoreData && meshDecl.faceIgnoreData[i]) + // User face ignore. + if (meshDecl.faceIgnoreData && meshDecl.faceIgnoreData[face]) ignore = true; - mesh->addFace(tri[0], tri[1], tri[2], ignore); + // User material. + uint32_t material = UINT32_MAX; + if (meshDecl.faceMaterialData) + material = meshDecl.faceMaterialData[face]; + // Add the face(s). + for (uint32_t i = 0; i < triIndices.size(); i += 3) { + mesh->addFace(&triIndices[i], ignore, material); + if (meshPolygonMapping) + meshPolygonMapping->triangleToPolygonMap.push_back(face); + } + if (meshPolygonMapping) { + for (uint32_t i = 0; i < triIndices.size(); i++) + meshPolygonMapping->triangleToPolygonIndicesMap.push_back(triIndices[i]); + } + firstFaceIndex += faceVertexCount; } if (warningCount > kMaxWarnings) XA_PRINT(" %u additional warnings truncated\n", warningCount - kMaxWarnings); XA_PROFILE_END(addMeshCopyData) ctx->meshes.push_back(mesh); + ctx->meshPolygonMappings.push_back(meshPolygonMapping); ctx->paramAtlas.addMesh(mesh); if (ctx->addMeshTaskGroup.value == UINT32_MAX) - ctx->addMeshTaskGroup = ctx->taskScheduler->createTaskGroup(); - AddMeshTaskArgs *taskArgs = XA_NEW(internal::MemTag::Default, AddMeshTaskArgs); // The task frees this. - taskArgs->ctx = ctx; - taskArgs->mesh = mesh; + ctx->addMeshTaskGroup = ctx->taskScheduler->createTaskGroup(ctx); internal::Task task; - task.userData = taskArgs; + task.userData = mesh; task.func = runAddMeshTask; ctx->taskScheduler->run(ctx->addMeshTaskGroup, task); return AddMeshError::Success; } -void AddMeshJoin(Atlas *atlas) -{ +void AddMeshJoin(Atlas *atlas) { XA_DEBUG_ASSERT(atlas); if (!atlas) { XA_PRINT_WARNING("AddMeshJoin: atlas is null.\n"); return; } Context *ctx = (Context *)atlas; - if (!ctx->addMeshProgress) - return; - ctx->taskScheduler->wait(&ctx->addMeshTaskGroup); - ctx->addMeshProgress->~Progress(); - XA_FREE(ctx->addMeshProgress); - ctx->addMeshProgress = nullptr; + if (!ctx->uvMeshes.isEmpty()) { #if XA_PROFILE - XA_PRINT("Added %u meshes\n", ctx->meshes.size()); - internal::s_profile.addMeshReal = clock() - internal::s_profile.addMeshReal; + XA_PRINT("Added %u UV meshes\n", ctx->uvMeshes.size()); + internal::s_profile.addMeshReal = uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - internal::s_profile.addMeshRealStart).count()); #endif - XA_PROFILE_PRINT_AND_RESET(" Total (real): ", addMeshReal) - XA_PROFILE_PRINT_AND_RESET(" Copy data: ", addMeshCopyData) - XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", addMeshThread) - XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", addMeshCreateColocals) + XA_PROFILE_PRINT_AND_RESET(" Total: ", addMeshReal) + XA_PROFILE_PRINT_AND_RESET(" Copy data: ", addMeshCopyData) #if XA_PROFILE_ALLOC - XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc) + XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc) #endif - XA_PRINT_MEM_USAGE + XA_PRINT_MEM_USAGE + } else { + if (!ctx->addMeshProgress) + return; + ctx->taskScheduler->wait(&ctx->addMeshTaskGroup); + ctx->addMeshProgress->~Progress(); + XA_FREE(ctx->addMeshProgress); + ctx->addMeshProgress = nullptr; +#if XA_PROFILE + XA_PRINT("Added %u meshes\n", ctx->meshes.size()); + internal::s_profile.addMeshReal = uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - internal::s_profile.addMeshRealStart).count()); +#endif + XA_PROFILE_PRINT_AND_RESET(" Total (real): ", addMeshReal) + XA_PROFILE_PRINT_AND_RESET(" Copy data: ", addMeshCopyData) + XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", addMeshThread) + XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", addMeshCreateColocals) +#if XA_PROFILE_ALLOC + XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc) +#endif + XA_PRINT_MEM_USAGE #if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS - internal::param::s_faceGroupsCurrentVertex = 0; + internal::param::s_faceGroupsCurrentVertex = 0; #endif + } } -struct EdgeKey -{ - EdgeKey() {} - EdgeKey(const EdgeKey &k) : v0(k.v0), v1(k.v1) {} - EdgeKey(uint32_t v0, uint32_t v1) : v0(v0), v1(v1) {} - bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; } - - uint32_t v0; - uint32_t v1; -}; - -AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl) -{ +AddMeshError AddUvMesh(Atlas *atlas, const UvMeshDecl &decl) { XA_DEBUG_ASSERT(atlas); if (!atlas) { XA_PRINT_WARNING("AddUvMesh: atlas is null.\n"); @@ -9525,13 +8858,18 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl) XA_PRINT_WARNING("AddUvMesh: Meshes and UV meshes cannot be added to the same atlas.\n"); return AddMeshError::Error; } - const bool decoded = (decl.indexCount <= 0); - const uint32_t indexCount = decoded ? decl.vertexCount : decl.indexCount; +#if XA_PROFILE + if (ctx->uvMeshInstances.isEmpty()) + internal::s_profile.addMeshRealStart = std::chrono::high_resolution_clock::now(); +#endif + XA_PROFILE_START(addMeshCopyData) + const bool hasIndices = decl.indexCount > 0; + const uint32_t indexCount = hasIndices ? decl.indexCount : decl.vertexCount; XA_PRINT("Adding UV mesh %d: %u vertices, %u triangles\n", ctx->uvMeshes.size(), decl.vertexCount, indexCount / 3); // Expecting triangle faces. if ((indexCount % 3) != 0) return AddMeshError::InvalidIndexCount; - if (!decoded) { + if (hasIndices) { // Check if any index is out of range. for (uint32_t i = 0; i < indexCount; i++) { const uint32_t index = DecodeIndex(decl.indexFormat, decl.indexData, decl.indexOffset, i); @@ -9539,319 +8877,266 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl) return AddMeshError::IndexOutOfRange; } } + // Create a mesh instance. internal::UvMeshInstance *meshInstance = XA_NEW(internal::MemTag::Default, internal::UvMeshInstance); - meshInstance->texcoords.resize(decl.vertexCount); - for (uint32_t i = 0; i < decl.vertexCount; i++) { - internal::Vector2 texcoord = *((const internal::Vector2 *)&((const uint8_t *)decl.vertexUvData)[decl.vertexStride * i]); - // Set nan values to 0. - if (internal::isNan(texcoord.x) || internal::isNan(texcoord.y)) - texcoord.x = texcoord.y = 0.0f; - meshInstance->texcoords[i] = texcoord; - } - meshInstance->rotateCharts = decl.rotateCharts; + meshInstance->mesh = nullptr; + ctx->uvMeshInstances.push_back(meshInstance); // See if this is an instance of an already existing mesh. internal::UvMesh *mesh = nullptr; for (uint32_t m = 0; m < ctx->uvMeshes.size(); m++) { if (memcmp(&ctx->uvMeshes[m]->decl, &decl, sizeof(UvMeshDecl)) == 0) { - meshInstance->mesh = mesh = ctx->uvMeshes[m]; + mesh = ctx->uvMeshes[m]; + XA_PRINT(" instance of a previous UV mesh\n"); break; } } if (!mesh) { // Copy geometry to mesh. - meshInstance->mesh = mesh = XA_NEW(internal::MemTag::Default, internal::UvMesh); + mesh = XA_NEW(internal::MemTag::Default, internal::UvMesh); + ctx->uvMeshes.push_back(mesh); mesh->decl = decl; + if (decl.faceMaterialData) { + mesh->faceMaterials.resize(decl.indexCount / 3); + memcpy(mesh->faceMaterials.data(), decl.faceMaterialData, mesh->faceMaterials.size() * sizeof(uint32_t)); + } mesh->indices.resize(decl.indexCount); for (uint32_t i = 0; i < indexCount; i++) - mesh->indices[i] = decoded ? i : DecodeIndex(decl.indexFormat, decl.indexData, decl.indexOffset, i); - mesh->vertexToChartMap.resize(decl.vertexCount); - for (uint32_t i = 0; i < mesh->vertexToChartMap.size(); i++) - mesh->vertexToChartMap[i] = UINT32_MAX; - // Calculate charts (incident faces). - internal::HashMap<internal::Vector2> vertexToFaceMap(internal::MemTag::Default, indexCount); // Face is index / 3 - const uint32_t faceCount = indexCount / 3; - for (uint32_t i = 0; i < indexCount; i++) - vertexToFaceMap.add(meshInstance->texcoords[mesh->indices[i]]); - internal::BitArray faceAssigned(faceCount); - faceAssigned.zeroOutMemory(); - for (uint32_t f = 0; f < faceCount; f++) { - if (faceAssigned.get(f)) - continue; - // Found an unassigned face, create a new chart. - internal::UvMeshChart *chart = XA_NEW(internal::MemTag::Default, internal::UvMeshChart); - chart->material = decl.faceMaterialData ? decl.faceMaterialData[f] : 0; - // Walk incident faces and assign them to the chart. - faceAssigned.set(f); - chart->faces.push_back(f); - for (;;) { - bool newFaceAssigned = false; - const uint32_t faceCount2 = chart->faces.size(); - for (uint32_t f2 = 0; f2 < faceCount2; f2++) { - const uint32_t face = chart->faces[f2]; - for (uint32_t i = 0; i < 3; i++) { - const internal::Vector2 &texcoord = meshInstance->texcoords[meshInstance->mesh->indices[face * 3 + i]]; - uint32_t mapIndex = vertexToFaceMap.get(texcoord); - while (mapIndex != UINT32_MAX) { - const uint32_t face2 = mapIndex / 3; // 3 vertices added per face. - // Materials must match. - if (!faceAssigned.get(face2) && (!decl.faceMaterialData || decl.faceMaterialData[face] == decl.faceMaterialData[face2])) { - faceAssigned.set(face2); - chart->faces.push_back(face2); - newFaceAssigned = true; - } - mapIndex = vertexToFaceMap.getNext(mapIndex); - } - } - } - if (!newFaceAssigned) + mesh->indices[i] = hasIndices ? DecodeIndex(decl.indexFormat, decl.indexData, decl.indexOffset, i) : i; + mesh->texcoords.resize(decl.vertexCount); + for (uint32_t i = 0; i < decl.vertexCount; i++) + mesh->texcoords[i] = *((const internal::Vector2 *)&((const uint8_t *)decl.vertexUvData)[decl.vertexStride * i]); + // Validate. + mesh->faceIgnore.resize(decl.indexCount / 3); + mesh->faceIgnore.zeroOutMemory(); + const uint32_t kMaxWarnings = 50; + uint32_t warningCount = 0; + for (uint32_t f = 0; f < indexCount / 3; f++) { + bool ignore = false; + uint32_t tri[3]; + for (uint32_t i = 0; i < 3; i++) + tri[i] = mesh->indices[f * 3 + i]; + // Check for nan UVs. + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = tri[i]; + if (internal::isNan(mesh->texcoords[vertex].x) || internal::isNan(mesh->texcoords[vertex].y)) { + ignore = true; + if (++warningCount <= kMaxWarnings) + XA_PRINT(" NAN texture coordinate in vertex %u\n", vertex); break; + } } - for (uint32_t i = 0; i < chart->faces.size(); i++) { - for (uint32_t j = 0; j < 3; j++) { - const uint32_t vertex = meshInstance->mesh->indices[chart->faces[i] * 3 + j]; - chart->indices.push_back(vertex); - mesh->vertexToChartMap[vertex] = mesh->charts.size(); + // Check for zero area faces. + if (!ignore) { + const internal::Vector2 &v1 = mesh->texcoords[tri[0]]; + const internal::Vector2 &v2 = mesh->texcoords[tri[1]]; + const internal::Vector2 &v3 = mesh->texcoords[tri[2]]; + const float area = fabsf(((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f); + if (area <= internal::kAreaEpsilon) { + ignore = true; + if (++warningCount <= kMaxWarnings) + XA_PRINT(" Zero area face: %d, indices (%d %d %d), area is %f\n", f, tri[0], tri[1], tri[2], area); } } - mesh->charts.push_back(chart); + if (ignore) + mesh->faceIgnore.set(f); } - ctx->uvMeshes.push_back(mesh); - } else { - XA_PRINT(" instance of a previous UV mesh\n"); + if (warningCount > kMaxWarnings) + XA_PRINT(" %u additional warnings truncated\n", warningCount - kMaxWarnings); } - XA_PRINT(" %u charts\n", meshInstance->mesh->charts.size()); - ctx->uvMeshInstances.push_back(meshInstance); + meshInstance->mesh = mesh; + XA_PROFILE_END(addMeshCopyData) return AddMeshError::Success; } -void ComputeCharts(Atlas *atlas, ChartOptions options) -{ +void ComputeCharts(Atlas *atlas, ChartOptions options) { if (!atlas) { XA_PRINT_WARNING("ComputeCharts: atlas is null.\n"); return; } Context *ctx = (Context *)atlas; - if (!ctx->uvMeshInstances.isEmpty()) { - XA_PRINT_WARNING("ComputeCharts: This function should not be called with UV meshes.\n"); - return; - } AddMeshJoin(atlas); - if (ctx->meshes.isEmpty()) { - XA_PRINT_WARNING("ComputeCharts: No meshes. Call AddMesh first.\n"); - return; - } - XA_PRINT("Computing charts\n"); - XA_PROFILE_START(computeChartsReal) - if (!ctx->paramAtlas.computeCharts(ctx->taskScheduler, options, ctx->progressFunc, ctx->progressUserData)) { - XA_PRINT(" Cancelled by user\n"); - return; - } - XA_PROFILE_END(computeChartsReal) - // Count charts. - uint32_t chartCount = 0; - const uint32_t meshCount = ctx->meshes.size(); - for (uint32_t i = 0; i < meshCount; i++) { - for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { - const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j); - chartCount += chartGroup->segmentChartCount(); - } - } - XA_PRINT(" %u charts\n", chartCount); -#if XA_PROFILE - XA_PRINT(" Chart groups\n"); - uint32_t chartGroupCount = 0; - for (uint32_t i = 0; i < meshCount; i++) { - XA_PRINT(" Mesh %u: %u chart groups\n", i, ctx->paramAtlas.chartGroupCount(i)); - chartGroupCount += ctx->paramAtlas.chartGroupCount(i); - } - XA_PRINT(" %u total\n", chartGroupCount); -#endif - XA_PROFILE_PRINT_AND_RESET(" Total (real): ", computeChartsReal) - XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", computeChartsThread) - XA_PROFILE_PRINT_AND_RESET(" Create face groups: ", createFaceGroups) - XA_PROFILE_PRINT_AND_RESET(" Extract invalid mesh geometry: ", extractInvalidMeshGeometry) - XA_PROFILE_PRINT_AND_RESET(" Chart group compute charts (real): ", chartGroupComputeChartsReal) - XA_PROFILE_PRINT_AND_RESET(" Chart group compute charts (thread): ", chartGroupComputeChartsThread) - XA_PROFILE_PRINT_AND_RESET(" Create chart group mesh: ", createChartGroupMesh) - XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", createChartGroupMeshColocals) - XA_PROFILE_PRINT_AND_RESET(" Create boundaries: ", createChartGroupMeshBoundaries) - XA_PROFILE_PRINT_AND_RESET(" Build atlas: ", buildAtlas) - XA_PROFILE_PRINT_AND_RESET(" Init: ", buildAtlasInit) - XA_PROFILE_PRINT_AND_RESET(" Planar charts: ", planarCharts) - XA_PROFILE_PRINT_AND_RESET(" Clustered charts: ", clusteredCharts) - XA_PROFILE_PRINT_AND_RESET(" Place seeds: ", clusteredChartsPlaceSeeds) - XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", clusteredChartsPlaceSeedsBoundaryIntersection) - XA_PROFILE_PRINT_AND_RESET(" Relocate seeds: ", clusteredChartsRelocateSeeds) - XA_PROFILE_PRINT_AND_RESET(" Reset: ", clusteredChartsReset) - XA_PROFILE_PRINT_AND_RESET(" Grow: ", clusteredChartsGrow) - XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", clusteredChartsGrowBoundaryIntersection) - XA_PROFILE_PRINT_AND_RESET(" Merge: ", clusteredChartsMerge) - XA_PROFILE_PRINT_AND_RESET(" Fill holes: ", clusteredChartsFillHoles) - XA_PROFILE_PRINT_AND_RESET(" Copy chart faces: ", copyChartFaces) -#if XA_PROFILE_ALLOC - XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc) -#endif - XA_PRINT_MEM_USAGE -} - -void ParameterizeCharts(Atlas *atlas, ParameterizeOptions options) -{ - if (!atlas) { - XA_PRINT_WARNING("ParameterizeCharts: atlas is null.\n"); - return; - } - Context *ctx = (Context *)atlas; - if (!ctx->uvMeshInstances.isEmpty()) { - XA_PRINT_WARNING("ParameterizeCharts: This function should not be called with UV meshes.\n"); - return; - } - if (!ctx->paramAtlas.chartsComputed()) { - XA_PRINT_WARNING("ParameterizeCharts: ComputeCharts must be called first.\n"); + if (ctx->meshes.isEmpty() && ctx->uvMeshInstances.isEmpty()) { + XA_PRINT_WARNING("ComputeCharts: No meshes. Call AddMesh or AddUvMesh first.\n"); return; } - atlas->atlasCount = 0; - atlas->height = 0; - atlas->texelsPerUnit = 0; - atlas->width = 0; - if (atlas->utilization) { + // Reset atlas state. This function may be called multiple times, or again after PackCharts. + if (atlas->utilization) XA_FREE(atlas->utilization); - atlas->utilization = nullptr; - } - if (atlas->image) { + if (atlas->image) XA_FREE(atlas->image); - atlas->image = nullptr; - } DestroyOutputMeshes(ctx); - XA_PRINT("Parameterizing charts\n"); - XA_PROFILE_START(parameterizeChartsReal) - if (!ctx->paramAtlas.parameterizeCharts(ctx->taskScheduler, options, ctx->progressFunc, ctx->progressUserData)) { - XA_PRINT(" Cancelled by user\n"); + memset(&ctx->atlas, 0, sizeof(Atlas)); + XA_PRINT("Computing charts\n"); + if (!ctx->meshes.isEmpty()) { + if (!ctx->paramAtlas.computeCharts(ctx->taskScheduler, options, ctx->progressFunc, ctx->progressUserData)) { + XA_PRINT(" Cancelled by user\n"); return; - } - XA_PROFILE_END(parameterizeChartsReal) - const uint32_t meshCount = ctx->meshes.size(); - uint32_t chartCount = 0, chartsWithHolesCount = 0, holesCount = 0, chartsWithTJunctionsCount = 0, tJunctionsCount = 0, orthoChartsCount = 0, planarChartsCount = 0, lscmChartsCount = 0, piecewiseChartsCount = 0, chartsAddedCount = 0, chartsDeletedCount = 0; - for (uint32_t i = 0; i < meshCount; i++) { - for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { - const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j); - for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { - const internal::param::Chart *chart = chartGroup->chartAt(k); -#if XA_PRINT_CHART_WARNINGS - if (chart->warningFlags() & internal::param::ChartWarningFlags::CloseHolesFailed) - XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): failed to close holes\n", chartCount, i, j, k); - if (chart->warningFlags() & internal::param::ChartWarningFlags::FixTJunctionsDuplicatedEdge) - XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): fixing t-junctions created non-manifold geometry\n", chartCount, i, j, k); - if (chart->warningFlags() & internal::param::ChartWarningFlags::FixTJunctionsFailed) - XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): fixing t-junctions failed\n", chartCount, i, j, k); - if (chart->warningFlags() & internal::param::ChartWarningFlags::TriangulateDuplicatedEdge) - XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): triangulation created non-manifold geometry\n", chartCount, i, j, k); -#endif - holesCount += chart->closedHolesCount(); - if (chart->closedHolesCount() > 0) - chartsWithHolesCount++; - tJunctionsCount += chart->fixedTJunctionsCount(); - if (chart->fixedTJunctionsCount() > 0) - chartsWithTJunctionsCount++; - if (chart->type() == ChartType::Planar) - planarChartsCount++; - else if (chart->type() == ChartType::Ortho) - orthoChartsCount++; - else if (chart->type() == ChartType::LSCM) - lscmChartsCount++; - else if (chart->type() == ChartType::Piecewise) - piecewiseChartsCount++; - } - chartCount += chartGroup->chartCount(); - chartsAddedCount += chartGroup->paramAddedChartsCount(); - chartsDeletedCount += chartGroup->paramDeletedChartsCount(); - } - } - if (holesCount > 0) - XA_PRINT(" %u holes closed in %u charts\n", holesCount, chartsWithHolesCount); - if (tJunctionsCount > 0) - XA_PRINT(" %u t-junctions fixed in %u charts\n", tJunctionsCount, chartsWithTJunctionsCount); - XA_PRINT(" %u planar charts, %u ortho charts, %u LSCM charts, %u piecewise charts\n", planarChartsCount, orthoChartsCount, lscmChartsCount, piecewiseChartsCount); - if (chartsDeletedCount > 0) { - XA_PRINT(" %u charts with invalid parameterizations replaced with %u new charts\n", chartsDeletedCount, chartsAddedCount); + } + uint32_t chartsWithTJunctionsCount = 0, tJunctionCount = 0, orthoChartsCount = 0, planarChartsCount = 0, lscmChartsCount = 0, piecewiseChartsCount = 0, originalUvChartsCount = 0; + uint32_t chartCount = 0; + const uint32_t meshCount = ctx->meshes.size(); + for (uint32_t i = 0; i < meshCount; i++) { + for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { + const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j); + for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { + const internal::param::Chart *chart = chartGroup->chartAt(k); + tJunctionCount += chart->tjunctionCount(); + if (chart->tjunctionCount() > 0) + chartsWithTJunctionsCount++; + if (chart->type() == ChartType::Planar) + planarChartsCount++; + else if (chart->type() == ChartType::Ortho) + orthoChartsCount++; + else if (chart->type() == ChartType::LSCM) + lscmChartsCount++; + else if (chart->type() == ChartType::Piecewise) + piecewiseChartsCount++; + if (chart->generatorType() == internal::segment::ChartGeneratorType::OriginalUv) + originalUvChartsCount++; + } + chartCount += chartGroup->chartCount(); + } + } + if (tJunctionCount > 0) + XA_PRINT(" %u t-junctions found in %u charts\n", tJunctionCount, chartsWithTJunctionsCount); XA_PRINT(" %u charts\n", chartCount); - } - uint32_t chartIndex = 0, invalidParamCount = 0; - for (uint32_t i = 0; i < meshCount; i++) { - for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { - const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j); - for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { - internal::param::Chart *chart = chartGroup->chartAt(k); - const internal::param::Quality &quality = chart->quality(); + XA_PRINT(" %u planar, %u ortho, %u LSCM, %u piecewise\n", planarChartsCount, orthoChartsCount, lscmChartsCount, piecewiseChartsCount); + if (originalUvChartsCount > 0) + XA_PRINT(" %u with original UVs\n", originalUvChartsCount); + uint32_t chartIndex = 0, invalidParamCount = 0; + for (uint32_t i = 0; i < meshCount; i++) { + for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { + const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j); + for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { + internal::param::Chart *chart = chartGroup->chartAt(k); + const internal::param::Quality &quality = chart->quality(); #if XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION - { - char filename[256]; - XA_SPRINTF(filename, sizeof(filename), "debug_chart_%03u_after_parameterization.obj", chartIndex); - chart->unifiedMesh()->writeObjFile(filename); - } -#endif - const char *type = "LSCM"; - if (chart->type() == ChartType::Planar) - type = "planar"; - else if (chart->type() == ChartType::Ortho) - type = "ortho"; - else if (chart->type() == ChartType::Piecewise) - type = "piecewise"; - if (chart->isInvalid()) { - if (quality.boundaryIntersection) { - XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, self-intersecting boundary.\n", chartIndex, i, j, k, type); - } - if (quality.flippedTriangleCount > 0) { - XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, %u / %u flipped triangles.\n", chartIndex, i, j, k, type, quality.flippedTriangleCount, quality.totalTriangleCount); + { + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_chart_%03u_after_parameterization.obj", chartIndex); + chart->unifiedMesh()->writeObjFile(filename); } - invalidParamCount++; +#endif + const char *type = "LSCM"; + if (chart->type() == ChartType::Planar) + type = "planar"; + else if (chart->type() == ChartType::Ortho) + type = "ortho"; + else if (chart->type() == ChartType::Piecewise) + type = "piecewise"; + if (chart->isInvalid()) { + if (quality.boundaryIntersection) { + XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, self-intersecting boundary.\n", chartIndex, i, j, k, type); + } + if (quality.flippedTriangleCount > 0) { + XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, %u / %u flipped triangles.\n", chartIndex, i, j, k, type, quality.flippedTriangleCount, quality.totalTriangleCount); + } + if (quality.zeroAreaTriangleCount > 0) { + XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, %u / %u zero area triangles.\n", chartIndex, i, j, k, type, quality.zeroAreaTriangleCount, quality.totalTriangleCount); + } + invalidParamCount++; #if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION - char filename[256]; - XA_SPRINTF(filename, sizeof(filename), "debug_chart_%03u_invalid_parameterization.obj", chartIndex); - const internal::Mesh *mesh = chart->unifiedMesh(); - FILE *file; - XA_FOPEN(file, filename, "w"); - if (file) { - mesh->writeObjVertices(file); - fprintf(file, "s off\n"); - fprintf(file, "o object\n"); - for (uint32_t f = 0; f < mesh->faceCount(); f++) - mesh->writeObjFace(file, f); - if (!chart->paramFlippedFaces().isEmpty()) { - fprintf(file, "o flipped_faces\n"); - for (uint32_t f = 0; f < chart->paramFlippedFaces().size(); f++) - mesh->writeObjFace(file, chart->paramFlippedFaces()[f]); + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_chart_%03u_invalid_parameterization.obj", chartIndex); + const internal::Mesh *mesh = chart->unifiedMesh(); + FILE *file; + XA_FOPEN(file, filename, "w"); + if (file) { + mesh->writeObjVertices(file); + fprintf(file, "s off\n"); + fprintf(file, "o object\n"); + for (uint32_t f = 0; f < mesh->faceCount(); f++) + mesh->writeObjFace(file, f); + if (!chart->paramFlippedFaces().isEmpty()) { + fprintf(file, "o flipped_faces\n"); + for (uint32_t f = 0; f < chart->paramFlippedFaces().size(); f++) + mesh->writeObjFace(file, chart->paramFlippedFaces()[f]); + } + mesh->writeObjBoundaryEges(file); + fclose(file); } - mesh->writeObjBoundaryEges(file); - mesh->writeObjLinkedBoundaries(file); - fclose(file); - } #endif + } + chartIndex++; } - chartIndex++; } } + if (invalidParamCount > 0) + XA_PRINT_WARNING(" %u charts with invalid parameterizations\n", invalidParamCount); +#if XA_PROFILE + XA_PRINT(" Chart groups\n"); + uint32_t chartGroupCount = 0; + for (uint32_t i = 0; i < meshCount; i++) { +#if 0 + XA_PRINT(" Mesh %u: %u chart groups\n", i, ctx->paramAtlas.chartGroupCount(i)); +#endif + chartGroupCount += ctx->paramAtlas.chartGroupCount(i); + } + XA_PRINT(" %u total\n", chartGroupCount); +#endif + XA_PROFILE_PRINT_AND_RESET(" Compute charts total (real): ", computeChartsReal) + XA_PROFILE_PRINT_AND_RESET(" Compute charts total (thread): ", computeChartsThread) + XA_PROFILE_PRINT_AND_RESET(" Create face groups: ", createFaceGroups) + XA_PROFILE_PRINT_AND_RESET(" Extract invalid mesh geometry: ", extractInvalidMeshGeometry) + XA_PROFILE_PRINT_AND_RESET(" Chart group compute charts (real): ", chartGroupComputeChartsReal) + XA_PROFILE_PRINT_AND_RESET(" Chart group compute charts (thread): ", chartGroupComputeChartsThread) + XA_PROFILE_PRINT_AND_RESET(" Create chart group mesh: ", createChartGroupMesh) + XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", createChartGroupMeshColocals) + XA_PROFILE_PRINT_AND_RESET(" Create boundaries: ", createChartGroupMeshBoundaries) + XA_PROFILE_PRINT_AND_RESET(" Build atlas: ", buildAtlas) + XA_PROFILE_PRINT_AND_RESET(" Init: ", buildAtlasInit) + XA_PROFILE_PRINT_AND_RESET(" Planar charts: ", planarCharts) + if (options.useInputMeshUvs) { + XA_PROFILE_PRINT_AND_RESET(" Original UV charts: ", originalUvCharts) + } + XA_PROFILE_PRINT_AND_RESET(" Clustered charts: ", clusteredCharts) + XA_PROFILE_PRINT_AND_RESET(" Place seeds: ", clusteredChartsPlaceSeeds) + XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", clusteredChartsPlaceSeedsBoundaryIntersection) + XA_PROFILE_PRINT_AND_RESET(" Relocate seeds: ", clusteredChartsRelocateSeeds) + XA_PROFILE_PRINT_AND_RESET(" Reset: ", clusteredChartsReset) + XA_PROFILE_PRINT_AND_RESET(" Grow: ", clusteredChartsGrow) + XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", clusteredChartsGrowBoundaryIntersection) + XA_PROFILE_PRINT_AND_RESET(" Merge: ", clusteredChartsMerge) + XA_PROFILE_PRINT_AND_RESET(" Fill holes: ", clusteredChartsFillHoles) + XA_PROFILE_PRINT_AND_RESET(" Copy chart faces: ", copyChartFaces) + XA_PROFILE_PRINT_AND_RESET(" Create chart mesh and parameterize (real): ", createChartMeshAndParameterizeReal) + XA_PROFILE_PRINT_AND_RESET(" Create chart mesh and parameterize (thread): ", createChartMeshAndParameterizeThread) + XA_PROFILE_PRINT_AND_RESET(" Create chart mesh: ", createChartMesh) + XA_PROFILE_PRINT_AND_RESET(" Parameterize charts: ", parameterizeCharts) + XA_PROFILE_PRINT_AND_RESET(" Orthogonal: ", parameterizeChartsOrthogonal) + XA_PROFILE_PRINT_AND_RESET(" LSCM: ", parameterizeChartsLSCM) + XA_PROFILE_PRINT_AND_RESET(" Recompute: ", parameterizeChartsRecompute) + XA_PROFILE_PRINT_AND_RESET(" Piecewise: ", parameterizeChartsPiecewise) + XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", parameterizeChartsPiecewiseBoundaryIntersection) + XA_PROFILE_PRINT_AND_RESET(" Evaluate quality: ", parameterizeChartsEvaluateQuality) +#if XA_PROFILE_ALLOC + XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc) +#endif + XA_PRINT_MEM_USAGE + } else { + XA_PROFILE_START(computeChartsReal) + if (!internal::segment::computeUvMeshCharts(ctx->taskScheduler, ctx->uvMeshes, ctx->progressFunc, ctx->progressUserData)) { + XA_PRINT(" Cancelled by user\n"); + return; + } + XA_PROFILE_END(computeChartsReal) + ctx->uvMeshChartsComputed = true; + // Count charts. + uint32_t chartCount = 0; + const uint32_t meshCount = ctx->uvMeshes.size(); + for (uint32_t i = 0; i < meshCount; i++) + chartCount += ctx->uvMeshes[i]->charts.size(); + XA_PRINT(" %u charts\n", chartCount); + XA_PROFILE_PRINT_AND_RESET(" Total (real): ", computeChartsReal) + XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", computeChartsThread) } - if (invalidParamCount > 0) - XA_PRINT_WARNING(" %u charts with invalid parameterizations\n", invalidParamCount); - XA_PROFILE_PRINT_AND_RESET(" Total (real): ", parameterizeChartsReal) - XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", parameterizeChartsThread) - XA_PROFILE_PRINT_AND_RESET(" Create chart mesh: ", createChartMesh) - XA_PROFILE_PRINT_AND_RESET(" Fix t-junctions: ", fixChartMeshTJunctions) - XA_PROFILE_PRINT_AND_RESET(" Close holes: ", closeChartMeshHoles) - XA_PROFILE_PRINT_AND_RESET(" Orthogonal: ", parameterizeChartsOrthogonal) - XA_PROFILE_PRINT_AND_RESET(" LSCM: ", parameterizeChartsLSCM) - XA_PROFILE_PRINT_AND_RESET(" Recompute: ", parameterizeChartsRecompute) - XA_PROFILE_PRINT_AND_RESET(" Piecewise: ", parameterizeChartsPiecewise) - XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", parameterizeChartsPiecewiseBoundaryIntersection) - XA_PROFILE_PRINT_AND_RESET(" Evaluate quality: ", parameterizeChartsEvaluateQuality) #if XA_PROFILE_ALLOC XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc) #endif XA_PRINT_MEM_USAGE } -void PackCharts(Atlas *atlas, PackOptions packOptions) -{ +void PackCharts(Atlas *atlas, PackOptions packOptions) { // Validate arguments and context state. if (!atlas) { XA_PRINT_WARNING("PackCharts: atlas is null.\n"); @@ -9867,10 +9152,9 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) XA_PRINT_WARNING("PackCharts: ComputeCharts must be called first.\n"); return; } - if (!ctx->paramAtlas.chartsParameterized()) { - XA_PRINT_WARNING("PackCharts: ParameterizeCharts must be called first.\n"); - return; - } + } else if (!ctx->uvMeshChartsComputed) { + XA_PRINT_WARNING("PackCharts: ComputeCharts must be called first.\n"); + return; } if (packOptions.texelsPerUnit < 0.0f) { XA_PRINT_WARNING("PackCharts: PackOptions::texelsPerUnit is negative.\n"); @@ -9893,8 +9177,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) if (!ctx->uvMeshInstances.isEmpty()) { for (uint32_t i = 0; i < ctx->uvMeshInstances.size(); i++) packAtlas.addUvMeshCharts(ctx->uvMeshInstances[i]); - } - else + } else packAtlas.addCharts(ctx->taskScheduler, &ctx->paramAtlas); XA_PROFILE_END(packChartsAddCharts) XA_PROFILE_START(packCharts) @@ -9946,16 +9229,35 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) uint32_t chartIndex = 0; for (uint32_t i = 0; i < atlas->meshCount; i++) { Mesh &outputMesh = atlas->meshes[i]; + MeshPolygonMapping *meshPolygonMapping = ctx->meshPolygonMappings[i]; + // One polygon can have many triangles. Don't want to process the same polygon more than once when counting indices, building chart faces etc. + internal::BitArray polygonTouched; + if (meshPolygonMapping) { + polygonTouched.resize(meshPolygonMapping->faceVertexCount.size()); + polygonTouched.zeroOutMemory(); + } // Count and alloc arrays. - const internal::param::InvalidMeshGeometry &invalid = ctx->paramAtlas.invalidMeshGeometry(i); + const internal::InvalidMeshGeometry &invalid = ctx->paramAtlas.invalidMeshGeometry(i); outputMesh.vertexCount += invalid.vertices().length; outputMesh.indexCount += invalid.faces().length * 3; for (uint32_t cg = 0; cg < ctx->paramAtlas.chartGroupCount(i); cg++) { const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, cg); for (uint32_t c = 0; c < chartGroup->chartCount(); c++) { const internal::param::Chart *chart = chartGroup->chartAt(c); - outputMesh.vertexCount += chart->mesh()->vertexCount(); - outputMesh.indexCount += chart->mesh()->faceCount() * 3; + outputMesh.vertexCount += chart->originalVertexCount(); + const uint32_t faceCount = chart->unifiedMesh()->faceCount(); + if (meshPolygonMapping) { + // Map triangles back to polygons and count the polygon vertices. + for (uint32_t f = 0; f < faceCount; f++) { + const uint32_t polygon = meshPolygonMapping->triangleToPolygonMap[chart->mapFaceToSourceFace(f)]; + if (!polygonTouched.get(polygon)) { + polygonTouched.set(polygon); + outputMesh.indexCount += meshPolygonMapping->faceVertexCount[polygon]; + } + } + } else { + outputMesh.indexCount += faceCount * 3; + } outputMesh.chartCount++; } } @@ -9966,7 +9268,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) // Copy mesh data. uint32_t firstVertex = 0; { - const internal::param::InvalidMeshGeometry &mesh = ctx->paramAtlas.invalidMeshGeometry(i); + const internal::InvalidMeshGeometry &mesh = ctx->paramAtlas.invalidMeshGeometry(i); internal::ConstArrayView<uint32_t> faces = mesh.faces(); internal::ConstArrayView<uint32_t> indices = mesh.indices(); internal::ConstArrayView<uint32_t> vertices = mesh.vertices(); @@ -9991,23 +9293,50 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, cg); for (uint32_t c = 0; c < chartGroup->chartCount(); c++) { const internal::param::Chart *chart = chartGroup->chartAt(c); - const internal::Mesh *mesh = chart->mesh(); + const internal::Mesh *unifiedMesh = chart->unifiedMesh(); + const uint32_t faceCount = unifiedMesh->faceCount(); +#if XA_CHECK_PARAM_WINDING + uint32_t flippedCount = 0; + for (uint32_t f = 0; f < faceCount; f++) { + const float area = mesh->computeFaceParametricArea(f); + if (area < 0.0f) + flippedCount++; + } + const char *type = "LSCM"; + if (chart->type() == ChartType::Planar) + type = "planar"; + else if (chart->type() == ChartType::Ortho) + type = "ortho"; + else if (chart->type() == ChartType::Piecewise) + type = "piecewise"; + if (flippedCount > 0) { + if (flippedCount == faceCount) { + XA_PRINT_WARNING("chart %u (%s): all face flipped\n", chartIndex, type); + } else { + XA_PRINT_WARNING("chart %u (%s): %u / %u faces flipped\n", chartIndex, type, flippedCount, faceCount); + } + } +#endif // Vertices. - for (uint32_t v = 0; v < mesh->vertexCount(); v++) { + for (uint32_t v = 0; v < chart->originalVertexCount(); v++) { Vertex &vertex = outputMesh.vertexArray[firstVertex + v]; vertex.atlasIndex = packAtlas.getChart(chartIndex)->atlasIndex; XA_DEBUG_ASSERT(vertex.atlasIndex >= 0); vertex.chartIndex = (int32_t)chartIndex; - const internal::Vector2 &uv = mesh->texcoord(v); + const internal::Vector2 &uv = unifiedMesh->texcoord(chart->originalVertexToUnifiedVertex(v)); vertex.uv[0] = internal::max(0.0f, uv.x); vertex.uv[1] = internal::max(0.0f, uv.y); vertex.xref = chart->mapChartVertexToSourceVertex(v); } // Indices. - for (uint32_t f = 0; f < mesh->faceCount(); f++) { + for (uint32_t f = 0; f < faceCount; f++) { const uint32_t indexOffset = chart->mapFaceToSourceFace(f) * 3; - for (uint32_t j = 0; j < 3; j++) - outputMesh.indexArray[indexOffset + j] = firstVertex + mesh->vertexAt(f * 3 + j); + for (uint32_t j = 0; j < 3; j++) { + uint32_t outIndex = indexOffset + j; + if (meshPolygonMapping) + outIndex = meshPolygonMapping->triangleToPolygonIndicesMap[outIndex]; + outputMesh.indexArray[outIndex] = firstVertex + chart->originalVertices()[f * 3 + j]; + } } // Charts. Chart *outputChart = &outputMesh.chartArray[meshChartIndex]; @@ -10015,14 +9344,38 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) XA_DEBUG_ASSERT(atlasIndex >= 0); outputChart->atlasIndex = (uint32_t)atlasIndex; outputChart->type = chart->isInvalid() ? ChartType::Invalid : chart->type(); - outputChart->faceCount = mesh->faceCount(); - outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount); - for (uint32_t f = 0; f < outputChart->faceCount; f++) - outputChart->faceArray[f] = chart->mapFaceToSourceFace(f); + if (meshPolygonMapping) { + // Count polygons. + polygonTouched.zeroOutMemory(); + outputChart->faceCount = 0; + for (uint32_t f = 0; f < faceCount; f++) { + const uint32_t polygon = meshPolygonMapping->triangleToPolygonMap[chart->mapFaceToSourceFace(f)]; + if (!polygonTouched.get(polygon)) { + polygonTouched.set(polygon); + outputChart->faceCount++; + } + } + // Write polygons. + outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount); + polygonTouched.zeroOutMemory(); + uint32_t of = 0; + for (uint32_t f = 0; f < faceCount; f++) { + const uint32_t polygon = meshPolygonMapping->triangleToPolygonMap[chart->mapFaceToSourceFace(f)]; + if (!polygonTouched.get(polygon)) { + polygonTouched.set(polygon); + outputChart->faceArray[of++] = polygon; + } + } + } else { + outputChart->faceCount = faceCount; + outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount); + for (uint32_t f = 0; f < outputChart->faceCount; f++) + outputChart->faceArray[f] = chart->mapFaceToSourceFace(f); + } outputChart->material = 0; meshChartIndex++; chartIndex++; - firstVertex += mesh->vertexCount(); + firstVertex += chart->originalVertexCount(); } } XA_DEBUG_ASSERT(outputMesh.vertexCount == firstVertex); @@ -10102,28 +9455,21 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) XA_PRINT_MEM_USAGE } -void Generate(Atlas *atlas, ChartOptions chartOptions, ParameterizeOptions parameterizeOptions, PackOptions packOptions) -{ +void Generate(Atlas *atlas, ChartOptions chartOptions, PackOptions packOptions) { if (!atlas) { XA_PRINT_WARNING("Generate: atlas is null.\n"); return; } Context *ctx = (Context *)atlas; - if (!ctx->uvMeshInstances.isEmpty()) { - XA_PRINT_WARNING("Generate: This function should not be called with UV meshes.\n"); - return; - } - if (ctx->meshes.isEmpty()) { - XA_PRINT_WARNING("Generate: No meshes. Call AddMesh first.\n"); + if (ctx->meshes.isEmpty() && ctx->uvMeshInstances.isEmpty()) { + XA_PRINT_WARNING("Generate: No meshes. Call AddMesh or AddUvMesh first.\n"); return; } ComputeCharts(atlas, chartOptions); - ParameterizeCharts(atlas, parameterizeOptions); PackCharts(atlas, packOptions); } -void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc, void *progressUserData) -{ +void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc, void *progressUserData) { if (!atlas) { XA_PRINT_WARNING("SetProgressCallback: atlas is null.\n"); return; @@ -10133,37 +9479,33 @@ void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc, void *progress ctx->progressUserData = progressUserData; } -void SetAlloc(ReallocFunc reallocFunc, FreeFunc freeFunc) -{ +void SetAlloc(ReallocFunc reallocFunc, FreeFunc freeFunc) { internal::s_realloc = reallocFunc; internal::s_free = freeFunc; } -void SetPrint(PrintFunc print, bool verbose) -{ +void SetPrint(PrintFunc print, bool verbose) { internal::s_print = print; internal::s_printVerbose = verbose; } -const char *StringForEnum(AddMeshError::Enum error) -{ +const char *StringForEnum(AddMeshError error) { if (error == AddMeshError::Error) return "Unspecified error"; if (error == AddMeshError::IndexOutOfRange) return "Index out of range"; + if (error == AddMeshError::InvalidFaceVertexCount) + return "Invalid face vertex count"; if (error == AddMeshError::InvalidIndexCount) return "Invalid index count"; return "Success"; } -const char *StringForEnum(ProgressCategory::Enum category) -{ +const char *StringForEnum(ProgressCategory category) { if (category == ProgressCategory::AddMesh) return "Adding mesh(es)"; if (category == ProgressCategory::ComputeCharts) return "Computing charts"; - if (category == ProgressCategory::ParameterizeCharts) - return "Parameterizing charts"; if (category == ProgressCategory::PackCharts) return "Packing charts"; if (category == ProgressCategory::BuildOutputMeshes) @@ -10172,3 +9514,96 @@ const char *StringForEnum(ProgressCategory::Enum category) } } // namespace xatlas + +#if XATLAS_C_API +static_assert(sizeof(xatlas::Chart) == sizeof(xatlasChart), "xatlasChart size mismatch"); +static_assert(sizeof(xatlas::Vertex) == sizeof(xatlasVertex), "xatlasVertex size mismatch"); +static_assert(sizeof(xatlas::Mesh) == sizeof(xatlasMesh), "xatlasMesh size mismatch"); +static_assert(sizeof(xatlas::Atlas) == sizeof(xatlasAtlas), "xatlasAtlas size mismatch"); +static_assert(sizeof(xatlas::MeshDecl) == sizeof(xatlasMeshDecl), "xatlasMeshDecl size mismatch"); +static_assert(sizeof(xatlas::UvMeshDecl) == sizeof(xatlasUvMeshDecl), "xatlasUvMeshDecl size mismatch"); +static_assert(sizeof(xatlas::ChartOptions) == sizeof(xatlasChartOptions), "xatlasChartOptions size mismatch"); +static_assert(sizeof(xatlas::PackOptions) == sizeof(xatlasPackOptions), "xatlasPackOptions size mismatch"); + +#ifdef __cplusplus +extern "C" { +#endif + +xatlasAtlas *xatlasCreate() { + return (xatlasAtlas *)xatlas::Create(); +} + +void xatlasDestroy(xatlasAtlas *atlas) { + xatlas::Destroy((xatlas::Atlas *)atlas); +} + +xatlasAddMeshError xatlasAddMesh(xatlasAtlas *atlas, const xatlasMeshDecl *meshDecl, uint32_t meshCountHint) { + return (xatlasAddMeshError)xatlas::AddMesh((xatlas::Atlas *)atlas, *(const xatlas::MeshDecl *)meshDecl, meshCountHint); +} + +void xatlasAddMeshJoin(xatlasAtlas *atlas) { + xatlas::AddMeshJoin((xatlas::Atlas *)atlas); +} + +xatlasAddMeshError xatlasAddUvMesh(xatlasAtlas *atlas, const xatlasUvMeshDecl *decl) { + return (xatlasAddMeshError)xatlas::AddUvMesh((xatlas::Atlas *)atlas, *(const xatlas::UvMeshDecl *)decl); +} + +void xatlasComputeCharts(xatlasAtlas *atlas, const xatlasChartOptions *chartOptions) { + xatlas::ComputeCharts((xatlas::Atlas *)atlas, chartOptions ? *(xatlas::ChartOptions *)chartOptions : xatlas::ChartOptions()); +} + +void xatlasPackCharts(xatlasAtlas *atlas, const xatlasPackOptions *packOptions) { + xatlas::PackCharts((xatlas::Atlas *)atlas, packOptions ? *(xatlas::PackOptions *)packOptions : xatlas::PackOptions()); +} + +void xatlasGenerate(xatlasAtlas *atlas, const xatlasChartOptions *chartOptions, const xatlasPackOptions *packOptions) { + xatlas::Generate((xatlas::Atlas *)atlas, chartOptions ? *(xatlas::ChartOptions *)chartOptions : xatlas::ChartOptions(), packOptions ? *(xatlas::PackOptions *)packOptions : xatlas::PackOptions()); +} + +void xatlasSetProgressCallback(xatlasAtlas *atlas, xatlasProgressFunc progressFunc, void *progressUserData) { + xatlas::ProgressFunc pf; + *(void **)&pf = (void *)progressFunc; + xatlas::SetProgressCallback((xatlas::Atlas *)atlas, pf, progressUserData); +} + +void xatlasSetAlloc(xatlasReallocFunc reallocFunc, xatlasFreeFunc freeFunc) { + xatlas::SetAlloc((xatlas::ReallocFunc)reallocFunc, (xatlas::FreeFunc)freeFunc); +} + +void xatlasSetPrint(xatlasPrintFunc print, bool verbose) { + xatlas::SetPrint((xatlas::PrintFunc)print, verbose); +} + +const char *xatlasAddMeshErrorString(xatlasAddMeshError error) { + return xatlas::StringForEnum((xatlas::AddMeshError)error); +} + +const char *xatlasProgressCategoryString(xatlasProgressCategory category) { + return xatlas::StringForEnum((xatlas::ProgressCategory)category); +} + +void xatlasMeshDeclInit(xatlasMeshDecl *meshDecl) { + xatlas::MeshDecl init; + memcpy(meshDecl, &init, sizeof(init)); +} + +void xatlasUvMeshDeclInit(xatlasUvMeshDecl *uvMeshDecl) { + xatlas::UvMeshDecl init; + memcpy(uvMeshDecl, &init, sizeof(init)); +} + +void xatlasChartOptionsInit(xatlasChartOptions *chartOptions) { + xatlas::ChartOptions init; + memcpy(chartOptions, &init, sizeof(init)); +} + +void xatlasPackOptionsInit(xatlasPackOptions *packOptions) { + xatlas::PackOptions init; + memcpy(packOptions, &init, sizeof(init)); +} + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // XATLAS_C_API diff --git a/thirdparty/xatlas/xatlas.h b/thirdparty/xatlas/xatlas.h index cc47f4837e..fc40d9d49c 100644 --- a/thirdparty/xatlas/xatlas.h +++ b/thirdparty/xatlas/xatlas.h @@ -31,35 +31,30 @@ Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com> #pragma once #ifndef XATLAS_H #define XATLAS_H +#include <stddef.h> #include <stdint.h> namespace xatlas { -struct ChartType -{ - enum Enum - { - Planar, - Ortho, - LSCM, - Piecewise, - Invalid - }; +enum class ChartType { + Planar, + Ortho, + LSCM, + Piecewise, + Invalid }; // A group of connected faces, belonging to a single atlas. -struct Chart -{ +struct Chart { uint32_t *faceArray; uint32_t atlasIndex; // Sub-atlas index. uint32_t faceCount; - ChartType::Enum type; + ChartType type; uint32_t material; }; // Output vertex. -struct Vertex -{ +struct Vertex { int32_t atlasIndex; // Sub-atlas index. -1 if the vertex doesn't exist in any atlas. int32_t chartIndex; // -1 if the vertex doesn't exist in any chart. float uv[2]; // Not normalized - values are in Atlas width and height range. @@ -67,8 +62,7 @@ struct Vertex }; // Output mesh. -struct Mesh -{ +struct Mesh { Chart *chartArray; uint32_t *indexArray; Vertex *vertexArray; @@ -83,16 +77,15 @@ static const uint32_t kImageIsBilinearBit = 0x40000000; static const uint32_t kImageIsPaddingBit = 0x20000000; // Empty on creation. Populated after charts are packed. -struct Atlas -{ +struct Atlas { uint32_t *image; Mesh *meshes; // The output meshes, corresponding to each AddMesh call. + float *utilization; // Normalized atlas texel utilization array. E.g. a value of 0.8 means 20% empty space. atlasCount in length. uint32_t width; // Atlas width in texels. uint32_t height; // Atlas height in texels. uint32_t atlasCount; // Number of sub-atlases. Equal to 0 unless PackOptions resolution is changed from default (0). uint32_t chartCount; // Total number of charts in all meshes. uint32_t meshCount; // Number of output meshes. Equal to the number of times AddMesh was called. - float *utilization; // Normalized atlas texel utilization array. E.g. a value of 0.8 means 20% empty space. atlasCount in length. float texelsPerUnit; // Equal to PackOptions texelsPerUnit if texelsPerUnit > 0, otherwise an estimated value to match PackOptions resolution. }; @@ -101,73 +94,76 @@ Atlas *Create(); void Destroy(Atlas *atlas); -struct IndexFormat -{ - enum Enum - { - UInt16, - UInt32 - }; +enum class IndexFormat { + UInt16, + UInt32 }; // Input mesh declaration. -struct MeshDecl -{ +struct MeshDecl { const void *vertexPositionData = nullptr; const void *vertexNormalData = nullptr; // optional const void *vertexUvData = nullptr; // optional. The input UVs are provided as a hint to the chart generator. const void *indexData = nullptr; // optional - - // Optional. indexCount / 3 (triangle count) in length. + + // Optional. Must be faceCount in length. // Don't atlas faces set to true. Ignored faces still exist in the output meshes, Vertex uv is set to (0, 0) and Vertex atlasIndex to -1. const bool *faceIgnoreData = nullptr; + // Optional. Must be faceCount in length. + // Only faces with the same material will be assigned to the same chart. + const uint32_t *faceMaterialData = nullptr; + + // Optional. Must be faceCount in length. + // Polygon / n-gon support. Faces are assumed to be triangles if this is null. + const uint8_t *faceVertexCount = nullptr; + uint32_t vertexCount = 0; uint32_t vertexPositionStride = 0; uint32_t vertexNormalStride = 0; // optional uint32_t vertexUvStride = 0; // optional uint32_t indexCount = 0; int32_t indexOffset = 0; // optional. Add this offset to all indices. - IndexFormat::Enum indexFormat = IndexFormat::UInt16; + uint32_t faceCount = 0; // Optional if faceVertexCount is null. Otherwise assumed to be indexCount / 3. + IndexFormat indexFormat = IndexFormat::UInt16; // Vertex positions within epsilon distance of each other are considered colocal. float epsilon = 1.192092896e-07F; }; -struct AddMeshError -{ - enum Enum - { - Success, // No error. - Error, // Unspecified error. - IndexOutOfRange, // An index is >= MeshDecl vertexCount. - InvalidIndexCount // Not evenly divisible by 3 - expecting triangles. - }; +enum class AddMeshError { + Success, // No error. + Error, // Unspecified error. + IndexOutOfRange, // An index is >= MeshDecl vertexCount. + InvalidFaceVertexCount, // Must be >= 3. + InvalidIndexCount // Not evenly divisible by 3 - expecting triangles. }; // Add a mesh to the atlas. MeshDecl data is copied, so it can be freed after AddMesh returns. -AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint = 0); +AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint = 0); // Wait for AddMesh async processing to finish. ComputeCharts / Generate call this internally. void AddMeshJoin(Atlas *atlas); -struct UvMeshDecl -{ +struct UvMeshDecl { const void *vertexUvData = nullptr; const void *indexData = nullptr; // optional - const uint32_t *faceMaterialData = nullptr; // Optional. Faces with different materials won't be assigned to the same chart. Must be indexCount / 3 in length. + const uint32_t *faceMaterialData = nullptr; // Optional. Overlapping UVs should be assigned a different material. Must be indexCount / 3 in length. uint32_t vertexCount = 0; uint32_t vertexStride = 0; uint32_t indexCount = 0; int32_t indexOffset = 0; // optional. Add this offset to all indices. - IndexFormat::Enum indexFormat = IndexFormat::UInt16; - bool rotateCharts = true; + IndexFormat indexFormat = IndexFormat::UInt16; }; -AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl); +AddMeshError AddUvMesh(Atlas *atlas, const UvMeshDecl &decl); + +// Custom parameterization function. texcoords initial values are an orthogonal parameterization. +typedef void (*ParameterizeFunc)(const float *positions, float *texcoords, uint32_t vertexCount, const uint32_t *indices, uint32_t indexCount); + +struct ChartOptions { + ParameterizeFunc paramFunc = nullptr; -struct ChartOptions -{ float maxChartArea = 0.0f; // Don't grow charts to be larger than this. 0 means no limit. float maxBoundaryLength = 0.0f; // Don't grow charts to have a longer boundary than this. 0 means no limit. @@ -180,26 +176,31 @@ struct ChartOptions float maxCost = 2.0f; // If total of all metrics * weights > maxCost, don't grow chart. Lower values result in more charts. uint32_t maxIterations = 1; // Number of iterations of the chart growing and seeding phases. Higher values result in better charts. + + bool useInputMeshUvs = false; // Use MeshDecl::vertexUvData for charts. + bool fixWinding = false; // Enforce consistent texture coordinate winding. }; // Call after all AddMesh calls. Can be called multiple times to recompute charts with different options. void ComputeCharts(Atlas *atlas, ChartOptions options = ChartOptions()); -// Custom parameterization function. texcoords initial values are an orthogonal parameterization. -typedef void (*ParameterizeFunc)(const float *positions, float *texcoords, uint32_t vertexCount, const uint32_t *indices, uint32_t indexCount); +struct PackOptions { + // Charts larger than this will be scaled down. 0 means no limit. + uint32_t maxChartSize = 0; -struct ParameterizeOptions -{ - ParameterizeFunc func = nullptr; - bool closeHoles = true; // If the custom parameterization function works with multiple boundaries, this can be set to false to improve performance. - bool fixTJunctions = true; // If meshes don't have T-junctions, this can be set to false to improve performance. -}; + // Number of pixels to pad charts with. + uint32_t padding = 0; + + // Unit to texel scale. e.g. a 1x1 quad with texelsPerUnit of 32 will take up approximately 32x32 texels in the atlas. + // If 0, an estimated value will be calculated to approximately match the given resolution. + // If resolution is also 0, the estimated value will approximately match a 1024x1024 atlas. + float texelsPerUnit = 0.0f; -// Call after ComputeCharts. Can be called multiple times to re-parameterize charts with a different ParameterizeFunc. -void ParameterizeCharts(Atlas *atlas, ParameterizeOptions options = ParameterizeOptions()); + // If 0, generate a single atlas with texelsPerUnit determining the final resolution. + // If not 0, and texelsPerUnit is not 0, generate one or more atlases with that exact resolution. + // If not 0, and texelsPerUnit is 0, texelsPerUnit is estimated to approximately match the resolution. + uint32_t resolution = 0; -struct PackOptions -{ // Leave space around charts for texels that would be sampled by bilinear filtering. bool bilinear = true; @@ -212,44 +213,29 @@ struct PackOptions // Create Atlas::image bool createImage = false; - // Charts larger than this will be scaled down. 0 means no limit. - uint32_t maxChartSize = 0; - - // Number of pixels to pad charts with. - uint32_t padding = 0; + // Rotate charts to the axis of their convex hull. + bool rotateChartsToAxis = true; - // Unit to texel scale. e.g. a 1x1 quad with texelsPerUnit of 32 will take up approximately 32x32 texels in the atlas. - // If 0, an estimated value will be calculated to approximately match the given resolution. - // If resolution is also 0, the estimated value will approximately match a 1024x1024 atlas. - float texelsPerUnit = 0.0f; - - // If 0, generate a single atlas with texelsPerUnit determining the final resolution. - // If not 0, and texelsPerUnit is not 0, generate one or more atlases with that exact resolution. - // If not 0, and texelsPerUnit is 0, texelsPerUnit is estimated to approximately match the resolution. - uint32_t resolution = 0; + // Rotate charts to improve packing. + bool rotateCharts = true; }; -// Call after ParameterizeCharts. Can be called multiple times to re-pack charts with different options. +// Call after ComputeCharts. Can be called multiple times to re-pack charts with different options. void PackCharts(Atlas *atlas, PackOptions packOptions = PackOptions()); -// Equivalent to calling ComputeCharts, ParameterizeCharts and PackCharts in sequence. Can be called multiple times to regenerate with different options. -void Generate(Atlas *atlas, ChartOptions chartOptions = ChartOptions(), ParameterizeOptions parameterizeOptions = ParameterizeOptions(), PackOptions packOptions = PackOptions()); +// Equivalent to calling ComputeCharts and PackCharts in sequence. Can be called multiple times to regenerate with different options. +void Generate(Atlas *atlas, ChartOptions chartOptions = ChartOptions(), PackOptions packOptions = PackOptions()); // Progress tracking. -struct ProgressCategory -{ - enum Enum - { - AddMesh, - ComputeCharts, - ParameterizeCharts, - PackCharts, - BuildOutputMeshes - }; +enum class ProgressCategory { + AddMesh, + ComputeCharts, + PackCharts, + BuildOutputMeshes }; // May be called from any thread. Return false to cancel. -typedef bool (*ProgressFunc)(ProgressCategory::Enum category, int progress, void *userData); +typedef bool (*ProgressFunc)(ProgressCategory category, int progress, void *userData); void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc = nullptr, void *progressUserData = nullptr); @@ -263,8 +249,8 @@ typedef int (*PrintFunc)(const char *, ...); void SetPrint(PrintFunc print, bool verbose); // Helper functions for error messages. -const char *StringForEnum(AddMeshError::Enum error); -const char *StringForEnum(ProgressCategory::Enum category); +const char *StringForEnum(AddMeshError error); +const char *StringForEnum(ProgressCategory category); } // namespace xatlas |