diff options
100 files changed, 3703 insertions, 2081 deletions
diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index 3b189be4ab..0eacffeb88 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -400,6 +400,10 @@ bool _OS::is_window_always_on_top() const { return OS::get_singleton()->is_window_always_on_top(); } +bool _OS::is_window_focused() const { + return OS::get_singleton()->is_window_focused(); +} + void _OS::set_borderless_window(bool p_borderless) { OS::get_singleton()->set_borderless_window(p_borderless); } @@ -1226,6 +1230,7 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("is_window_maximized"), &_OS::is_window_maximized); ClassDB::bind_method(D_METHOD("set_window_always_on_top", "enabled"), &_OS::set_window_always_on_top); ClassDB::bind_method(D_METHOD("is_window_always_on_top"), &_OS::is_window_always_on_top); + ClassDB::bind_method(D_METHOD("is_window_focused"), &_OS::is_window_focused); ClassDB::bind_method(D_METHOD("request_attention"), &_OS::request_attention); ClassDB::bind_method(D_METHOD("get_real_window_size"), &_OS::get_real_window_size); ClassDB::bind_method(D_METHOD("center_window"), &_OS::center_window); diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index 46a5fdb5a4..7c5031cad4 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -198,6 +198,7 @@ public: virtual bool is_window_maximized() const; virtual void set_window_always_on_top(bool p_enabled); virtual bool is_window_always_on_top() const; + virtual bool is_window_focused() const; virtual void request_attention(); virtual void center_window(); virtual void move_window_to_foreground(); diff --git a/core/engine.cpp b/core/engine.cpp index e461bfe44a..1772cc7c48 100644 --- a/core/engine.cpp +++ b/core/engine.cpp @@ -94,11 +94,7 @@ Dictionary Engine::get_version_info() const { Dictionary dict; dict["major"] = VERSION_MAJOR; dict["minor"] = VERSION_MINOR; -#ifdef VERSION_PATCH dict["patch"] = VERSION_PATCH; -#else - dict["patch"] = 0; -#endif dict["hex"] = VERSION_HEX; dict["status"] = VERSION_STATUS; dict["build"] = VERSION_BUILD; diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index bef92b938b..83ce03418a 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -34,8 +34,6 @@ #include <stdio.h> -#define PACK_VERSION 1 - Error PackedData::add_pack(const String &p_path, bool p_replace_files) { for (int i = 0; i < sources.size(); i++) { @@ -140,16 +138,14 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files) if (!f) return false; - //printf("try open %ls!\n", p_path.c_str()); - uint32_t magic = f->get_32(); - if (magic != 0x43504447) { + if (magic != PACK_HEADER_MAGIC) { //maybe at the end.... self contained exe f->seek_end(); f->seek(f->get_position() - 4); magic = f->get_32(); - if (magic != 0x43504447) { + if (magic != PACK_HEADER_MAGIC) { f->close(); memdelete(f); @@ -161,7 +157,7 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files) f->seek(f->get_position() - ds - 8); magic = f->get_32(); - if (magic != 0x43504447) { + if (magic != PACK_HEADER_MAGIC) { f->close(); memdelete(f); @@ -172,9 +168,9 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files) uint32_t version = f->get_32(); uint32_t ver_major = f->get_32(); uint32_t ver_minor = f->get_32(); - f->get_32(); // ver_rev + f->get_32(); // patch number, not used for validation. - if (version != PACK_VERSION) { + if (version != PACK_FORMAT_VERSION) { f->close(); memdelete(f); ERR_FAIL_V_MSG(false, "Pack version unsupported: " + itos(version) + "."); diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 6ced2b2d4d..b6ea9c158f 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -37,6 +37,11 @@ #include "core/os/file_access.h" #include "core/print_string.h" +// Godot's packed file magic header ("GDPC" in ASCII). +#define PACK_HEADER_MAGIC 0x43504447 +// The current packed file format version number. +#define PACK_FORMAT_VERSION 1 + class PackSource; class PackedData { diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index c317125c28..8bc73103e9 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -30,6 +30,7 @@ #include "pck_packer.h" +#include "core/io/file_access_pack.h" // PACK_HEADER_MAGIC, PACK_FORMAT_VERSION #include "core/os/file_access.h" #include "core/version.h" @@ -68,11 +69,11 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment) { alignment = p_alignment; - file->store_32(0x43504447); // MAGIC - file->store_32(1); // # version - file->store_32(VERSION_MAJOR); // # major - file->store_32(VERSION_MINOR); // # minor - file->store_32(0); // # revision + file->store_32(PACK_HEADER_MAGIC); + file->store_32(PACK_FORMAT_VERSION); + file->store_32(VERSION_MAJOR); + file->store_32(VERSION_MINOR); + file->store_32(VERSION_PATCH); for (int i = 0; i < 16; i++) { diff --git a/core/os/os.h b/core/os/os.h index cdc9536653..593ea2b645 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -222,6 +222,7 @@ public: virtual bool is_window_maximized() const { return true; } virtual void set_window_always_on_top(bool p_enabled) {} virtual bool is_window_always_on_top() const { return false; } + virtual bool is_window_focused() const { return true; } virtual void set_console_visible(bool p_enabled) {} virtual bool is_console_visible() const { return false; } virtual void request_attention() {} diff --git a/core/resource.cpp b/core/resource.cpp index 1c9d3f8d49..30e09716aa 100644 --- a/core/resource.cpp +++ b/core/resource.cpp @@ -68,7 +68,10 @@ void Resource::set_path(const String &p_path, bool p_take_over) { if (p_take_over) { ResourceCache::lock->write_lock(); - ResourceCache::resources.get(p_path)->set_name(""); + Resource **res = ResourceCache::resources.getptr(p_path); + if (res) { + (*res)->set_name(""); + } ResourceCache::lock->write_unlock(); } else { ResourceCache::lock->read_lock(); diff --git a/core/version.h b/core/version.h index a790152ca4..42c85c1b13 100644 --- a/core/version.h +++ b/core/version.h @@ -28,6 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef GODOT_VERSION_H +#define GODOT_VERSION_H + #include "core/version_generated.gen.h" // Godot versions are of the form <major>.<minor> for the initial release, @@ -38,18 +41,18 @@ // forward-compatible. // Example: "3.1" #define VERSION_BRANCH "" _MKSTR(VERSION_MAJOR) "." _MKSTR(VERSION_MINOR) -#ifdef VERSION_PATCH +#if VERSION_PATCH // Example: "3.1.4" #define VERSION_NUMBER "" VERSION_BRANCH "." _MKSTR(VERSION_PATCH) +#else // patch is 0, we don't include it in the "pretty" version number. +// Example: "3.1" instead of "3.1.0" +#define VERSION_NUMBER "" VERSION_BRANCH +#endif // VERSION_PATCH + // Version number encoded as hexadecimal int with one byte for each number, // for easy comparison from code. // Example: 3.1.4 will be 0x030104, making comparison easy from script. #define VERSION_HEX 0x10000 * VERSION_MAJOR + 0x100 * VERSION_MINOR + VERSION_PATCH -#else -// Example: "3.1" -#define VERSION_NUMBER "" VERSION_BRANCH -#define VERSION_HEX 0x10000 * VERSION_MAJOR + 0x100 * VERSION_MINOR -#endif // VERSION_PATCH // Describes the full configuration of that Godot version, including the version number, // the status (beta, stable, etc.) and potential module-specific features (e.g. mono). @@ -64,3 +67,5 @@ // Same as above, but prepended with Godot's name and a cosmetic "v" for "version". // Example: "Godot v3.1.4.stable.official.mono" #define VERSION_FULL_NAME "" VERSION_NAME " v" VERSION_FULL_BUILD + +#endif // GODOT_VERSION_H diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml index 1feccec501..00a80a084b 100644 --- a/doc/classes/Environment.xml +++ b/doc/classes/Environment.xml @@ -21,6 +21,7 @@ <argument index="0" name="idx" type="int"> </argument> <description> + Returns [code]true[/code] if the glow level [code]idx[/code] is specified, [code]false[/code] otherwise. </description> </method> <method name="set_glow_level"> @@ -31,132 +32,134 @@ <argument index="1" name="enabled" type="bool"> </argument> <description> + Enables or disables the glow level at index [code]idx[/code]. The more glow levels are enabled, the slower the glow effect will be. </description> </method> </methods> <members> <member name="adjustment_brightness" type="float" setter="set_adjustment_brightness" getter="get_adjustment_brightness" default="1.0"> - Global brightness value of the rendered scene (default value is 1). + The global brightness value of the rendered scene. Effective only if [code]adjustment_enabled[/code] is [code]true[/code]. </member> <member name="adjustment_color_correction" type="Texture" setter="set_adjustment_color_correction" getter="get_adjustment_color_correction"> - Applies the provided [Texture] resource to affect the global color aspect of the rendered scene. + Applies the provided [Texture] resource to affect the global color aspect of the rendered scene. Effective only if [code]adjustment_enabled[/code] is [code]true[/code]. </member> <member name="adjustment_contrast" type="float" setter="set_adjustment_contrast" getter="get_adjustment_contrast" default="1.0"> - Global contrast value of the rendered scene (default value is 1). + The global contrast value of the rendered scene (default value is 1). Effective only if [code]adjustment_enabled[/code] is [code]true[/code]. </member> <member name="adjustment_enabled" type="bool" setter="set_adjustment_enable" getter="is_adjustment_enabled" default="false"> - Enables the [code]adjustment_*[/code] options provided by this resource. If [code]false[/code], adjustments modifications will have no effect on the rendered scene. + If [code]true[/code], enables the [code]adjustment_*[/code] properties provided by this resource. If [code]false[/code], modifications to the [code]adjustment_*[/code] properties will have no effect on the rendered scene. </member> <member name="adjustment_saturation" type="float" setter="set_adjustment_saturation" getter="get_adjustment_saturation" default="1.0"> - Global color saturation value of the rendered scene (default value is 1). + The global color saturation value of the rendered scene (default value is 1). Effective only if [code]adjustment_enabled[/code] is [code]true[/code]. </member> <member name="ambient_light_color" type="Color" setter="set_ambient_light_color" getter="get_ambient_light_color" default="Color( 0, 0, 0, 1 )"> - [Color] of the ambient light. + The ambient light's [Color]. </member> <member name="ambient_light_energy" type="float" setter="set_ambient_light_energy" getter="get_ambient_light_energy" default="1.0"> - Energy of the ambient light. The higher the value, the stronger the light. + The ambient light's energy. The higher the value, the stronger the light. </member> <member name="ambient_light_sky_contribution" type="float" setter="set_ambient_light_sky_contribution" getter="get_ambient_light_sky_contribution" default="1.0"> Defines the amount of light that the sky brings on the scene. A value of 0 means that the sky's light emission has no effect on the scene illumination, thus all ambient illumination is provided by the ambient light. On the contrary, a value of 1 means that all the light that affects the scene is provided by the sky, thus the ambient light parameter has no effect on the scene. + This value is multiplied by [member ambient_light_energy], so it must be set to a value higher than [code]0[/code] for changes to be visible when adjusting [member ambient_light_sky_contribution]. </member> <member name="auto_exposure_enabled" type="bool" setter="set_tonemap_auto_exposure" getter="get_tonemap_auto_exposure" default="false"> - Enables the tonemapping auto exposure mode of the scene renderer. If activated, the renderer will automatically determine the exposure setting to adapt to the illumination of the scene and the observed light. + If [code]true[/code], enables the tonemapping auto exposure mode of the scene renderer. If [code]true[/code], the renderer will automatically determine the exposure setting to adapt to the scene's illumination and the observed light. </member> <member name="auto_exposure_max_luma" type="float" setter="set_tonemap_auto_exposure_max" getter="get_tonemap_auto_exposure_max" default="8.0"> - Maximum luminance value for the auto exposure. + The maximum luminance value for the auto exposure. </member> <member name="auto_exposure_min_luma" type="float" setter="set_tonemap_auto_exposure_min" getter="get_tonemap_auto_exposure_min" default="0.05"> - Minimum luminance value for the auto exposure. + The minimum luminance value for the auto exposure. </member> <member name="auto_exposure_scale" type="float" setter="set_tonemap_auto_exposure_grey" getter="get_tonemap_auto_exposure_grey" default="0.4"> - Scale of the auto exposure effect. Affects the intensity of auto exposure. + The scale of the auto exposure effect. Affects the intensity of auto exposure. </member> <member name="auto_exposure_speed" type="float" setter="set_tonemap_auto_exposure_speed" getter="get_tonemap_auto_exposure_speed" default="0.5"> - Speed of the auto exposure effect. Affects the time needed for the camera to perform auto exposure. + The speed of the auto exposure effect. Affects the time needed for the camera to perform auto exposure. </member> <member name="background_camera_feed_id" type="int" setter="set_camera_feed_id" getter="get_camera_feed_id" default="1"> - The id of the camera feed to show in the background. + The ID of the camera feed to show in the background. </member> <member name="background_canvas_max_layer" type="int" setter="set_canvas_max_layer" getter="get_canvas_max_layer" default="0"> - Maximum layer id (if using Layer background mode). + The maximum layer ID to display. Only effective when using the [constant BG_CANVAS] background mode. </member> <member name="background_color" type="Color" setter="set_bg_color" getter="get_bg_color" default="Color( 0, 0, 0, 1 )"> - Color displayed for clear areas of the scene (if using Custom color or Color+Sky background modes). + The [Color] displayed for clear areas of the scene. Only effective when using the [constant BG_COLOR] or [constant BG_COLOR_SKY] background modes). </member> <member name="background_energy" type="float" setter="set_bg_energy" getter="get_bg_energy" default="1.0"> - Power of light emitted by the background. + The power of the light emitted by the background. </member> <member name="background_mode" type="int" setter="set_background" getter="get_background" enum="Environment.BGMode" default="0"> - Defines the mode of background. + The background mode. See [enum BGMode] for possible values. </member> <member name="background_sky" type="Sky" setter="set_sky" getter="get_sky"> - [Sky] resource defined as background. + The [Sky] resource defined as background. </member> <member name="background_sky_custom_fov" type="float" setter="set_sky_custom_fov" getter="get_sky_custom_fov" default="0.0"> - [Sky] resource's custom field of view. + The [Sky] resource's custom field of view. </member> <member name="background_sky_orientation" type="Basis" setter="set_sky_orientation" getter="get_sky_orientation" default="Basis( 1, 0, 0, 0, 1, 0, 0, 0, 1 )"> - [Sky] resource's rotation expressed as a [Basis]. + The [Sky] resource's rotation expressed as a [Basis]. </member> <member name="background_sky_rotation" type="Vector3" setter="set_sky_rotation" getter="get_sky_rotation" default="Vector3( 0, 0, 0 )"> - [Sky] resource's rotation expressed as Euler angles in radians. + The [Sky] resource's rotation expressed as Euler angles in radians. </member> <member name="background_sky_rotation_degrees" type="Vector3" setter="set_sky_rotation_degrees" getter="get_sky_rotation_degrees" default="Vector3( 0, 0, 0 )"> - [Sky] resource's rotation expressed as Euler angles in degrees. + The [Sky] resource's rotation expressed as Euler angles in degrees. </member> <member name="dof_blur_far_amount" type="float" setter="set_dof_blur_far_amount" getter="get_dof_blur_far_amount" default="0.1"> - Amount of far blur. + The amount of far blur for the depth-of-field effect. </member> <member name="dof_blur_far_distance" type="float" setter="set_dof_blur_far_distance" getter="get_dof_blur_far_distance" default="10.0"> - Distance from the camera where the far blur effect affects the rendering. + The distance from the camera where the far blur effect affects the rendering. </member> <member name="dof_blur_far_enabled" type="bool" setter="set_dof_blur_far_enabled" getter="is_dof_blur_far_enabled" default="false"> - Enables the far blur effect. + If [code]true[/code], enables the depth-of-field far blur effect. </member> <member name="dof_blur_far_quality" type="int" setter="set_dof_blur_far_quality" getter="get_dof_blur_far_quality" enum="Environment.DOFBlurQuality" default="1"> - Quality of the far blur quality. + The depth-of-field far blur's quality. Higher values can mitigate the visible banding effect seen at higher strengths, but are much slower. </member> <member name="dof_blur_far_transition" type="float" setter="set_dof_blur_far_transition" getter="get_dof_blur_far_transition" default="5.0"> - Transition between no-blur area and far blur. + The length of the transition between the no-blur area and far blur. </member> <member name="dof_blur_near_amount" type="float" setter="set_dof_blur_near_amount" getter="get_dof_blur_near_amount" default="0.1"> - Amount of near blur. + The amount of near blur for the depth-of-field effect. </member> <member name="dof_blur_near_distance" type="float" setter="set_dof_blur_near_distance" getter="get_dof_blur_near_distance" default="2.0"> Distance from the camera where the near blur effect affects the rendering. </member> <member name="dof_blur_near_enabled" type="bool" setter="set_dof_blur_near_enabled" getter="is_dof_blur_near_enabled" default="false"> - Enables the near blur effect. + If [code]true[/code], enables the depth-of-field near blur effect. </member> <member name="dof_blur_near_quality" type="int" setter="set_dof_blur_near_quality" getter="get_dof_blur_near_quality" enum="Environment.DOFBlurQuality" default="1"> - Quality of the near blur quality. + The depth-of-field near blur's quality. Higher values can mitigate the visible banding effect seen at higher strengths, but are much slower. </member> <member name="dof_blur_near_transition" type="float" setter="set_dof_blur_near_transition" getter="get_dof_blur_near_transition" default="1.0"> - Transition between near blur and no-blur area. + The length of the transition between the near blur and no-blur area. </member> <member name="fog_color" type="Color" setter="set_fog_color" getter="get_fog_color" default="Color( 0.5, 0.6, 0.7, 1 )"> - Fog's [Color]. + The fog's [Color]. </member> <member name="fog_depth_begin" type="float" setter="set_fog_depth_begin" getter="get_fog_depth_begin" default="10.0"> - Fog's depth starting distance from the camera. + The fog's depth starting distance from the camera. </member> <member name="fog_depth_curve" type="float" setter="set_fog_depth_curve" getter="get_fog_depth_curve" default="1.0"> - Value defining the fog depth intensity. + The fog depth's intensity curve. A number of presets are available in the [b]Inspector[/b] by right-clicking the curve. </member> <member name="fog_depth_enabled" type="bool" setter="set_fog_depth_enabled" getter="is_fog_depth_enabled" default="true"> - Enables the fog depth. + If [code]true[/code], the depth fog effect is enabled. When enabled, fog will appear in the distance (relative to the camera). </member> <member name="fog_depth_end" type="float" setter="set_fog_depth_end" getter="get_fog_depth_end" default="100.0"> - Fog's depth end distance from the camera. If this value is set to 0, it will be equal to the current camera's [member Camera.far] value. + The fog's depth end distance from the camera. If this value is set to 0, it will be equal to the current camera's [member Camera.far] value. </member> <member name="fog_enabled" type="bool" setter="set_fog_enabled" getter="is_fog_enabled" default="false"> - Enables the fog. Needs [member fog_height_enabled] and/or [member fog_depth_enabled] to actually display fog. + If [code]true[/code], fog effects are enabled. [member fog_height_enabled] and/or [member fog_depth_enabled] must be set to [code]true[/code] to actually display fog. </member> <member name="fog_height_curve" type="float" setter="set_fog_height_curve" getter="get_fog_height_curve" default="1.0"> - Value defining the fog height intensity. + The height fog's intensity. A number of presets are available in the [b]Inspector[/b] by right-clicking the curve. </member> <member name="fog_height_enabled" type="bool" setter="set_fog_height_enabled" getter="is_fog_height_enabled" default="false"> - Enables the fog height. + If [code]true[/code], the height fog effect is enabled. When enabled, fog will appear in a defined height range, regardless of the distance from the camera. This can be used to simulate "deep water" effects with a lower performance cost compared to a dedicated shader. </member> <member name="fog_height_max" type="float" setter="set_fog_height_max" getter="get_fog_height_max" default="0.0"> The Y coordinate where the height fog will be the most intense. If this value is greater than [member fog_height_min], fog will be displayed from bottom to top. Otherwise, it will be displayed from top to bottom. @@ -165,131 +168,151 @@ The Y coordinate where the height fog will be the least intense. If this value is greater than [member fog_height_max], fog will be displayed from top to bottom. Otherwise, it will be displayed from bottom to top. </member> <member name="fog_sun_amount" type="float" setter="set_fog_sun_amount" getter="get_fog_sun_amount" default="0.0"> - Amount of sun that affects the fog rendering. + The intensity of the depth fog color transition when looking towards the sun. The sun's direction is determined automatically using the DirectionalLight node in the scene. </member> <member name="fog_sun_color" type="Color" setter="set_fog_sun_color" getter="get_fog_sun_color" default="Color( 1, 0.9, 0.7, 1 )"> - Sun [Color]. + The depth fog's [Color] when looking towards the sun. </member> <member name="fog_transmit_curve" type="float" setter="set_fog_transmit_curve" getter="get_fog_transmit_curve" default="1.0"> - Amount of light that the fog transmits. + The intensity of the fog light transmittance effect. Amount of light that the fog transmits. </member> <member name="fog_transmit_enabled" type="bool" setter="set_fog_transmit_enabled" getter="is_fog_transmit_enabled" default="false"> - Enables fog's light transmission. If enabled, lets reflections light to be transmitted by the fog. + Enables fog's light transmission effect. If [code]true[/code], light will be more visible in the fog to simulate light scattering as in real life. </member> <member name="glow_bicubic_upscale" type="bool" setter="set_glow_bicubic_upscale" getter="is_glow_bicubic_upscale_enabled" default="false"> - Smooths out blockiness created by sampling higher levels. + Smooths out the blockiness created by sampling higher levels, at the cost of performance. + [b]Note:[/b] Only available when using the GLES3 renderer. </member> <member name="glow_blend_mode" type="int" setter="set_glow_blend_mode" getter="get_glow_blend_mode" enum="Environment.GlowBlendMode" default="2"> - Glow blending mode. + The glow blending mode. </member> <member name="glow_bloom" type="float" setter="set_glow_bloom" getter="get_glow_bloom" default="0.0"> - Bloom value (global glow). + The bloom's intensity. If set to a value higher than [code]0[/code], this will make glow visible in areas darker than the [member glow_hdr_threshold]. </member> <member name="glow_enabled" type="bool" setter="set_glow_enabled" getter="is_glow_enabled" default="false"> - Enables glow rendering. + If [code]true[/code], the glow effect is enabled. </member> <member name="glow_hdr_luminance_cap" type="float" setter="set_glow_hdr_luminance_cap" getter="get_glow_hdr_luminance_cap" default="12.0"> + The higher threshold of the HDR glow. Areas brighter than this threshold will be clamped for the purposes of the glow effect. </member> <member name="glow_hdr_scale" type="float" setter="set_glow_hdr_bleed_scale" getter="get_glow_hdr_bleed_scale" default="2.0"> - Bleed scale of the HDR glow. + The bleed scale of the HDR glow. </member> <member name="glow_hdr_threshold" type="float" setter="set_glow_hdr_bleed_threshold" getter="get_glow_hdr_bleed_threshold" default="1.0"> - Bleed threshold of the HDR glow. In GLES2, this needs to be below 1.0 in order for glow to be visible, a default value of 0.9 works well. + The lower threshold of the HDR glow. When using the GLES2 renderer (which doesn't support HDR), this needs to be below [code]1.0[/code] for glow to be visible. A value of [code]0.9[/code] works well in this case. </member> <member name="glow_intensity" type="float" setter="set_glow_intensity" getter="get_glow_intensity" default="0.8"> - Glow intensity. In GLES2, this should be increased to 1.5 by default to compensate for not using HDR. + The glow intensity. When using the GLES2 renderer, this should be increased to 1.5 to compensate for the lack of HDR rendering. </member> <member name="glow_levels/1" type="bool" setter="set_glow_level" getter="is_glow_level_enabled" default="false"> - First level of glow (most local). + If [code]true[/code], the 1st level of glow is enabled. This is the most "local" level (least blurry). </member> <member name="glow_levels/2" type="bool" setter="set_glow_level" getter="is_glow_level_enabled" default="false"> - Second level of glow. + If [code]true[/code], the 2th level of glow is enabled. </member> <member name="glow_levels/3" type="bool" setter="set_glow_level" getter="is_glow_level_enabled" default="true"> - Third level of glow. + If [code]true[/code], the 3th level of glow is enabled. </member> <member name="glow_levels/4" type="bool" setter="set_glow_level" getter="is_glow_level_enabled" default="false"> - Fourth level of glow. + If [code]true[/code], the 4th level of glow is enabled. </member> <member name="glow_levels/5" type="bool" setter="set_glow_level" getter="is_glow_level_enabled" default="true"> - Fifth level of glow. + If [code]true[/code], the 5th level of glow is enabled. </member> <member name="glow_levels/6" type="bool" setter="set_glow_level" getter="is_glow_level_enabled" default="false"> - Sixth level of glow. + If [code]true[/code], the 6th level of glow is enabled. </member> <member name="glow_levels/7" type="bool" setter="set_glow_level" getter="is_glow_level_enabled" default="false"> - Seventh level of glow (most global). + If [code]true[/code], the 7th level of glow is enabled. This is the most "global" level (blurriest). </member> <member name="glow_strength" type="float" setter="set_glow_strength" getter="get_glow_strength" default="1.0"> - Glow strength. In GLES2, this should be increased to 1.3 by default to compensate for not using HDR. + The glow strength. When using the GLES2 renderer, this should be increased to 1.3 to compensate for the lack of HDR rendering. </member> <member name="ss_reflections_depth_tolerance" type="float" setter="set_ssr_depth_tolerance" getter="get_ssr_depth_tolerance" default="0.2"> + The depth tolerance for screen-space reflections. </member> <member name="ss_reflections_enabled" type="bool" setter="set_ssr_enabled" getter="is_ssr_enabled" default="false"> + If [code]true[/code], screen-space reflections are enabled. Screen-space reflections are more accurate than reflections from [GIProbe]s or [ReflectionProbe]s, but are slower and can't reflect surfaces occluded by others. </member> <member name="ss_reflections_fade_in" type="float" setter="set_ssr_fade_in" getter="get_ssr_fade_in" default="0.15"> + The fade-in distance for screen-space reflections. Affects the area from the reflected material to the screen-space reflection). </member> <member name="ss_reflections_fade_out" type="float" setter="set_ssr_fade_out" getter="get_ssr_fade_out" default="2.0"> + The fade-out distance for screen-space reflections. Affects the area from the screen-space reflection to the "global" reflection. </member> <member name="ss_reflections_max_steps" type="int" setter="set_ssr_max_steps" getter="get_ssr_max_steps" default="64"> + The maximum number of steps for screen-space reflections. Higher values are slower. </member> <member name="ss_reflections_roughness" type="bool" setter="set_ssr_rough" getter="is_ssr_rough" default="true"> + If [code]true[/code], screen-space reflections will take the material roughness into account. </member> <member name="ssao_ao_channel_affect" type="float" setter="set_ssao_ao_channel_affect" getter="get_ssao_ao_channel_affect" default="0.0"> + The screen-space ambient occlusion intensity on materials that have an AO texture defined. Values higher than [code]0[/code] will make the SSAO effect visible in areas darkened by AO textures. </member> <member name="ssao_bias" type="float" setter="set_ssao_bias" getter="get_ssao_bias" default="0.01"> + The screen-space ambient occlusion bias. This should be kept high enough to prevent "smooth" curves from being affected by ambient occlusion. </member> <member name="ssao_blur" type="int" setter="set_ssao_blur" getter="get_ssao_blur" enum="Environment.SSAOBlur" default="3"> + The screen-space ambient occlusion blur quality. See [enum SSAOBlur] for possible values. </member> <member name="ssao_color" type="Color" setter="set_ssao_color" getter="get_ssao_color" default="Color( 0, 0, 0, 1 )"> + The screen-space ambient occlusion color. </member> <member name="ssao_edge_sharpness" type="float" setter="set_ssao_edge_sharpness" getter="get_ssao_edge_sharpness" default="4.0"> + The screen-space ambient occlusion edge sharpness. </member> <member name="ssao_enabled" type="bool" setter="set_ssao_enabled" getter="is_ssao_enabled" default="false"> + If [code]true[/code], the screen-space ambient occlusion effect is enabled. This is a costly effect and should be disabled first when having performance issues. </member> <member name="ssao_intensity" type="float" setter="set_ssao_intensity" getter="get_ssao_intensity" default="1.0"> + The primary screen-space ambient occlusion intensity. See also [member ssao_radius]. </member> <member name="ssao_intensity2" type="float" setter="set_ssao_intensity2" getter="get_ssao_intensity2" default="1.0"> + The secondary screen-space ambient occlusion intensity. See also [member ssao_radius2]. </member> <member name="ssao_light_affect" type="float" setter="set_ssao_direct_light_affect" getter="get_ssao_direct_light_affect" default="0.0"> + The screen-space ambient occlusion intensity in direct light. In real life, ambient occlusion only applies to indirect light, which means its effects can't be seen in direct light. Values higher than [code]0[/code] will make the SSAO effect visible in direct light. </member> <member name="ssao_quality" type="int" setter="set_ssao_quality" getter="get_ssao_quality" enum="Environment.SSAOQuality" default="1"> + The screen-space ambient occlusion quality. Higher qualities will make better use of small objects for ambient occlusion, but are slower. </member> <member name="ssao_radius" type="float" setter="set_ssao_radius" getter="get_ssao_radius" default="1.0"> + The primary screen-space ambient occlusion radius. </member> <member name="ssao_radius2" type="float" setter="set_ssao_radius2" getter="get_ssao_radius2" default="0.0"> + The secondary screen-space ambient occlusion radius. If set to a value higher than [code]0[/code], enables the secondary screen-space ambient occlusion effect which can be used to improve the effect's appearance (at the cost of performance). </member> <member name="tonemap_exposure" type="float" setter="set_tonemap_exposure" getter="get_tonemap_exposure" default="1.0"> - Default exposure for tonemap. + The default exposure used for tonemapping. </member> <member name="tonemap_mode" type="int" setter="set_tonemapper" getter="get_tonemapper" enum="Environment.ToneMapper" default="0"> - Tonemapping mode. + The tonemapping mode to use. Tonemapping is the process that "converts" HDR values to be suitable for rendering on a LDR display. (Godot doesn't support rendering on HDR displays yet.) </member> <member name="tonemap_white" type="float" setter="set_tonemap_white" getter="get_tonemap_white" default="1.0"> - White reference value for tonemap. + The white reference value for tonemapping. Only effective if the [member tonemap_mode] isn't set to [constant TONE_MAPPER_LINEAR]. </member> </members> <constants> <constant name="BG_KEEP" value="5" enum="BGMode"> - Keep on screen every pixel drawn in the background. + Keeps on screen every pixel drawn in the background. This is the fastest background mode, but it can only be safely used in fully-interior scenes (no visible sky or sky reflections). If enabled in a scene where the background is visible, "ghost trail" artifacts will be visible when moving the camera. </constant> <constant name="BG_CLEAR_COLOR" value="0" enum="BGMode"> - Clear the background using the project's clear color. + Clears the background using the clear color defined in [member ProjectSettings.rendering/environment/default_clear_color]. </constant> <constant name="BG_COLOR" value="1" enum="BGMode"> - Clear the background using a custom clear color. + Clears the background using a custom clear color. </constant> <constant name="BG_SKY" value="2" enum="BGMode"> - Display a user-defined sky in the background. + Displays a user-defined sky in the background. </constant> <constant name="BG_COLOR_SKY" value="3" enum="BGMode"> - Clear the background using a custom clear color and allows defining a sky for shading and reflection. + Clears the background using a custom clear color and allows defining a sky for shading and reflection. This mode is slightly faster than [constant BG_SKY] and should be preferred in scenes where reflections can be visible, but the sky itself never is (e.g. top-down camera). </constant> <constant name="BG_CANVAS" value="4" enum="BGMode"> - Display a [CanvasLayer] in the background. + Displays a [CanvasLayer] in the background. </constant> <constant name="BG_CAMERA_FEED" value="6" enum="BGMode"> - Display a camera feed in the background. + Displays a camera feed in the background. </constant> <constant name="BG_MAX" value="7" enum="BGMode"> Represents the size of the [enum BGMode] enum. @@ -301,10 +324,10 @@ Screen glow blending mode. Increases brightness, used frequently with bloom. </constant> <constant name="GLOW_BLEND_MODE_SOFTLIGHT" value="2" enum="GlowBlendMode"> - Soft light glow blending mode. Modifies contrast, exposes shadows and highlights, vivid bloom. + Soft light glow blending mode. Modifies contrast, exposes shadows and highlights (vivid bloom). </constant> <constant name="GLOW_BLEND_MODE_REPLACE" value="3" enum="GlowBlendMode"> - Replace glow blending mode. Replaces all pixels' color by the glow value. + Replace glow blending mode. Replaces all pixels' color by the glow value. This can be used to simulate a full-screen blur effect by tweaking the glow parameters to match the original image's brightness. </constant> <constant name="TONE_MAPPER_LINEAR" value="0" enum="ToneMapper"> Linear tonemapper operator. Reads the linear data and passes it on unmodified. @@ -319,27 +342,34 @@ Academy Color Encoding System tonemapper operator. </constant> <constant name="DOF_BLUR_QUALITY_LOW" value="0" enum="DOFBlurQuality"> - Low depth-of-field blur quality. + Low depth-of-field blur quality (fastest). </constant> <constant name="DOF_BLUR_QUALITY_MEDIUM" value="1" enum="DOFBlurQuality"> Medium depth-of-field blur quality. </constant> <constant name="DOF_BLUR_QUALITY_HIGH" value="2" enum="DOFBlurQuality"> - High depth-of-field blur quality. + High depth-of-field blur quality (slowest). </constant> <constant name="SSAO_BLUR_DISABLED" value="0" enum="SSAOBlur"> + No blur for the screen-space ambient occlusion effect (fastest). </constant> <constant name="SSAO_BLUR_1x1" value="1" enum="SSAOBlur"> + 1×1 blur for the screen-space ambient occlusion effect. </constant> <constant name="SSAO_BLUR_2x2" value="2" enum="SSAOBlur"> + 2×2 blur for the screen-space ambient occlusion effect. </constant> <constant name="SSAO_BLUR_3x3" value="3" enum="SSAOBlur"> + 3×3 blur for the screen-space ambient occlusion effect (slowest). </constant> <constant name="SSAO_QUALITY_LOW" value="0" enum="SSAOQuality"> + Low quality for the screen-space ambient occlusion effect (fastest). </constant> <constant name="SSAO_QUALITY_MEDIUM" value="1" enum="SSAOQuality"> + Low quality for the screen-space ambient occlusion effect. </constant> <constant name="SSAO_QUALITY_HIGH" value="2" enum="SSAOQuality"> + Low quality for the screen-space ambient occlusion effect (slowest). </constant> </constants> </class> diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index 3041a7cb7f..802134d3b6 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -160,6 +160,7 @@ <argument index="4" name="amount" type="float"> </argument> <description> + Sets the coloration of the connection between [code]from[/code]'s [code]from_port[/code] and [code]to[/code]'s [code]to_port[/code] with the color provided in the [code]activity[/code] theme property. </description> </method> <method name="set_selected"> @@ -194,12 +195,12 @@ <signals> <signal name="_begin_node_move"> <description> - Signal sent at the beginning of a GraphNode movement. + Emitted at the beginning of a GraphNode movement. </description> </signal> <signal name="_end_node_move"> <description> - Signal sent at the end of a GraphNode movement. + Emitted at the end of a GraphNode movement. </description> </signal> <signal name="connection_from_empty"> @@ -210,7 +211,7 @@ <argument index="2" name="release_position" type="Vector2"> </argument> <description> - Signal sent when user dragging connection from input port into empty space of the graph. + Emitted when user dragging connection from input port into empty space of the graph. </description> </signal> <signal name="connection_request"> @@ -223,7 +224,7 @@ <argument index="3" name="to_slot" type="int"> </argument> <description> - Signal sent to the GraphEdit when the connection between the [code]from_slot[/code] slot of the [code]from[/code] GraphNode and the [code]to_slot[/code] slot of the [code]to[/code] GraphNode is attempted to be created. + Emitted to the GraphEdit when the connection between the [code]from_slot[/code] slot of the [code]from[/code] GraphNode and the [code]to_slot[/code] slot of the [code]to[/code] GraphNode is attempted to be created. </description> </signal> <signal name="connection_to_empty"> @@ -234,17 +235,17 @@ <argument index="2" name="release_position" type="Vector2"> </argument> <description> - Signal sent when user dragging connection from output port into empty space of the graph. + Emitted when user dragging connection from output port into empty space of the graph. </description> </signal> <signal name="copy_nodes_request"> <description> - Signal sent when the user presses [code]Ctrl + C[/code]. + Emitted when the user presses [code]Ctrl + C[/code]. </description> </signal> <signal name="delete_nodes_request"> <description> - Signal sent when a GraphNode is attempted to be removed from the GraphEdit. + Emitted when a GraphNode is attempted to be removed from the GraphEdit. </description> </signal> <signal name="disconnection_request"> @@ -274,7 +275,7 @@ </signal> <signal name="paste_nodes_request"> <description> - Signal sent when the user presses [code]Ctrl + V[/code]. + Emitted when the user presses [code]Ctrl + V[/code]. </description> </signal> <signal name="popup_request"> @@ -288,6 +289,7 @@ <argument index="0" name="ofs" type="Vector2"> </argument> <description> + Emitted when the scroll offset is changed by the user. It will not be emitted when changed in code. </description> </signal> </signals> @@ -316,6 +318,10 @@ </theme_item> <theme_item name="reset" type="Texture"> </theme_item> + <theme_item name="selection_fill" type="Color" default="Color( 1, 1, 1, 0.3 )"> + </theme_item> + <theme_item name="selection_stroke" type="Color" default="Color( 1, 1, 1, 0.8 )"> + </theme_item> <theme_item name="snap" type="Texture"> </theme_item> </theme_items> diff --git a/doc/classes/GraphNode.xml b/doc/classes/GraphNode.xml index 77b3eb1ca0..8fda9c20a5 100644 --- a/doc/classes/GraphNode.xml +++ b/doc/classes/GraphNode.xml @@ -170,12 +170,12 @@ <argument index="8" name="custom_right" type="Texture" default="null"> </argument> <description> - Sets properties of the slot with id [code]idx[/code]. + Sets properties of the slot with ID [code]idx[/code]. If [code]enable_left[/code]/[code]right[/code], a port will appear and the slot will be able to be connected from this side. [code]type_left[/code]/[code]right[/code] is an arbitrary type of the port. Only ports with the same type values can be connected. [code]color_left[/code]/[code]right[/code] is the tint of the port's icon on this side. [code]custom_left[/code]/[code]right[/code] is a custom texture for this side's port. - [b]Note:[/b] this method only sets properties of the slot. To create the slot, add a [Control]-derived child to the GraphNode. + [b]Note:[/b] This method only sets properties of the slot. To create the slot, add a [Control]-derived child to the GraphNode. </description> </method> </methods> @@ -188,20 +188,27 @@ [b]Note:[/b] You cannot use position directly, as [GraphEdit] is a [Container]. </member> <member name="overlay" type="int" setter="set_overlay" getter="get_overlay" enum="GraphNode.Overlay" default="0"> + Sets the overlay shown above the GraphNode. See [enum Overlay]. </member> <member name="resizable" type="bool" setter="set_resizable" getter="is_resizable" default="false"> + If [code]true[/code], the user can resize the GraphNode. + [b]Note:[/b] Dragging the handle will only trigger the [signal resize_request] signal, the GraphNode needs to be resized manually. </member> <member name="selected" type="bool" setter="set_selected" getter="is_selected" default="false"> + If [code]true[/code], the GraphNode is selected. </member> <member name="show_close" type="bool" setter="set_show_close_button" getter="is_close_button_visible" default="false"> + If [code]true[/code], the close button will be visible. + [b]Note:[/b] Pressing it will only trigger the [signal close_request] signal, the GraphNode needs to be removed manually. </member> <member name="title" type="String" setter="set_title" getter="get_title" default=""""> + The text displayed in the GraphNode's title bar. </member> </members> <signals> <signal name="close_request"> <description> - Signal sent on closing the GraphNode. + Emitted when the GraphNode is requested to be closed. Happens on clicking the close button (see [member show_close]). </description> </signal> <signal name="dragged"> @@ -210,32 +217,36 @@ <argument index="1" name="to" type="Vector2"> </argument> <description> - Signal sent when the GraphNode is dragged. + Emitted when the GraphNode is dragged. </description> </signal> <signal name="offset_changed"> <description> - Signal sent when the GraphNode is moved. + Emitted when the GraphNode is moved. </description> </signal> <signal name="raise_request"> <description> - Signal sent when the GraphNode is requested to be displayed over other ones. Happens on focusing (clicking into) the GraphNode. + Emitted when the GraphNode is requested to be displayed over other ones. Happens on focusing (clicking into) the GraphNode. </description> </signal> <signal name="resize_request"> <argument index="0" name="new_minsize" type="Vector2"> </argument> <description> + Emitted when the GraphNode is requested to be resized. Happens on dragging the resizer handle (see [member resizable]). </description> </signal> </signals> <constants> <constant name="OVERLAY_DISABLED" value="0" enum="Overlay"> + No overlay is shown. </constant> <constant name="OVERLAY_BREAKPOINT" value="1" enum="Overlay"> + Show overlay set in the [code]breakpoint[/code] theme property. </constant> <constant name="OVERLAY_POSITION" value="2" enum="Overlay"> + Show overlay set in the [code]position[/code] theme property. </constant> </constants> <theme_items> diff --git a/doc/classes/MultiMesh.xml b/doc/classes/MultiMesh.xml index 8a72aa155b..ec65f407cd 100644 --- a/doc/classes/MultiMesh.xml +++ b/doc/classes/MultiMesh.xml @@ -11,6 +11,7 @@ </description> <tutorials> <link>http://docs.godotengine.org/en/latest/tutorials/3d/vertex_animation/animating_thousands_of_fish.html</link> + <link>http://docs.godotengine.org/en/latest/tutorials/optimization/using_multimesh.html</link> </tutorials> <methods> <method name="get_aabb" qualifiers="const"> @@ -87,7 +88,7 @@ <argument index="1" name="custom_data" type="Color"> </argument> <description> - Sets custom data for a specific instance. Although [Color] is used, it is just a container for 4 numbers. + Sets custom data for a specific instance. Although [Color] is used, it is just a container for 4 floating point numbers. The format of the number can change depending on the [enum CustomDataFormat] used. </description> </method> <method name="set_instance_transform"> @@ -153,7 +154,7 @@ Use when you are not using per-instance custom data. </constant> <constant name="CUSTOM_DATA_8BIT" value="1" enum="CustomDataFormat"> - Compress custom_data into 8 bits when passing to shader. This uses less memory and can be faster, but loses precision. + Compress custom_data into 8 bits when passing to shader. This uses less memory and can be faster, but loses precision and range. Floats packed into 8 bits can only represent values between 0 and 1, numbers outside that range will be clamped. </constant> <constant name="CUSTOM_DATA_FLOAT" value="2" enum="CustomDataFormat"> The [Color] passed into [method set_instance_custom_data] will use 4 floats. Use this for highest precision. diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 22f4dc2d83..71d0c1a6fe 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -655,6 +655,14 @@ Returns [code]true[/code] if the window should always be on top of other windows. </description> </method> + <method name="is_window_focused" qualifiers="const"> + <return type="bool"> + </return> + <description> + Returns [code]true[/code] if the window is currently focused. + [b]Note:[/b] Only implemented on desktop platforms. On other platforms, it will always return [code]true[/code]. + </description> + </method> <method name="kill"> <return type="int" enum="Error"> </return> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 1d7e5f8080..d063bd81e7 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -203,7 +203,7 @@ <argument index="0" name="property" type="String"> </argument> <description> - Returns the [Variant] value of the given [code]property[/code]. + Returns the [Variant] value of the given [code]property[/code]. If the [code]property[/code] doesn't exist, this will return [code]null[/code]. </description> </method> <method name="get_class" qualifiers="const"> @@ -460,6 +460,7 @@ </argument> <description> Assigns a script to the object. Each object can have a single script assigned to it, which are used to extend its functionality. + If the object already had a script, the previous script instance will be freed and its variables and state will be lost. The new script's [method _init] method will be called. </description> </method> <method name="to_string"> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index c920b52df6..89039eebda 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -521,6 +521,7 @@ <argument index="1" name="state" type="int"> </argument> <description> + Sets the state of an multistate item. See [method add_multistate_item] for details. </description> </method> <method name="set_item_shortcut"> @@ -595,6 +596,7 @@ <argument index="0" name="idx" type="int"> </argument> <description> + Cycle to the next state of an multistate item. See [method add_multistate_item] for details. </description> </method> </methods> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 2567b64c03..c3fb226b6a 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -91,6 +91,7 @@ <argument index="0" name="effect" type="Variant"> </argument> <description> + Installs a custom effect. [code]effect[/code] should be a valid [RichTextEffect]. </description> </method> <method name="newline"> @@ -115,6 +116,7 @@ <argument index="0" name="expressions" type="PoolStringArray"> </argument> <description> + Parses BBCode parameter [code]expressions[/code] into a dictionary. </description> </method> <method name="pop"> @@ -137,12 +139,14 @@ <return type="void"> </return> <description> + Adds a [code][font][/code] tag with a bold font to the tag stack. This is the same as adding a [code][b][/code] tag if not currently in a [code][i][/code] tag. </description> </method> <method name="push_bold_italics"> <return type="void"> </return> <description> + Adds a [code][font][/code] tag with a bold italics font to the tag stack. </description> </method> <method name="push_cell"> @@ -176,13 +180,14 @@ <argument index="0" name="level" type="int"> </argument> <description> - Adds an [code][indent][/code] tag to the tag stack. Multiplies "level" by current tab_size to determine new margin length. + Adds an [code][indent][/code] tag to the tag stack. Multiplies [code]level[/code] by current [member tab_size] to determine new margin length. </description> </method> <method name="push_italics"> <return type="void"> </return> <description> + Adds a [code][font][/code] tag with a italics font to the tag stack. This is the same as adding a [code][i][/code] tag if not currently in a [code][b][/code] tag. </description> </method> <method name="push_list"> @@ -207,12 +212,14 @@ <return type="void"> </return> <description> + Adds a [code][font][/code] tag with a monospace font to the tag stack. </description> </method> <method name="push_normal"> <return type="void"> </return> <description> + Adds a [code][font][/code] tag with a normal font to the tag stack. </description> </method> <method name="push_strikethrough"> @@ -245,6 +252,7 @@ </argument> <description> Removes a line of content from the label. Returns [code]true[/code] if the line exists. + The [code]line[/code] argument is the index of the line to remove, it can take values in the interval [code][0, get_line_count() - 1][/code]. </description> </method> <method name="scroll_to_line"> @@ -281,6 +289,8 @@ [b]Note:[/b] It is unadvised to use [code]+=[/code] operator with [code]bbcode_text[/code] (e.g. [code]bbcode_text += "some string"[/code]) as it replaces the whole text and can cause slowdowns. Use [method append_bbcode] for adding text instead. </member> <member name="custom_effects" type="Array" setter="set_effects" getter="get_effects" default="[ ]"> + The currently installed custom effects. This is an array of [RichTextEffect]s. + To add a custom effect, it's more convenient to use [method install_effect]. </member> <member name="meta_underlined" type="bool" setter="set_meta_underline" getter="is_meta_underlined" default="true"> If [code]true[/code], the label underlines meta tags such as [code][url]{text}[/url][/code]. @@ -289,11 +299,12 @@ If [code]true[/code], the label uses the custom font color. </member> <member name="percent_visible" type="float" setter="set_percent_visible" getter="get_percent_visible" default="1.0"> - The text's visibility, as a [float] between 0.0 and 1.0. + The range of characters to display, as a [float] between 0.0 and 1.0. When assigned an out of range value, it's the same as assigning 1.0. + [b]Note:[/b] Setting this property updates [member visible_characters] based on current [method get_total_character_count]. </member> <member name="rect_clip_content" type="bool" setter="set_clip_contents" getter="is_clipping_contents" override="true" default="true" /> <member name="scroll_active" type="bool" setter="set_scroll_active" getter="is_scroll_active" default="true"> - If [code]true[/code], the scrollbar is visible. Does not block scrolling completely. See [method scroll_to_line]. + If [code]true[/code], the scrollbar is visible. Setting this to [code]false[/code] does not block scrolling completely. See [method scroll_to_line]. </member> <member name="scroll_following" type="bool" setter="set_scroll_follow" getter="is_scroll_following" default="false"> If [code]true[/code], the window scrolls down to display new content automatically. @@ -317,7 +328,7 @@ <argument index="0" name="meta" type="Nil"> </argument> <description> - Triggered when the user clicks on content between [code][url][/code] tags. If the meta is defined in text, e.g. [code][url={"data"="hi"}]hi[/url][/code], then the parameter for this signal will be a [String] type. If a particular type or an object is desired, the [method push_meta] method must be used to manually insert the data into the tag stack. + Triggered when the user clicks on content between meta tags. If the meta is defined in text, e.g. [code][url={"data"="hi"}]hi[/url][/code], then the parameter for this signal will be a [String] type. If a particular type or an object is desired, the [method push_meta] method must be used to manually insert the data into the tag stack. </description> </signal> <signal name="meta_hover_ended"> @@ -337,18 +348,25 @@ </signals> <constants> <constant name="ALIGN_LEFT" value="0" enum="Align"> + Makes text left aligned. </constant> <constant name="ALIGN_CENTER" value="1" enum="Align"> + Makes text centered. </constant> <constant name="ALIGN_RIGHT" value="2" enum="Align"> + Makes text right aligned. </constant> <constant name="ALIGN_FILL" value="3" enum="Align"> + Makes text fill width. </constant> <constant name="LIST_NUMBERS" value="0" enum="ListType"> + Each list item has a number marker. </constant> <constant name="LIST_LETTERS" value="1" enum="ListType"> + Each list item has a letter marker. </constant> <constant name="LIST_DOTS" value="2" enum="ListType"> + Each list item has a filled circle marker. </constant> <constant name="ITEM_FRAME" value="0" enum="ItemType"> </constant> diff --git a/doc/classes/SplitContainer.xml b/doc/classes/SplitContainer.xml index 71731f685a..25d4546c3a 100644 --- a/doc/classes/SplitContainer.xml +++ b/doc/classes/SplitContainer.xml @@ -4,7 +4,7 @@ Container for splitting and adjusting. </brief_description> <description> - Container for splitting two controls vertically or horizontally, with a grabber that allows adjusting the split offset or ratio. + Container for splitting two [Control]s vertically or horizontally, with a grabber that allows adjusting the split offset or ratio. </description> <tutorials> </tutorials> @@ -13,16 +13,19 @@ <return type="void"> </return> <description> + Clamps the [member split_offset] value to not go outside the currently possible minimal and maximum values. </description> </method> </methods> <members> <member name="collapsed" type="bool" setter="set_collapsed" getter="is_collapsed" default="false"> + If [code]true[/code], the area of the first [Control] will be collapsed and the dragger will be disabled. </member> <member name="dragger_visibility" type="int" setter="set_dragger_visibility" getter="get_dragger_visibility" enum="SplitContainer.DraggerVisibility" default="0"> - Determines the dragger's visibility. See [enum DraggerVisibility] for options. + Determines the dragger's visibility. See [enum DraggerVisibility] for details. </member> <member name="split_offset" type="int" setter="set_split_offset" getter="get_split_offset" default="0"> + The initial offset of the splitting between the two [Control]s, with [code]0[/code] being at the end of the first [Control]. </member> </members> <signals> @@ -36,13 +39,13 @@ </signals> <constants> <constant name="DRAGGER_VISIBLE" value="0" enum="DraggerVisibility"> - The split dragger is visible. + The split dragger is visible when the cursor hovers it. </constant> <constant name="DRAGGER_HIDDEN" value="1" enum="DraggerVisibility"> - The split dragger is invisible. + The split dragger is never visible. </constant> <constant name="DRAGGER_HIDDEN_COLLAPSED" value="2" enum="DraggerVisibility"> - The split dragger is invisible and collapsed. + The split dragger is never visible and its space collapsed. </constant> </constants> </class> diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index 1f584ad317..e5f126c344 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -150,6 +150,7 @@ If [code]true[/code], tabs are visible. If [code]false[/code], tabs' content and titles are hidden. </member> <member name="use_hidden_tabs_for_min_size" type="bool" setter="set_use_hidden_tabs_for_min_size" getter="get_use_hidden_tabs_for_min_size" default="false"> + If [code]true[/code], children [Control] nodes that are hidden have their minimum size take into account in the total, instead of only the currently visible one. </member> </members> <signals> diff --git a/doc/classes/Tabs.xml b/doc/classes/Tabs.xml index 6bd7b8c2c3..04119b7cdb 100644 --- a/doc/classes/Tabs.xml +++ b/doc/classes/Tabs.xml @@ -33,6 +33,7 @@ <return type="bool"> </return> <description> + Returns [code]true[/code] if the offset buttons (the ones that appear when there's not enough space for all tabs) are visible. </description> </method> <method name="get_select_with_rmb" qualifiers="const"> @@ -71,6 +72,7 @@ <return type="int"> </return> <description> + Returns the number of hidden tabs offsetted to the left. </description> </method> <method name="get_tab_rect" qualifiers="const"> @@ -179,11 +181,13 @@ If [code]true[/code], tabs can be rearranged with mouse drag. </member> <member name="scrolling_enabled" type="bool" setter="set_scrolling_enabled" getter="get_scrolling_enabled" default="true"> + if [code]true[/code], the mouse's scroll wheel cab be used to navigate the scroll view. </member> <member name="tab_align" type="int" setter="set_tab_align" getter="get_tab_align" enum="Tabs.TabAlign" default="1"> - The alignment of all tabs. See enum [code]TabAlign[/code] constants for details. + The alignment of all tabs. See [enum TabAlign] for details. </member> <member name="tab_close_display_policy" type="int" setter="set_tab_close_display_policy" getter="get_tab_close_display_policy" enum="Tabs.CloseButtonDisplayPolicy" default="0"> + Sets when the close button will appear on the tabs. See [enum CloseButtonDisplayPolicy] for details. </member> </members> <signals> @@ -191,36 +195,42 @@ <argument index="0" name="idx_to" type="int"> </argument> <description> + Emitted when the active tab is rearranged via mouse drag. See [member drag_to_rearrange_enabled]. </description> </signal> <signal name="right_button_pressed"> <argument index="0" name="tab" type="int"> </argument> <description> + Emitted when a tab is right-clicked. </description> </signal> <signal name="tab_changed"> <argument index="0" name="tab" type="int"> </argument> <description> + Emitted when switching to another tab. </description> </signal> <signal name="tab_clicked"> <argument index="0" name="tab" type="int"> </argument> <description> + Emitted when a tab is clicked, even if it is the current tab. </description> </signal> <signal name="tab_close"> <argument index="0" name="tab" type="int"> </argument> <description> + Emitted when a tab is closed. </description> </signal> <signal name="tab_hover"> <argument index="0" name="tab" type="int"> </argument> <description> + Emitted when a tab is hovered by the mouse. </description> </signal> </signals> @@ -238,10 +248,13 @@ Represents the size of the [enum TabAlign] enum. </constant> <constant name="CLOSE_BUTTON_SHOW_NEVER" value="0" enum="CloseButtonDisplayPolicy"> + Never show the close buttons. </constant> <constant name="CLOSE_BUTTON_SHOW_ACTIVE_ONLY" value="1" enum="CloseButtonDisplayPolicy"> + Only show the close button on the currently active tab. </constant> <constant name="CLOSE_BUTTON_SHOW_ALWAYS" value="2" enum="CloseButtonDisplayPolicy"> + Show the close button on all tabs. </constant> <constant name="CLOSE_BUTTON_MAX" value="3" enum="CloseButtonDisplayPolicy"> Represents the size of the [enum CloseButtonDisplayPolicy] enum. diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 75fceac500..ad2cadb96b 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -459,6 +459,12 @@ <member name="wrap_enabled" type="bool" setter="set_wrap_enabled" getter="is_wrap_enabled" default="false"> If [code]true[/code], enables text wrapping when it goes beyond the edge of what is visible. </member> + <member name="scroll_vertical" type="float" setter="set_v_scroll" getter="get_v_scroll" default="0.0"> + The current vertical scroll value. + </member> + <member name="scroll_horizontal" type="int" setter="set_h_scroll" getter="get_h_scroll" default="0"> + The current horizontal scroll value. + </member> </members> <signals> <signal name="breakpoint_toggled"> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index dd4330b00b..aa1f8638d2 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -43,14 +43,18 @@ <argument index="1" name="idx" type="int" default="-1"> </argument> <description> - Create an item in the tree and add it as the last child of [code]parent[/code]. If [code]parent[/code] is [code]null[/code], it will be added as the root's last child, or it'll be the the root itself if the tree is empty. + Creates an item in the tree and adds it as a child of [code]parent[/code]. + If [code]parent[/code] is [code]null[/code], the root item will be the parent, or the new item will be the root itself if the tree is empty. + The new item will be the [code]idx[/code]th child of parent, or it will be the last child if there are not enough siblings. </description> </method> <method name="ensure_cursor_is_visible"> <return type="void"> </return> <description> - Makes the currently selected item visible. This will scroll the tree to make sure the selected item is visible. + Makes the currently focused cell visible. + This will scroll the tree if necessary. In [constant SELECT_ROW] mode, this will not do horizontal scrolling, as all the cells in the selected row is focused logically. + [b]Note:[/b] Despite the name of this method, the focus cursor itself is only visible in [constant SELECT_MULTI] mode. </description> </method> <method name="get_column_at_position" qualifiers="const"> @@ -59,7 +63,7 @@ <argument index="0" name="position" type="Vector2"> </argument> <description> - Returns the column index under the given point. + Returns the column index at [code]position[/code], or -1 if no item is there. </description> </method> <method name="get_column_title" qualifiers="const"> @@ -93,8 +97,9 @@ <argument index="0" name="position" type="Vector2"> </argument> <description> - If [member drop_mode_flags] includes [constant DROP_MODE_INBETWEEN], returns -1 if [code]position[/code] is the upper part of a tree item at that position, 1 for the lower part, and additionally 0 for the middle part if [member drop_mode_flags] includes [constant DROP_MODE_ON_ITEM]. - Otherwise, returns 0. If there are no tree items at [code]position[/code], returns -100. + Returns the drop section at [code]position[/code], or -100 if no item is there. + Values -1, 0, or 1 will be returned for the "above item", "on item", and "below item" drop sections, respectively. See [enum DropModeFlags] for a description of each drop section. + To get the item which the returned drop section is relative to, use [method get_item_at_position]. </description> </method> <method name="get_edited" qualifiers="const"> @@ -119,7 +124,7 @@ <argument index="1" name="column" type="int" default="-1"> </argument> <description> - Returns the rectangle area for the specified item. If column is specified, only get the position and size of that column, otherwise get the rectangle containing all columns. + Returns the rectangle area for the specified item. If [code]column[/code] is specified, only get the position and size of that column, otherwise get the rectangle containing all columns. </description> </method> <method name="get_item_at_position" qualifiers="const"> @@ -137,7 +142,8 @@ <argument index="0" name="from" type="Object"> </argument> <description> - Returns the next selected item after the given one. + Returns the next selected item after the given one, or [code]null[/code] if the end is reached. + If [code]from[/code] is [code]null[/code], this returns the first selected item. </description> </method> <method name="get_pressed_button" qualifiers="const"> @@ -151,7 +157,7 @@ <return type="TreeItem"> </return> <description> - Returns the tree's root item. + Returns the tree's root item, or [code]null[/code] if the tree is empty. </description> </method> <method name="get_scroll" qualifiers="const"> @@ -165,14 +171,18 @@ <return type="TreeItem"> </return> <description> - Returns the currently selected item. + Returns the currently focused item, or [code]null[/code] if no item is focused. + In [constant SELECT_ROW] and [constant SELECT_SINGLE] modes, the focused item is same as the selected item. In [constant SELECT_MULTI] mode, the focused item is the item under the focus cursor, not necessarily selected. + To get the currently selected item(s), use [method get_next_selected]. </description> </method> <method name="get_selected_column" qualifiers="const"> <return type="int"> </return> <description> - Returns the current selection's column. + Returns the currently focused column, or -1 if no column is focused. + In [constant SELECT_SINGLE] mode, the focused column is the selected column. In [constant SELECT_ROW] mode, the focused column is always 0 if any item is selected. In [constant SELECT_MULTI] mode, the focused column is the column under the focus cursor, and there are not necessarily any column selected. + To tell whether a column of an item is selected, use [method TreeItem.is_selected]. </description> </method> <method name="set_column_expand"> @@ -230,6 +240,7 @@ </member> <member name="drop_mode_flags" type="int" setter="set_drop_mode_flags" getter="get_drop_mode_flags" default="0"> The drop mode as an OR combination of flags. See [enum DropModeFlags] constants. Once dropping is done, reverts to [constant DROP_MODE_DISABLED]. Setting this during [method Control.can_drop_data] is recommended. + This controls the drop sections, i.e. the decision and drawing of possible drop locations based on the mouse position. </member> <member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" override="true" enum="Control.FocusMode" default="2" /> <member name="hide_folding" type="bool" setter="set_hide_folding" getter="is_folding_hidden" default="false"> @@ -278,6 +289,7 @@ <argument index="0" name="position" type="Vector2"> </argument> <description> + Emitted when the right mouse button is pressed in the empty space of the tree. </description> </signal> <signal name="empty_tree_rmb_selected"> @@ -343,23 +355,34 @@ </signal> <signal name="nothing_selected"> <description> + Emitted when a left mouse button click does not select any item. </description> </signal> </signals> <constants> <constant name="SELECT_SINGLE" value="0" enum="SelectMode"> - Allows selection of a single item at a time. + Allows selection of a single cell at a time. From the perspective of items, only a single item is allowed to be selected. And there is only one column selected in the selected item. + The focus cursor is always hidden in this mode, but it is positioned at the current selection, making the currently selected item the currently focused item. </constant> <constant name="SELECT_ROW" value="1" enum="SelectMode"> + Allows selection of a single row at a time. From the perspective of items, only a single items is allowed to be selected. And all the columns are selected in the selected item. + The focus cursor is always hidden in this mode, but it is positioned at the first column of the current selection, making the currently selected item the currently focused item. </constant> <constant name="SELECT_MULTI" value="2" enum="SelectMode"> - Allows selection of multiple items at the same time. + Allows selection of multiple cells at the same time. From the perspective of items, multiple items are allowed to be selected. And there can be multiple columns selected in each selected item. + The focus cursor is visible in this mode, the item or column under the cursor is not necessarily selected. </constant> <constant name="DROP_MODE_DISABLED" value="0" enum="DropModeFlags"> + Disables all drop sections, but still allows to detect the "on item" drop section by [method get_drop_section_at_position]. + [b]Note:[/b] This is the default flag, it has no effect when combined with other flags. </constant> <constant name="DROP_MODE_ON_ITEM" value="1" enum="DropModeFlags"> + Enables the "on item" drop section. This drop section covers the entire item. + When combined with [constant DROP_MODE_INBETWEEN], this drop section halves the height and stays centered vertically. </constant> <constant name="DROP_MODE_INBETWEEN" value="2" enum="DropModeFlags"> + Enables "above item" and "below item" drop sections. The "above item" drop section covers the top half of the item, and the "below item" drop section covers the bottom half. + When combined with [constant DROP_MODE_ON_ITEM], these drop sections halves the height and stays on top / bottom accordingly. </constant> </constants> <theme_items> diff --git a/doc/classes/VisualServer.xml b/doc/classes/VisualServer.xml index a216d4b7b3..6b66721237 100644 --- a/doc/classes/VisualServer.xml +++ b/doc/classes/VisualServer.xml @@ -6,8 +6,16 @@ <description> Server for anything visible. The visual server is the API backend for everything visible. The whole scene system mounts on it to display. The visual server is completely opaque, the internals are entirely implementation specific and cannot be accessed. + The visual server can be used to bypass the scene system entirely. + Resources are created using the [code]*_create[/code] functions. + All objects are drawn to a viewport. You can use the [Viewport] attached to the [SceneTree] or you can create one yourself with [method viewport_create]. When using a custom scenario or canvas, the scenario or canvas needs to be attached to the viewport using [method viewport_set_scenario] or [method viewport_attach_canvas]. + In 3D, all visual objects must be associated with a scenario. The scenario is a visual representation of the world. If accessing the visual server from a running game, the scenario can be accessed from the scene tree from any [Spatial] node with [method Spatial.get_world]. Otherwise, a scenario can be created with [method scenario_create]. + Similarly in 2D, a canvas is needed to draw all canvas items. + In 3D, all visible objects are comprised of a resource and an instance. A resource can be a mesh, a particle system, a light, or any other 3D object. In order to be visible resources must be attached to an instance using [method instance_set_base]. The instance must also be attached to the scenario using [method instance_set_scenario] in order to be visible. + In 2D, all visible objects are some form of canvas item. In order to be visible, a canvas item needs to be the child of a canvas attached to a viewport, or it needs to be the child of another canvas item that is eventually attached to the canvas. </description> <tutorials> + <link>http://docs.godotengine.org/en/latest/tutorials/optimization/using_servers.html</link> </tutorials> <methods> <method name="black_bars_set_images"> @@ -45,6 +53,7 @@ </return> <description> Creates a camera and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]camera_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="camera_set_cull_mask"> @@ -142,7 +151,8 @@ <return type="RID"> </return> <description> - Creates a canvas and returns the assigned [RID]. + Creates a canvas and returns the assigned [RID]. It can be accessed with the RID that is returned. This RID will be used in all [code]canvas_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="canvas_item_add_circle"> @@ -441,7 +451,8 @@ <return type="RID"> </return> <description> - Creates a new [CanvasItem] and returns its [RID]. + Creates a new [CanvasItem] and returns its [RID]. It can be accessed with the RID that is returned. This RID will be used in all [code]canvas_item_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="canvas_item_set_clip"> @@ -555,7 +566,7 @@ <argument index="1" name="parent" type="RID"> </argument> <description> - Sets the parent for the [CanvasItem]. + Sets the parent for the [CanvasItem]. The parent can be another canvas item, or it can be the root canvas that is attached to the viewport. </description> </method> <method name="canvas_item_set_self_modulate"> @@ -650,7 +661,8 @@ <return type="RID"> </return> <description> - Creates a canvas light. + Creates a canvas light and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]canvas_light_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="canvas_light_occluder_attach_to_canvas"> @@ -668,7 +680,8 @@ <return type="RID"> </return> <description> - Creates a light occluder. + Creates a light occluder and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]canvas_light_ocluder_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="canvas_light_occluder_set_enabled"> @@ -932,7 +945,8 @@ <return type="RID"> </return> <description> - Creates a new light occluder polygon. + Creates a new light occluder polygon and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]canvas_occluder_polygon_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="canvas_occluder_polygon_set_cull_mode"> @@ -999,6 +1013,8 @@ </return> <description> Creates a directional light and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID can be used in most [code]light_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + To place in a scene, attach this directional light to an instance using [method instance_set_base] using the returned RID. </description> </method> <method name="draw"> @@ -1017,6 +1033,7 @@ </return> <description> Creates an environment and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]environment_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="environment_set_adjustment"> @@ -1388,6 +1405,22 @@ Returns the id of the test texture. Creates one if none exists. </description> </method> + <method name="get_video_adapter_name" qualifiers="const"> + <return type="String"> + </return> + <description> + Returns the name of the video adapter (e.g. "GeForce GTX 1080/PCIe/SSE2"). + [b]Note:[/b] When running a headless or server binary, this function returns an empty string. + </description> + </method> + <method name="get_video_adapter_vendor" qualifiers="const"> + <return type="String"> + </return> + <description> + Returns the vendor of the video adapter (e.g. "NVIDIA Corporation"). + [b]Note:[/b] When running a headless or server binary, this function returns an empty string. + </description> + </method> <method name="get_white_texture"> <return type="RID"> </return> @@ -1399,6 +1432,9 @@ <return type="RID"> </return> <description> + Creates a GI probe and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]gi_probe_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + To place in a scene, attach this GI probe to an instance using [method instance_set_base] using the returned RID. </description> </method> <method name="gi_probe_get_bias" qualifiers="const"> @@ -1407,6 +1443,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns the bias value for the GI probe. Bias is used to avoid self occlusion. Equivalent to [member GIProbeData.bias]. </description> </method> <method name="gi_probe_get_bounds" qualifiers="const"> @@ -1415,6 +1452,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns the axis-aligned bounding box that covers the full extent of the GI probe. </description> </method> <method name="gi_probe_get_cell_size" qualifiers="const"> @@ -1423,6 +1461,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns the cell size set by [method gi_probe_set_cell_size]. </description> </method> <method name="gi_probe_get_dynamic_data" qualifiers="const"> @@ -1431,6 +1470,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns the data used by the GI probe. </description> </method> <method name="gi_probe_get_dynamic_range" qualifiers="const"> @@ -1439,6 +1479,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns the dynamic range set for this GI probe. Equivalent to [member GIProbe.dynamic_range]. </description> </method> <method name="gi_probe_get_energy" qualifiers="const"> @@ -1447,6 +1488,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns the energy multiplier for this GI probe. Equivalent to [member GIProbe.energy]. </description> </method> <method name="gi_probe_get_normal_bias" qualifiers="const"> @@ -1455,6 +1497,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns the normal bias for this GI probe. Equivalent to [member GIProbe.normal_bias]. </description> </method> <method name="gi_probe_get_propagation" qualifiers="const"> @@ -1463,6 +1506,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns the propagation value for this GI probe. Equivalent to [member GIProbe.propagation]. </description> </method> <method name="gi_probe_get_to_cell_xform" qualifiers="const"> @@ -1471,6 +1515,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns the Transform set by [method gi_probe_set_to_cell_xform]. </description> </method> <method name="gi_probe_is_compressed" qualifiers="const"> @@ -1479,6 +1524,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns [code]true[/code] if the GI probe data associated with this GI probe is compressed. Equivalent to [member GIProbe.compress]. </description> </method> <method name="gi_probe_is_interior" qualifiers="const"> @@ -1487,6 +1533,7 @@ <argument index="0" name="probe" type="RID"> </argument> <description> + Returns [code]true[/code] if the GI probe is set to interior, meaning it does not account for sky light. Equivalent to [member GIProbe.interior]. </description> </method> <method name="gi_probe_set_bias"> @@ -1497,6 +1544,7 @@ <argument index="1" name="bias" type="float"> </argument> <description> + Sets the bias value to avoid self-occlusion. Equivalent to [member GIProbe.bias]. </description> </method> <method name="gi_probe_set_bounds"> @@ -1507,6 +1555,7 @@ <argument index="1" name="bounds" type="AABB"> </argument> <description> + Sets the axis-aligned bounding box that covers the extent of the GI probe. </description> </method> <method name="gi_probe_set_cell_size"> @@ -1517,6 +1566,7 @@ <argument index="1" name="range" type="float"> </argument> <description> + Sets the size of individual cells within the GI probe. </description> </method> <method name="gi_probe_set_compress"> @@ -1527,6 +1577,7 @@ <argument index="1" name="enable" type="bool"> </argument> <description> + Sets the compression setting for the GI probe data. Compressed data will take up less space but may look worse. Equivalent to [member GIProbe.compress]. </description> </method> <method name="gi_probe_set_dynamic_data"> @@ -1537,6 +1588,7 @@ <argument index="1" name="data" type="PoolIntArray"> </argument> <description> + Sets the data to be used in the GI probe for lighting calculations. Normally this is created and called internally within the [GIProbe] node. You should not try to set this yourself. </description> </method> <method name="gi_probe_set_dynamic_range"> @@ -1547,6 +1599,7 @@ <argument index="1" name="range" type="int"> </argument> <description> + Sets the dynamic range of the GI probe. Dynamic range sets the limit for how bright lights can be. A smaller range captures greater detail but limits how bright lights can be. Equivalent to [member GIProbe.dynamic_range]. </description> </method> <method name="gi_probe_set_energy"> @@ -1557,6 +1610,7 @@ <argument index="1" name="energy" type="float"> </argument> <description> + Sets the energy multiplier for this GI probe. A higher energy makes the indirect light from the GI probe brighter. Equivalent to [member GIProbe.energy]. </description> </method> <method name="gi_probe_set_interior"> @@ -1567,6 +1621,7 @@ <argument index="1" name="enable" type="bool"> </argument> <description> + Sets the interior value of this GI probe. A GI probe set to interior does not include the sky when calculating lighting. Equivalent to [member GIProbe.interior]. </description> </method> <method name="gi_probe_set_normal_bias"> @@ -1577,6 +1632,7 @@ <argument index="1" name="bias" type="float"> </argument> <description> + Sets the normal bias for this GI probe. Normal bias behaves similar to the other form of bias and may help reduce self-occlusion. Equivalent to [member GIProbe.normal_bias]. </description> </method> <method name="gi_probe_set_propagation"> @@ -1587,6 +1643,7 @@ <argument index="1" name="propagation" type="float"> </argument> <description> + Sets the propagation of light within this GI probe. Equivalent to [member GIProbe.propagation]. </description> </method> <method name="gi_probe_set_to_cell_xform"> @@ -1597,6 +1654,7 @@ <argument index="1" name="xform" type="Transform"> </argument> <description> + Sets the to cell [Transform] for this GI probe. </description> </method> <method name="has_changed" qualifiers="const"> @@ -1612,6 +1670,7 @@ <argument index="0" name="feature" type="int" enum="VisualServer.Features"> </argument> <description> + Not yet implemented. Always returns [code]false[/code]. </description> </method> <method name="has_os_feature" qualifiers="const"> @@ -1660,7 +1719,9 @@ <return type="RID"> </return> <description> - Creates an [ImmediateGeometry] and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]immediate_*[/code] VisualServer functions. + Creates an immediate geometry and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]immediate_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + To place in a scene, attach this immediate geometry to an instance using [method instance_set_base] using the returned RID. </description> </method> <method name="immediate_end"> @@ -1755,13 +1816,14 @@ <argument index="1" name="vertex" type="Vector2"> </argument> <description> + Adds the next vertex using the information provided in advance. This is a helper class that calls [method immediate_vertex] under the hood. Equivalent to [method ImmediateGeometry.add_vertex]. </description> </method> <method name="init"> <return type="void"> </return> <description> - Initializes the visual server. + Initializes the visual server. This function is called internally by platform-dependent code during engine initialization. If called from a running game, it will not do anything. </description> </method> <method name="instance_attach_object_instance_id"> @@ -1772,6 +1834,7 @@ <argument index="1" name="id" type="int"> </argument> <description> + Attaches a unique Object ID to instance. Object ID must be attached to instance for proper culling with [method instances_cull_aabb], [method instances_cull_convex], and [method instances_cull_ray]. </description> </method> <method name="instance_attach_skeleton"> @@ -1782,12 +1845,16 @@ <argument index="1" name="skeleton" type="RID"> </argument> <description> + Attaches a skeleton to an instance. Removes the previous skeleton from the instance. </description> </method> <method name="instance_create"> <return type="RID"> </return> <description> + Creates a visual instance and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]instance_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + An instance is a way of placing a 3D object in the scenario. Objects like particles, meshes, and reflection probes need to be associated with an instance to be visible in the scenario using [method instance_set_base]. </description> </method> <method name="instance_create2"> @@ -1798,6 +1865,8 @@ <argument index="1" name="scenario" type="RID"> </argument> <description> + Creates a visual instance, adds it to the VisualServer, and sets both base and scenario. It can be accessed with the RID that is returned. This RID will be used in all [code]instance_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="instance_geometry_set_as_instance_lod"> @@ -1808,6 +1877,7 @@ <argument index="1" name="as_lod_of_instance" type="RID"> </argument> <description> + Not implemented in Godot 3.x. </description> </method> <method name="instance_geometry_set_cast_shadows_setting"> @@ -1818,6 +1888,7 @@ <argument index="1" name="shadow_casting_setting" type="int" enum="VisualServer.ShadowCastingSetting"> </argument> <description> + Sets the shadow casting setting to one of [enum ShadowCastingSetting]. Equivalent to [member GeometryInstance.cast_shadow]. </description> </method> <method name="instance_geometry_set_draw_range"> @@ -1834,6 +1905,7 @@ <argument index="4" name="max_margin" type="float"> </argument> <description> + Not implemented in Godot 3.x. </description> </method> <method name="instance_geometry_set_flag"> @@ -1846,6 +1918,7 @@ <argument index="2" name="enabled" type="bool"> </argument> <description> + Sets the flag for a given [enum InstanceFlags]. See [enum InstanceFlags] for more details. </description> </method> <method name="instance_geometry_set_material_override"> @@ -1856,6 +1929,7 @@ <argument index="1" name="material" type="RID"> </argument> <description> + Sets a material that will override the material for all surfaces on the mesh associated with this instance. Equivalent to [member GeometryInstance.material_override]. </description> </method> <method name="instance_set_base"> @@ -1866,6 +1940,7 @@ <argument index="1" name="base" type="RID"> </argument> <description> + Sets the base of the instance. A base can be any of the 3D objects that are created in the VisualServer that can be displayed. For example, any of the light types, mesh, multimesh, immediate geometry, particle system, reflection probe, lightmap capture, and the GI probe are all types that can be set as the base of an instance in order to be displayed in the scenario. </description> </method> <method name="instance_set_blend_shape_weight"> @@ -1878,6 +1953,7 @@ <argument index="2" name="weight" type="float"> </argument> <description> + Sets the weight for a given blend shape associated with this instance. </description> </method> <method name="instance_set_custom_aabb"> @@ -1888,6 +1964,7 @@ <argument index="1" name="aabb" type="AABB"> </argument> <description> + Sets a custom AABB to use when culling objects from the view frustum. Equivalent to [method GeometryInstance.set_custom_aabb]. </description> </method> <method name="instance_set_exterior"> @@ -1898,6 +1975,7 @@ <argument index="1" name="enabled" type="bool"> </argument> <description> + Function not implemented in Godot 3.x. </description> </method> <method name="instance_set_extra_visibility_margin"> @@ -1908,6 +1986,7 @@ <argument index="1" name="margin" type="float"> </argument> <description> + Sets a margin to increase the size of the AABB when culling objects from the view frustum. This allows you avoid culling objects that fall outside the view frustum. Equivalent to [member GeometryInstance.extra_cull_margin]. </description> </method> <method name="instance_set_layer_mask"> @@ -1918,6 +1997,7 @@ <argument index="1" name="mask" type="int"> </argument> <description> + Sets the render layers that this instance will be drawn to. Equivalent to [member VisualInstance.layers]. </description> </method> <method name="instance_set_scenario"> @@ -1928,6 +2008,7 @@ <argument index="1" name="scenario" type="RID"> </argument> <description> + Sets the scenario that the instance is in. The scenario is the 3D world that the objects will be displayed in. </description> </method> <method name="instance_set_surface_material"> @@ -1940,6 +2021,7 @@ <argument index="2" name="material" type="RID"> </argument> <description> + Sets the material of a specific surface. Equivalent to [method MeshInstance.set_surface_material]. </description> </method> <method name="instance_set_transform"> @@ -1950,6 +2032,7 @@ <argument index="1" name="transform" type="Transform"> </argument> <description> + Sets the world space transform of the instance. Equivalent to [member Spatial.transform]. </description> </method> <method name="instance_set_use_lightmap"> @@ -1962,6 +2045,7 @@ <argument index="2" name="lightmap" type="RID"> </argument> <description> + Sets the lightmap to use with this instance. </description> </method> <method name="instance_set_visible"> @@ -1972,6 +2056,7 @@ <argument index="1" name="visible" type="bool"> </argument> <description> + Sets whether an instance is drawn or not. Equivalent to [member Spatial.visible]. </description> </method> <method name="instances_cull_aabb" qualifiers="const"> @@ -1982,6 +2067,8 @@ <argument index="1" name="scenario" type="RID"> </argument> <description> + Returns an array of object IDs intersecting with the provided AABB. Only visual 3D nodes are considered, such as [MeshInstance] or [DirectionalLight]. Use [method @GDScript.instance_from_id] to obtain the actual nodes. A scenario RID must be provided, which is available in the [World] you want to query. This forces an update for all resources queued to update. + [b]Warning:[/b] This function is primarily intended for editor usage. For in-game use cases, prefer physics collision. </description> </method> <method name="instances_cull_convex" qualifiers="const"> @@ -1992,6 +2079,8 @@ <argument index="1" name="scenario" type="RID"> </argument> <description> + Returns an array of object IDs intersecting with the provided convex shape. Only visual 3D nodes are considered, such as [MeshInstance] or [DirectionalLight]. Use [method @GDScript.instance_from_id] to obtain the actual nodes. A scenario RID must be provided, which is available in the [World] you want to query. This forces an update for all resources queued to update. + [b]Warning:[/b] This function is primarily intended for editor usage. For in-game use cases, prefer physics collision. </description> </method> <method name="instances_cull_ray" qualifiers="const"> @@ -2004,7 +2093,7 @@ <argument index="2" name="scenario" type="RID"> </argument> <description> - Returns an array of object IDs intersecting with the provided 3D ray. Only visual 3D nodes are considered, such as [MeshInstance] or [DirectionalLight]. Use [method @GDScript.instance_from_id] to obtain the actual nodes. A scenario RID must be provided, which is available in the [World] you want to query. + Returns an array of object IDs intersecting with the provided 3D ray. Only visual 3D nodes are considered, such as [MeshInstance] or [DirectionalLight]. Use [method @GDScript.instance_from_id] to obtain the actual nodes. A scenario RID must be provided, which is available in the [World] you want to query. This forces an update for all resources queued to update. [b]Warning:[/b] This function is primarily intended for editor usage. For in-game use cases, prefer physics collision. </description> </method> @@ -2016,6 +2105,7 @@ <argument index="1" name="enable" type="bool"> </argument> <description> + If [code]true[/code], this directional light will blend between shadow map splits resulting in a smoother transition between them. Equivalent to [member DirectionalLight.directional_shadow_blend_splits]. </description> </method> <method name="light_directional_set_shadow_depth_range_mode"> @@ -2026,6 +2116,7 @@ <argument index="1" name="range_mode" type="int" enum="VisualServer.LightDirectionalShadowDepthRangeMode"> </argument> <description> + Sets the shadow depth range mode for this directional light. Equivalent to [member DirectionalLight.directional_shadow_depth_range]. See [enum LightDirectionalShadowDepthRangeMode] for options. </description> </method> <method name="light_directional_set_shadow_mode"> @@ -2036,6 +2127,7 @@ <argument index="1" name="mode" type="int" enum="VisualServer.LightDirectionalShadowMode"> </argument> <description> + Sets the shadow mode for this directional light. Equivalent to [member DirectionalLight.directional_shadow_mode]. See [enum LightDirectionalShadowMode] for options. </description> </method> <method name="light_omni_set_shadow_detail"> @@ -2046,6 +2138,7 @@ <argument index="1" name="detail" type="int" enum="VisualServer.LightOmniShadowDetail"> </argument> <description> + Sets whether to use vertical or horizontal detail for this omni light. This can be used to alleviate artifacts in the shadow map. Equivalent to [member OmniLight.omni_shadow_detail]. </description> </method> <method name="light_omni_set_shadow_mode"> @@ -2056,6 +2149,7 @@ <argument index="1" name="mode" type="int" enum="VisualServer.LightOmniShadowMode"> </argument> <description> + Sets whether to use a dual paraboloid or a cubemap for the shadow map. Dual paraboloid is faster but may suffer from artifacts. Equivalent to [member OmniLight.omni_shadow_mode]. </description> </method> <method name="light_set_color"> @@ -2066,6 +2160,7 @@ <argument index="1" name="color" type="Color"> </argument> <description> + Sets the color of the light. Equivalent to [member Light.light_color]. </description> </method> <method name="light_set_cull_mask"> @@ -2076,6 +2171,7 @@ <argument index="1" name="mask" type="int"> </argument> <description> + Sets the cull mask for this Light. Lights only affect objects in the selected layers. Equivalent to [member Light.light_cull_mask]. </description> </method> <method name="light_set_negative"> @@ -2086,6 +2182,7 @@ <argument index="1" name="enable" type="bool"> </argument> <description> + If [code]true[/code], light will subtract light instead of adding light. Equivalent to [member Light.light_negative]. </description> </method> <method name="light_set_param"> @@ -2098,6 +2195,7 @@ <argument index="2" name="value" type="float"> </argument> <description> + Sets the specified light parameter. See [enum LightParam] for options. Equivalent to [method Light.set_param]. </description> </method> <method name="light_set_projector"> @@ -2108,6 +2206,7 @@ <argument index="1" name="texture" type="RID"> </argument> <description> + Not implemented in Godot 3.x. </description> </method> <method name="light_set_reverse_cull_face_mode"> @@ -2118,6 +2217,7 @@ <argument index="1" name="enabled" type="bool"> </argument> <description> + If [code]true[/code], reverses the backface culling of the mesh. This can be useful when you have a flat mesh that has a light behind it. If you need to cast a shadow on both sides of the mesh, set the mesh to use double sided shadows with [method instance_geometry_set_cast_shadows_setting]. Equivalent to [member Light.shadow_reverse_cull_face]. </description> </method> <method name="light_set_shadow"> @@ -2128,6 +2228,7 @@ <argument index="1" name="enabled" type="bool"> </argument> <description> + If [code]true[/code], light will cast shadows. Equivalent to [member Light.shadow_enabled]. </description> </method> <method name="light_set_shadow_color"> @@ -2138,6 +2239,7 @@ <argument index="1" name="color" type="Color"> </argument> <description> + Sets the color of the shadow cast by the light. Equivalent to [member Light.shadow_color]. </description> </method> <method name="light_set_use_gi"> @@ -2155,6 +2257,9 @@ <return type="RID"> </return> <description> + Creates a lightmap capture and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]lightmap_capture_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + To place in a scene, attach this lightmap capture to an instance using [method instance_set_base] using the returned RID. </description> </method> <method name="lightmap_capture_get_bounds" qualifiers="const"> @@ -2163,6 +2268,7 @@ <argument index="0" name="capture" type="RID"> </argument> <description> + Returns the size of the lightmap capture area. </description> </method> <method name="lightmap_capture_get_energy" qualifiers="const"> @@ -2171,6 +2277,7 @@ <argument index="0" name="capture" type="RID"> </argument> <description> + Returns the energy multiplier used by the lightmap capture. </description> </method> <method name="lightmap_capture_get_octree" qualifiers="const"> @@ -2179,6 +2286,7 @@ <argument index="0" name="capture" type="RID"> </argument> <description> + Returns the octree used by the lightmap capture. </description> </method> <method name="lightmap_capture_get_octree_cell_subdiv" qualifiers="const"> @@ -2187,6 +2295,7 @@ <argument index="0" name="capture" type="RID"> </argument> <description> + Returns the cell subdivision amount used by this lightmap capture's octree. </description> </method> <method name="lightmap_capture_get_octree_cell_transform" qualifiers="const"> @@ -2195,6 +2304,7 @@ <argument index="0" name="capture" type="RID"> </argument> <description> + Returns the cell transform for this lightmap capture's octree. </description> </method> <method name="lightmap_capture_set_bounds"> @@ -2205,6 +2315,7 @@ <argument index="1" name="bounds" type="AABB"> </argument> <description> + Sets the size of the area covered by the lightmap capture. Equivalent to [member BakedLightmapData.bounds]. </description> </method> <method name="lightmap_capture_set_energy"> @@ -2215,6 +2326,7 @@ <argument index="1" name="energy" type="float"> </argument> <description> + Sets the energy multiplier for this lightmap capture. Equivalent to [member BakedLightmapData.energy]. </description> </method> <method name="lightmap_capture_set_octree"> @@ -2225,6 +2337,7 @@ <argument index="1" name="octree" type="PoolByteArray"> </argument> <description> + Sets the octree to be used by this lightmap capture. This function is normally used by the [BakedLightmap] node. Equivalent to [member BakedLightmapData.octree]. </description> </method> <method name="lightmap_capture_set_octree_cell_subdiv"> @@ -2235,6 +2348,7 @@ <argument index="1" name="subdiv" type="int"> </argument> <description> + Sets the subdivision level of this lightmap capture's octree. Equivalent to [member BakedLightmapData.cell_subdiv]. </description> </method> <method name="lightmap_capture_set_octree_cell_transform"> @@ -2245,6 +2359,7 @@ <argument index="1" name="xform" type="Transform"> </argument> <description> + Sets the octree cell transform for this lightmap capture's octree. Equivalent to [member BakedLightmapData.cell_space_transform]. </description> </method> <method name="make_sphere_mesh"> @@ -2264,7 +2379,8 @@ <return type="RID"> </return> <description> - Returns an empty material. + Creates an empty material and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]material_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="material_get_param" qualifiers="const"> @@ -2286,6 +2402,7 @@ <argument index="1" name="parameter" type="String"> </argument> <description> + Returns the default value for the param if available. Otherwise returns an empty [Variant]. </description> </method> <method name="material_get_shader" qualifiers="const"> @@ -2384,7 +2501,9 @@ <return type="RID"> </return> <description> - Creates a new mesh. + Creates a new mesh and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]mesh_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + To place in a scene, attach this mesh to an instance using [method instance_set_base] using the returned RID. </description> </method> <method name="mesh_get_blend_shape_count" qualifiers="const"> @@ -2556,6 +2675,7 @@ <argument index="3" name="array_index" type="int"> </argument> <description> + Function is unused in Godot 3.x. </description> </method> <method name="mesh_surface_get_format_stride" qualifiers="const"> @@ -2568,6 +2688,7 @@ <argument index="2" name="index_len" type="int"> </argument> <description> + Function is unused in Godot 3.x. </description> </method> <method name="mesh_surface_get_index_array" qualifiers="const"> @@ -2639,6 +2760,7 @@ <argument index="3" name="data" type="PoolByteArray"> </argument> <description> + Updates a specific region of a vertex buffer for the specified surface. Warning: this function alters the vertex buffer directly with no safety mechanisms, you can easily corrupt your mesh. </description> </method> <method name="multimesh_allocate"> @@ -2655,14 +2777,16 @@ <argument index="4" name="custom_data_format" type="int" enum="VisualServer.MultimeshCustomDataFormat" default="0"> </argument> <description> + Allocates space for the multimesh data. Format parameters determine how the data will be stored by OpenGL. See [enum MultimeshTransformFormat], [enum MultimeshColorFormat], and [enum MultimeshCustomDataFormat] for usage. Equivalent to [member MultiMesh.instance_count]. </description> </method> <method name="multimesh_create"> <return type="RID"> </return> <description> - Creates a new multimesh on the VisualServer and returns an [RID] handle. + Creates a new multimesh on the VisualServer and returns an [RID] handle. This RID will be used in all [code]multimesh_*[/code] VisualServer functions. Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + To place in a scene, attach this multimesh to an instance using [method instance_set_base] using the returned RID. </description> </method> <method name="multimesh_get_aabb" qualifiers="const"> @@ -2671,6 +2795,7 @@ <argument index="0" name="multimesh" type="RID"> </argument> <description> + Calculates and returns the axis-aligned bounding box that encloses all instances within the multimesh. </description> </method> <method name="multimesh_get_instance_count" qualifiers="const"> @@ -2679,6 +2804,7 @@ <argument index="0" name="multimesh" type="RID"> </argument> <description> + Returns the number of instances allocated for this multimesh. </description> </method> <method name="multimesh_get_mesh" qualifiers="const"> @@ -2687,6 +2813,7 @@ <argument index="0" name="multimesh" type="RID"> </argument> <description> + Returns the RID of the mesh that will be used in drawing this multimesh. </description> </method> <method name="multimesh_get_visible_instances" qualifiers="const"> @@ -2695,6 +2822,7 @@ <argument index="0" name="multimesh" type="RID"> </argument> <description> + Returns the number of visible instances for this multimesh. </description> </method> <method name="multimesh_instance_get_color" qualifiers="const"> @@ -2705,6 +2833,7 @@ <argument index="1" name="index" type="int"> </argument> <description> + Returns the color by which the specified instance will be modulated. </description> </method> <method name="multimesh_instance_get_custom_data" qualifiers="const"> @@ -2715,6 +2844,7 @@ <argument index="1" name="index" type="int"> </argument> <description> + Returns the custom data associated with the specified instance. </description> </method> <method name="multimesh_instance_get_transform" qualifiers="const"> @@ -2725,6 +2855,7 @@ <argument index="1" name="index" type="int"> </argument> <description> + Returns the [Transform] of the specified instance. </description> </method> <method name="multimesh_instance_get_transform_2d" qualifiers="const"> @@ -2735,6 +2866,7 @@ <argument index="1" name="index" type="int"> </argument> <description> + Returns the [Transform2D] of the specified instance. For use when the multimesh is set to use 2D transforms. </description> </method> <method name="multimesh_instance_set_color"> @@ -2747,6 +2879,7 @@ <argument index="2" name="color" type="Color"> </argument> <description> + Sets the color by which this instance will be modulated. Equivalent to [method MultiMesh.set_instance_color]. </description> </method> <method name="multimesh_instance_set_custom_data"> @@ -2759,6 +2892,7 @@ <argument index="2" name="custom_data" type="Color"> </argument> <description> + Sets the custom data for this instance. Custom data is passed as a [Color], but is interpreted as a [code]vec4[/code] in the shader. Equivalent to [method MultiMesh.set_instance_custom_data]. </description> </method> <method name="multimesh_instance_set_transform"> @@ -2771,6 +2905,7 @@ <argument index="2" name="transform" type="Transform"> </argument> <description> + Sets the [Transform] for this instance. Equivalent to [method MultiMesh.set_instance_transform]. </description> </method> <method name="multimesh_instance_set_transform_2d"> @@ -2783,6 +2918,7 @@ <argument index="2" name="transform" type="Transform2D"> </argument> <description> + Sets the [Transform2D] for this instance. For use when multimesh is used in 2D. Equivalent to [method MultiMesh.set_instance_transform_2d]. </description> </method> <method name="multimesh_set_as_bulk_array"> @@ -2793,6 +2929,11 @@ <argument index="1" name="array" type="PoolRealArray"> </argument> <description> + Sets all data related to the instances in one go. This is especially useful when loading the data from disk or preparing the data from GDNative. + + All data is packed in one large float array. An array may look like this: Transform for instance 1, color data for instance 1, custom data for instance 1, transform for instance 2, color data for instance 2, etc. + + [Transform] is stored as 12 floats, [Transform2D] is stored as 8 floats, [code]COLOR_8BIT[/code] / [code]CUSTOM_DATA_8BIT[/code] is stored as 1 float (4 bytes as is) and [code]COLOR_FLOAT[/code] / [code]CUSTOM_DATA_FLOAT[/code] is stored as 4 floats. </description> </method> <method name="multimesh_set_mesh"> @@ -2803,6 +2944,7 @@ <argument index="1" name="mesh" type="RID"> </argument> <description> + Sets the mesh to be drawn by the multimesh. Equivalent to [member MultiMesh.mesh]. </description> </method> <method name="multimesh_set_visible_instances"> @@ -2813,18 +2955,25 @@ <argument index="1" name="visible" type="int"> </argument> <description> + Sets the number of instances visible at a given time. If -1, all instances that have been allocated are drawn. Equivalent to [member MultiMesh.visible_instance_count]. </description> </method> <method name="omni_light_create"> <return type="RID"> </return> <description> + Creates a new omni light and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID can be used in most [code]light_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + To place in a scene, attach this omni light to an instance using [method instance_set_base] using the returned RID. </description> </method> <method name="particles_create"> <return type="RID"> </return> <description> + Creates a particle system and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]particles_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + To place in a scene, attach these particles to an instance using [method instance_set_base] using the returned RID. </description> </method> <method name="particles_get_current_aabb"> @@ -2833,6 +2982,7 @@ <argument index="0" name="particles" type="RID"> </argument> <description> + Calculates and returns the axis-aligned bounding box that contains all the particles. Equivalent to [method Particles.capture_aabb]. </description> </method> <method name="particles_get_emitting"> @@ -2841,6 +2991,7 @@ <argument index="0" name="particles" type="RID"> </argument> <description> + Returns [code]true[/code] if particles are currently set to emitting. </description> </method> <method name="particles_is_inactive"> @@ -2849,6 +3000,7 @@ <argument index="0" name="particles" type="RID"> </argument> <description> + Returns [code]true[/code] if particles are not emitting and particles are set to inactive. </description> </method> <method name="particles_request_process"> @@ -2857,6 +3009,7 @@ <argument index="0" name="particles" type="RID"> </argument> <description> + Add particle system to list of particle systems that need to be updated. Update will take place on the next frame, or on the next call to [method instances_cull_aabb], [method instances_cull_convex], or [method instances_cull_ray]. </description> </method> <method name="particles_restart"> @@ -2865,6 +3018,7 @@ <argument index="0" name="particles" type="RID"> </argument> <description> + Reset the particles on the next update. Equivalent to [method Particles.restart] </description> </method> <method name="particles_set_amount"> @@ -2875,6 +3029,7 @@ <argument index="1" name="amount" type="int"> </argument> <description> + Sets the number of particles to be drawn and allocates the memory for them. Equivalent to [member Particles.amount]. </description> </method> <method name="particles_set_custom_aabb"> @@ -2885,6 +3040,7 @@ <argument index="1" name="aabb" type="AABB"> </argument> <description> + Sets a custom axis-aligned bounding box for the particle system. Equivalent to [member Particles.visibility_aabb]. </description> </method> <method name="particles_set_draw_order"> @@ -2895,6 +3051,7 @@ <argument index="1" name="order" type="int" enum="VisualServer.ParticlesDrawOrder"> </argument> <description> + Sets the draw order of the particles to one of the named enums from [enum ParticlesDrawOrder]. See [enum ParticlesDrawOrder] for options. Equivalent to [member Particles.draw_order]. </description> </method> <method name="particles_set_draw_pass_mesh"> @@ -2907,6 +3064,7 @@ <argument index="2" name="mesh" type="RID"> </argument> <description> + Sets the mesh to be used for the specified draw pass. Equivalent to [member Particles.draw_pass_1], [member Particles.draw_pass_2], [member Particles.draw_pass_3], and [member Particles.draw_pass_4]. </description> </method> <method name="particles_set_draw_passes"> @@ -2917,6 +3075,7 @@ <argument index="1" name="count" type="int"> </argument> <description> + Sets the number of draw passes to use. Equivalent to [member Particles.draw_passes]. </description> </method> <method name="particles_set_emission_transform"> @@ -2927,6 +3086,7 @@ <argument index="1" name="transform" type="Transform"> </argument> <description> + Sets the [Transform] that will be used by the particles when they first emit. </description> </method> <method name="particles_set_emitting"> @@ -2937,6 +3097,7 @@ <argument index="1" name="emitting" type="bool"> </argument> <description> + If [code]true[/code], particles will emit over time. Setting to false does not reset the particles, but only stops their emission. Equivalent to [member Particles.emitting]. </description> </method> <method name="particles_set_explosiveness_ratio"> @@ -2947,6 +3108,7 @@ <argument index="1" name="ratio" type="float"> </argument> <description> + Sets the explosiveness ratio. Equivalent to [member Particles.explosiveness]. </description> </method> <method name="particles_set_fixed_fps"> @@ -2957,6 +3119,7 @@ <argument index="1" name="fps" type="int"> </argument> <description> + Sets the frame rate that the particle system rendering will be fixed to. Equivalent to [member Particles.fixed_fps]. </description> </method> <method name="particles_set_fractional_delta"> @@ -2967,6 +3130,7 @@ <argument index="1" name="enable" type="bool"> </argument> <description> + If [code]true[/code], uses fractional delta which smooths the movement of the particles. Equivalent to [member Particles.fract_delta]. </description> </method> <method name="particles_set_lifetime"> @@ -2977,6 +3141,7 @@ <argument index="1" name="lifetime" type="float"> </argument> <description> + Sets the lifetime of each particle in the system. Equivalent to [member Particles.lifetime]. </description> </method> <method name="particles_set_one_shot"> @@ -2987,6 +3152,7 @@ <argument index="1" name="one_shot" type="bool"> </argument> <description> + If [code]true[/code], particles will emit once and then stop. Equivalent to [member Particles.one_shot]. </description> </method> <method name="particles_set_pre_process_time"> @@ -2997,6 +3163,7 @@ <argument index="1" name="time" type="float"> </argument> <description> + Sets the preprocess time for the particles animation. This lets you delay starting an animation until after the particles have begun emitting. Equivalent to [member Particles.preprocess]. </description> </method> <method name="particles_set_process_material"> @@ -3007,6 +3174,7 @@ <argument index="1" name="material" type="RID"> </argument> <description> + Sets the material for processing the particles. Note: this is not the material used to draw the materials. Equivalent to [member Particles.process_material]. </description> </method> <method name="particles_set_randomness_ratio"> @@ -3017,6 +3185,7 @@ <argument index="1" name="ratio" type="float"> </argument> <description> + Sets the emission randomness ratio. This randomizes the emission of particles within their phase. Equivalent to [member Particles.randomness]. </description> </method> <method name="particles_set_speed_scale"> @@ -3027,6 +3196,7 @@ <argument index="1" name="scale" type="float"> </argument> <description> + Sets the speed scale of the particle system. Equivalent to [member Particles.speed_scale]. </description> </method> <method name="particles_set_use_local_coordinates"> @@ -3037,12 +3207,16 @@ <argument index="1" name="enable" type="bool"> </argument> <description> + If [code]true[/code], particles use local coordinates. If [code]false[/code] they use global coordinates. Equivalent to [member Particles.local_coords]. </description> </method> <method name="reflection_probe_create"> <return type="RID"> </return> <description> + Creates a reflection probe and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]reflection_probe_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + To place in a scene, attach this reflection probe to an instance using [method instance_set_base] using the returned RID. </description> </method> <method name="reflection_probe_set_as_interior"> @@ -3053,6 +3227,7 @@ <argument index="1" name="enable" type="bool"> </argument> <description> + If [code]true[/code], reflections will ignore sky contribution. Equivalent to [member ReflectionProbe.interior_enable]. </description> </method> <method name="reflection_probe_set_cull_mask"> @@ -3063,6 +3238,7 @@ <argument index="1" name="layers" type="int"> </argument> <description> + Sets the render cull mask for this reflection probe. Only instances with a matching cull mask will be rendered by this probe. Equivalent to [member ReflectionProbe.cull_mask]. </description> </method> <method name="reflection_probe_set_enable_box_projection"> @@ -3073,6 +3249,7 @@ <argument index="1" name="enable" type="bool"> </argument> <description> + If [code]true[/code], uses box projection. This can make reflections look more correct in certain situations. Equivalent to [member ReflectionProbe.box_projection]. </description> </method> <method name="reflection_probe_set_enable_shadows"> @@ -3083,6 +3260,7 @@ <argument index="1" name="enable" type="bool"> </argument> <description> + If [code]true[/code], computes shadows in the reflection probe. This makes the reflection much slower to compute. Equivalent to [member ReflectionProbe.enable_shadows]. </description> </method> <method name="reflection_probe_set_extents"> @@ -3093,6 +3271,7 @@ <argument index="1" name="extents" type="Vector3"> </argument> <description> + Sets the size of the area that the reflection probe will capture. Equivalent to [member ReflectionProbe.extents]. </description> </method> <method name="reflection_probe_set_intensity"> @@ -3103,6 +3282,7 @@ <argument index="1" name="intensity" type="float"> </argument> <description> + Sets the intensity of the reflection probe. Intensity modulates the strength of the reflection. Equivalent to [member ReflectionProbe.intensity]. </description> </method> <method name="reflection_probe_set_interior_ambient"> @@ -3113,6 +3293,7 @@ <argument index="1" name="color" type="Color"> </argument> <description> + Sets the ambient light color for this reflection probe when set to interior mode. Equivalent to [member ReflectionProbe.interior_ambient_color]. </description> </method> <method name="reflection_probe_set_interior_ambient_energy"> @@ -3123,6 +3304,7 @@ <argument index="1" name="energy" type="float"> </argument> <description> + Sets the energy multiplier for this reflection probes ambient light contribution when set to interior mode. Equivalent to [member ReflectionProbe.interior_ambient_energy]. </description> </method> <method name="reflection_probe_set_interior_ambient_probe_contribution"> @@ -3133,6 +3315,7 @@ <argument index="1" name="contrib" type="float"> </argument> <description> + Sets the contribution value for how much the reflection affects the ambient light for this reflection probe when set to interior mode. Useful so that ambient light matches the color of the room. Equivalent to [member ReflectionProbe.interior_ambient_contrib]. </description> </method> <method name="reflection_probe_set_max_distance"> @@ -3143,6 +3326,7 @@ <argument index="1" name="distance" type="float"> </argument> <description> + Sets the max distance away from the probe an object can be before it is culled. Equivalent to [member ReflectionProbe.max_distance]. </description> </method> <method name="reflection_probe_set_origin_offset"> @@ -3153,6 +3337,7 @@ <argument index="1" name="offset" type="Vector3"> </argument> <description> + Sets the origin offset to be used when this reflection probe is in box project mode. Equivalent to [member ReflectionProbe.origin_offset]. </description> </method> <method name="reflection_probe_set_update_mode"> @@ -3163,6 +3348,7 @@ <argument index="1" name="mode" type="int" enum="VisualServer.ReflectionProbeUpdateMode"> </argument> <description> + Sets how often the reflection probe updates. Can either be once or every frame. See [enum ReflectionProbeUpdateMode] for options. </description> </method> <method name="request_frame_drawn_callback"> @@ -3183,6 +3369,9 @@ <return type="RID"> </return> <description> + Creates a scenario and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]scenario_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + The scenario is the 3D world that all the visual instances exist in. </description> </method> <method name="scenario_set_debug"> @@ -3193,6 +3382,7 @@ <argument index="1" name="debug_mode" type="int" enum="VisualServer.ScenarioDebugMode"> </argument> <description> + Sets the [enum ScenarioDebugMode] for this scenario. See [enum ScenarioDebugMode] for options. </description> </method> <method name="scenario_set_environment"> @@ -3203,6 +3393,7 @@ <argument index="1" name="environment" type="RID"> </argument> <description> + Sets the environment that will be used with this scenario. </description> </method> <method name="scenario_set_fallback_environment"> @@ -3213,6 +3404,7 @@ <argument index="1" name="environment" type="RID"> </argument> <description> + Sets the fallback environment to be used by this scenario. The fallback environment is used if no environment is set. Internally, this is used by the editor to provide a default environment. </description> </method> <method name="scenario_set_reflection_atlas_size"> @@ -3225,6 +3417,7 @@ <argument index="2" name="subdiv" type="int"> </argument> <description> + Sets the size of the reflection atlas shared by all reflection probes in this scenario. </description> </method> <method name="set_boot_image"> @@ -3248,6 +3441,7 @@ <argument index="0" name="generate" type="bool"> </argument> <description> + If [code]true[/code], the engine will generate wireframes for use with the wireframe debug mode. </description> </method> <method name="set_default_clear_color"> @@ -3256,13 +3450,15 @@ <argument index="0" name="color" type="Color"> </argument> <description> + Sets the default clear color which is used when a specific clear color has not been selected. </description> </method> <method name="shader_create"> <return type="RID"> </return> <description> - Creates an empty shader. + Creates an empty shader and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]shader_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="shader_get_code" qualifiers="const"> @@ -3328,6 +3524,7 @@ <argument index="2" name="is_2d_skeleton" type="bool" default="false"> </argument> <description> + Allocates the GPU buffers for this skeleton. </description> </method> <method name="skeleton_bone_get_transform" qualifiers="const"> @@ -3338,6 +3535,7 @@ <argument index="1" name="bone" type="int"> </argument> <description> + Returns the [Transform] set for a specific bone of this skeleton. </description> </method> <method name="skeleton_bone_get_transform_2d" qualifiers="const"> @@ -3348,6 +3546,7 @@ <argument index="1" name="bone" type="int"> </argument> <description> + Returns the [Transform2D] set for a specific bone of this skeleton. </description> </method> <method name="skeleton_bone_set_transform"> @@ -3360,6 +3559,7 @@ <argument index="2" name="transform" type="Transform"> </argument> <description> + Sets the [Transform] for a specific bone of this skeleton. </description> </method> <method name="skeleton_bone_set_transform_2d"> @@ -3372,12 +3572,15 @@ <argument index="2" name="transform" type="Transform2D"> </argument> <description> + Sets the [Transform2D] for a specific bone of this skeleton. </description> </method> <method name="skeleton_create"> <return type="RID"> </return> <description> + Creates a skeleton and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]skeleton_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="skeleton_get_bone_count" qualifiers="const"> @@ -3386,13 +3589,15 @@ <argument index="0" name="skeleton" type="RID"> </argument> <description> + Returns the number of bones allocated for this skeleton. </description> </method> <method name="sky_create"> <return type="RID"> </return> <description> - Creates an empty sky. + Creates an empty sky and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]sky_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="sky_set_texture"> @@ -3412,12 +3617,16 @@ <return type="RID"> </return> <description> + Creates a spot light and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID can be used in most [code]light_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + To place in a scene, attach this spot light to an instance using [method instance_set_base] using the returned RID. </description> </method> <method name="sync"> <return type="void"> </return> <description> + Not implemented in Godot 3.x. </description> </method> <method name="texture_allocate"> @@ -3438,6 +3647,7 @@ <argument index="6" name="flags" type="int" default="7"> </argument> <description> + Allocates the GPU memory for the texture. </description> </method> <method name="texture_bind"> @@ -3455,7 +3665,8 @@ <return type="RID"> </return> <description> - Creates an empty texture. + Creates an empty texture and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]texture_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="texture_create_from_image"> @@ -3493,6 +3704,7 @@ <argument index="0" name="texture" type="RID"> </argument> <description> + Returns the depth of the texture. </description> </method> <method name="texture_get_flags" qualifiers="const"> @@ -3546,6 +3758,7 @@ <argument index="0" name="texture" type="RID"> </argument> <description> + Returns the type of the texture, can be any of the [enum TextureType]. </description> </method> <method name="texture_get_width" qualifiers="const"> @@ -3594,6 +3807,7 @@ <argument index="9" name="layer" type="int" default="0"> </argument> <description> + Sets a part of the data for a texture. Warning: this function calls the underlying graphics API directly and may corrupt your texture if used improperly. </description> </method> <method name="texture_set_flags"> @@ -3639,6 +3853,7 @@ <argument index="3" name="depth" type="int"> </argument> <description> + Resizes the texture to the specified dimensions. </description> </method> <method name="textures_keep_original"> @@ -3696,7 +3911,8 @@ <return type="RID"> </return> <description> - Creates an empty viewport. + Creates an empty viewport and adds it to the VisualServer. It can be accessed with the RID that is returned. This RID will be used in all [code]viewport_*[/code] VisualServer functions. + Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. </description> </method> <method name="viewport_detach"> @@ -3864,6 +4080,7 @@ <argument index="1" name="hidden" type="bool"> </argument> <description> + Currently unimplemented in Godot 3.x. </description> </method> <method name="viewport_set_msaa"> @@ -4021,6 +4238,7 @@ Marks an error that shows that the index array is empty. </constant> <constant name="ARRAY_WEIGHTS_SIZE" value="4"> + Number of weights/bones per vertex. </constant> <constant name="CANVAS_ITEM_Z_MIN" value="-4096"> The minimum Z-layer for canvas items. @@ -4029,8 +4247,10 @@ The maximum Z-layer for canvas items. </constant> <constant name="MAX_GLOW_LEVELS" value="7"> + Max number of glow levels that can be used with glow post-process effect. </constant> <constant name="MAX_CURSORS" value="8"> + Unused enum in Godot 3.x. </constant> <constant name="MATERIAL_RENDER_PRIORITY_MIN" value="-128"> The minimum renderpriority of all materials. @@ -4057,12 +4277,16 @@ Marks the back side of a cubemap. </constant> <constant name="TEXTURE_TYPE_2D" value="0" enum="TextureType"> + Normal texture with 2 dimensions, width and height. </constant> <constant name="TEXTURE_TYPE_CUBEMAP" value="1" enum="TextureType"> + Texture made up of six faces, can be looked up with a [code]vec3[/code] in shader. </constant> <constant name="TEXTURE_TYPE_2D_ARRAY" value="2" enum="TextureType"> + An array of 2-dimensional textures. </constant> <constant name="TEXTURE_TYPE_3D" value="3" enum="TextureType"> + A 3-dimensional texture with width, height, and depth. </constant> <constant name="TEXTURE_FLAG_MIPMAPS" value="1" enum="TextureFlags"> Generates mipmaps, which are smaller versions of the same texture to use when zoomed out, keeping the aspect ratio. @@ -4177,11 +4401,13 @@ Flag used to mark a compressed (half float) UV coordinates array for the second UV coordinates. </constant> <constant name="ARRAY_COMPRESS_BONES" value="32768" enum="ArrayFormat"> + Flag used to mark a compressed bone array. </constant> <constant name="ARRAY_COMPRESS_WEIGHTS" value="65536" enum="ArrayFormat"> Flag used to mark a compressed (half float) weight array. </constant> <constant name="ARRAY_COMPRESS_INDEX" value="131072" enum="ArrayFormat"> + Flag used to mark a compressed index array. </constant> <constant name="ARRAY_FLAG_USE_2D_VERTICES" value="262144" enum="ArrayFormat"> Flag used to mark that the array contains 2D vertices. @@ -4217,8 +4443,10 @@ Represents the size of the [enum PrimitiveType] enum. </constant> <constant name="BLEND_SHAPE_MODE_NORMALIZED" value="0" enum="BlendShapeMode"> + Blend shapes are normalized. </constant> <constant name="BLEND_SHAPE_MODE_RELATIVE" value="1" enum="BlendShapeMode"> + Blend shapes are relative to base weight. </constant> <constant name="LIGHT_DIRECTIONAL" value="0" enum="LightType"> Is a directional (sun) light. @@ -4251,47 +4479,67 @@ Scales the shadow color. </constant> <constant name="LIGHT_PARAM_SHADOW_MAX_DISTANCE" value="8" enum="LightParam"> + Max distance that shadows will be rendered. </constant> <constant name="LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET" value="9" enum="LightParam"> + Proportion of shadow atlas occupied by the first split. </constant> <constant name="LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET" value="10" enum="LightParam"> + Proportion of shadow atlas occupied by the second split. </constant> <constant name="LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET" value="11" enum="LightParam"> + Proportion of shadow atlas occupied by the third split. The fourth split occupies the rest. </constant> <constant name="LIGHT_PARAM_SHADOW_NORMAL_BIAS" value="12" enum="LightParam"> + Normal bias used to offset shadow lookup by object normal. Can be used to fix self-shadowing artifacts. </constant> <constant name="LIGHT_PARAM_SHADOW_BIAS" value="13" enum="LightParam"> + Bias the shadow lookup to fix self-shadowing artifacts. </constant> <constant name="LIGHT_PARAM_SHADOW_BIAS_SPLIT_SCALE" value="14" enum="LightParam"> + Increases bias on further splits to fix self-shadowing that only occurs far away from the camera. </constant> <constant name="LIGHT_PARAM_MAX" value="15" enum="LightParam"> Represents the size of the [enum LightParam] enum. </constant> <constant name="LIGHT_OMNI_SHADOW_DUAL_PARABOLOID" value="0" enum="LightOmniShadowMode"> + Use a dual paraboloid shadow map for omni lights. </constant> <constant name="LIGHT_OMNI_SHADOW_CUBE" value="1" enum="LightOmniShadowMode"> + Use a cubemap shadow map for omni lights. Slower but better quality than dual paraboloid. </constant> <constant name="LIGHT_OMNI_SHADOW_DETAIL_VERTICAL" value="0" enum="LightOmniShadowDetail"> + Use more detail vertically when computing shadow map. </constant> <constant name="LIGHT_OMNI_SHADOW_DETAIL_HORIZONTAL" value="1" enum="LightOmniShadowDetail"> + Use more detail horizontally when computing shadow map. </constant> <constant name="LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL" value="0" enum="LightDirectionalShadowMode"> + Use orthogonal shadow projection for directional light. </constant> <constant name="LIGHT_DIRECTIONAL_SHADOW_PARALLEL_2_SPLITS" value="1" enum="LightDirectionalShadowMode"> + Use 2 splits for shadow projection when using directional light. </constant> <constant name="LIGHT_DIRECTIONAL_SHADOW_PARALLEL_4_SPLITS" value="2" enum="LightDirectionalShadowMode"> + Use 4 splits for shadow projection when using directional light. </constant> <constant name="LIGHT_DIRECTIONAL_SHADOW_DEPTH_RANGE_STABLE" value="0" enum="LightDirectionalShadowDepthRangeMode"> + Keeps shadows stable as camera moves but has lower effective resolution. </constant> <constant name="LIGHT_DIRECTIONAL_SHADOW_DEPTH_RANGE_OPTIMIZED" value="1" enum="LightDirectionalShadowDepthRangeMode"> + Optimize use of shadow maps, increasing the effective resolution. But may result in shadows moving or flickering slightly. </constant> <constant name="VIEWPORT_UPDATE_DISABLED" value="0" enum="ViewportUpdateMode"> + Do not update the viewport. </constant> <constant name="VIEWPORT_UPDATE_ONCE" value="1" enum="ViewportUpdateMode"> + Update the viewport once then set to disabled. </constant> <constant name="VIEWPORT_UPDATE_WHEN_VISIBLE" value="2" enum="ViewportUpdateMode"> + Update the viewport whenever it is visible. </constant> <constant name="VIEWPORT_UPDATE_ALWAYS" value="3" enum="ViewportUpdateMode"> + Always update the viewport. </constant> <constant name="VIEWPORT_CLEAR_ALWAYS" value="0" enum="ViewportClearMode"> The viewport is always cleared before drawing. @@ -4336,16 +4584,22 @@ The Viewport renders 3D but without effects. </constant> <constant name="VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME" value="0" enum="ViewportRenderInfo"> + Number of objects drawn in a single frame. </constant> <constant name="VIEWPORT_RENDER_INFO_VERTICES_IN_FRAME" value="1" enum="ViewportRenderInfo"> + Number of vertices drawn in a single frame. </constant> <constant name="VIEWPORT_RENDER_INFO_MATERIAL_CHANGES_IN_FRAME" value="2" enum="ViewportRenderInfo"> + Number of material changes during this frame. </constant> <constant name="VIEWPORT_RENDER_INFO_SHADER_CHANGES_IN_FRAME" value="3" enum="ViewportRenderInfo"> + Number of shader changes during this frame. </constant> <constant name="VIEWPORT_RENDER_INFO_SURFACE_CHANGES_IN_FRAME" value="4" enum="ViewportRenderInfo"> + Number of surface changes during this frame. </constant> <constant name="VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME" value="5" enum="ViewportRenderInfo"> + Number of draw calls during this frame. </constant> <constant name="VIEWPORT_RENDER_INFO_MAX" value="6" enum="ViewportRenderInfo"> Represents the size of the [enum ViewportRenderInfo] enum. @@ -4363,12 +4617,16 @@ Debug draw draws objects in wireframe. </constant> <constant name="SCENARIO_DEBUG_DISABLED" value="0" enum="ScenarioDebugMode"> + Do not use a debug mode. </constant> <constant name="SCENARIO_DEBUG_WIREFRAME" value="1" enum="ScenarioDebugMode"> + Draw all objects as wireframe models. </constant> <constant name="SCENARIO_DEBUG_OVERDRAW" value="2" enum="ScenarioDebugMode"> + Draw all objects in a way that displays how much overdraw is occurring. Overdraw occurs when a section of pixels is drawn and shaded and then another object covers it up. To optimize a scene, you should reduce overdraw. </constant> <constant name="SCENARIO_DEBUG_SHADELESS" value="3" enum="ScenarioDebugMode"> + Draw all objects without shading. Equivalent to setting all objects shaders to [code]unshaded[/code]. </constant> <constant name="INSTANCE_NONE" value="0" enum="InstanceType"> The instance does not have a type. @@ -4389,10 +4647,13 @@ The instance is a light. </constant> <constant name="INSTANCE_REFLECTION_PROBE" value="6" enum="InstanceType"> + The instance is a reflection probe. </constant> <constant name="INSTANCE_GI_PROBE" value="7" enum="InstanceType"> + The instance is a GI probe. </constant> <constant name="INSTANCE_LIGHTMAP_CAPTURE" value="8" enum="InstanceType"> + The instance is a lightmap capture. </constant> <constant name="INSTANCE_MAX" value="9" enum="InstanceType"> Represents the size of the [enum InstanceType] enum. @@ -4401,19 +4662,25 @@ A combination of the flags of geometry instances (mesh, multimesh, immediate and particles). </constant> <constant name="INSTANCE_FLAG_USE_BAKED_LIGHT" value="0" enum="InstanceFlags"> + Allows the instance to be used in baked lighting. </constant> <constant name="INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE" value="1" enum="InstanceFlags"> + When set, manually requests to draw geometry on next frame. </constant> <constant name="INSTANCE_FLAG_MAX" value="2" enum="InstanceFlags"> Represents the size of the [enum InstanceFlags] enum. </constant> <constant name="SHADOW_CASTING_SETTING_OFF" value="0" enum="ShadowCastingSetting"> + Disable shadows from this instance. </constant> <constant name="SHADOW_CASTING_SETTING_ON" value="1" enum="ShadowCastingSetting"> + Cast shadows from this instance. </constant> <constant name="SHADOW_CASTING_SETTING_DOUBLE_SIDED" value="2" enum="ShadowCastingSetting"> + Disable backface culling when rendering the shadow of the object. This is slightly slower but may result in more correct shadows. </constant> <constant name="SHADOW_CASTING_SETTING_SHADOWS_ONLY" value="3" enum="ShadowCastingSetting"> + Only render the shadows from the object. The object itself will not be drawn. </constant> <constant name="NINE_PATCH_STRETCH" value="0" enum="NinePatchAxisMode"> The nine patch gets stretched where needed. @@ -4437,16 +4704,22 @@ The light adds color depending on mask. </constant> <constant name="CANVAS_LIGHT_FILTER_NONE" value="0" enum="CanvasLightShadowFilter"> + Do not apply a filter to canvas light shadows. </constant> <constant name="CANVAS_LIGHT_FILTER_PCF3" value="1" enum="CanvasLightShadowFilter"> + Use PCF3 filtering to filter canvas light shadows. </constant> <constant name="CANVAS_LIGHT_FILTER_PCF5" value="2" enum="CanvasLightShadowFilter"> + Use PCF5 filtering to filter canvas light shadows. </constant> <constant name="CANVAS_LIGHT_FILTER_PCF7" value="3" enum="CanvasLightShadowFilter"> + Use PCF7 filtering to filter canvas light shadows. </constant> <constant name="CANVAS_LIGHT_FILTER_PCF9" value="4" enum="CanvasLightShadowFilter"> + Use PCF9 filtering to filter canvas light shadows. </constant> <constant name="CANVAS_LIGHT_FILTER_PCF13" value="5" enum="CanvasLightShadowFilter"> + Use PCF13 filtering to filter canvas light shadows. </constant> <constant name="CANVAS_OCCLUDER_POLYGON_CULL_DISABLED" value="0" enum="CanvasOccluderPolygonCullMode"> Culling of the canvas occluder is disabled. @@ -4488,85 +4761,124 @@ The amount of vertex memory used. </constant> <constant name="FEATURE_SHADERS" value="0" enum="Features"> + Hardware supports shaders. This enum is currently unused in Godot 3.x. </constant> <constant name="FEATURE_MULTITHREADED" value="1" enum="Features"> + Hardware supports multithreading. This enum is currently unused in Godot 3.x. </constant> <constant name="MULTIMESH_TRANSFORM_2D" value="0" enum="MultimeshTransformFormat"> + Use [Transform2D] to store MultiMesh transform. </constant> <constant name="MULTIMESH_TRANSFORM_3D" value="1" enum="MultimeshTransformFormat"> + Use [Transform] to store MultiMesh transform. </constant> <constant name="MULTIMESH_COLOR_NONE" value="0" enum="MultimeshColorFormat"> + MultiMesh does not use per-instance color. </constant> <constant name="MULTIMESH_COLOR_8BIT" value="1" enum="MultimeshColorFormat"> + MultiMesh color uses 8 bits per component. This packs the color into a single float. </constant> <constant name="MULTIMESH_COLOR_FLOAT" value="2" enum="MultimeshColorFormat"> + MultiMesh color uses a float per channel. </constant> <constant name="MULTIMESH_CUSTOM_DATA_NONE" value="0" enum="MultimeshCustomDataFormat"> + MultiMesh does not use custom data. </constant> <constant name="MULTIMESH_CUSTOM_DATA_8BIT" value="1" enum="MultimeshCustomDataFormat"> + MultiMesh custom data uses 8 bits per component. This packs the 4-component custom data into a single float. </constant> <constant name="MULTIMESH_CUSTOM_DATA_FLOAT" value="2" enum="MultimeshCustomDataFormat"> + MultiMesh custom data uses a float per component. </constant> <constant name="REFLECTION_PROBE_UPDATE_ONCE" value="0" enum="ReflectionProbeUpdateMode"> + Reflection probe will update reflections once and then stop. </constant> <constant name="REFLECTION_PROBE_UPDATE_ALWAYS" value="1" enum="ReflectionProbeUpdateMode"> + Reflection probe will update each frame. This mode is necessary to capture moving objects. </constant> <constant name="PARTICLES_DRAW_ORDER_INDEX" value="0" enum="ParticlesDrawOrder"> + Draw particles in the order that they appear in the particles array. </constant> <constant name="PARTICLES_DRAW_ORDER_LIFETIME" value="1" enum="ParticlesDrawOrder"> + Sort particles based on their lifetime. </constant> <constant name="PARTICLES_DRAW_ORDER_VIEW_DEPTH" value="2" enum="ParticlesDrawOrder"> + Sort particles based on their distance to the camera. </constant> <constant name="ENV_BG_CLEAR_COLOR" value="0" enum="EnvironmentBG"> + Use the clear color as background. </constant> <constant name="ENV_BG_COLOR" value="1" enum="EnvironmentBG"> + Use a specified color as the background. </constant> <constant name="ENV_BG_SKY" value="2" enum="EnvironmentBG"> + Use a sky resource for the background. </constant> <constant name="ENV_BG_COLOR_SKY" value="3" enum="EnvironmentBG"> + Use a custom color for background, but use a sky for shading and reflections. </constant> <constant name="ENV_BG_CANVAS" value="4" enum="EnvironmentBG"> + Use a specified canvas layer as the background. This can be useful for instantiating a 2D scene in a 3D world. </constant> <constant name="ENV_BG_KEEP" value="5" enum="EnvironmentBG"> + Do not clear the background, use whatever was rendered last frame as the background. </constant> <constant name="ENV_BG_MAX" value="7" enum="EnvironmentBG"> Represents the size of the [enum EnvironmentBG] enum. </constant> <constant name="ENV_DOF_BLUR_QUALITY_LOW" value="0" enum="EnvironmentDOFBlurQuality"> + Use lowest blur quality. Fastest, but may look bad. </constant> <constant name="ENV_DOF_BLUR_QUALITY_MEDIUM" value="1" enum="EnvironmentDOFBlurQuality"> + Use medium blur quality. </constant> <constant name="ENV_DOF_BLUR_QUALITY_HIGH" value="2" enum="EnvironmentDOFBlurQuality"> + Used highest blur quality. Looks the best, but is the slowest. </constant> <constant name="GLOW_BLEND_MODE_ADDITIVE" value="0" enum="EnvironmentGlowBlendMode"> + Add the effect of the glow on top of the scene. </constant> <constant name="GLOW_BLEND_MODE_SCREEN" value="1" enum="EnvironmentGlowBlendMode"> + Blends the glow effect with the screen. Does not get as bright as additive. </constant> <constant name="GLOW_BLEND_MODE_SOFTLIGHT" value="2" enum="EnvironmentGlowBlendMode"> + Produces a subtle color disturbance around objects. </constant> <constant name="GLOW_BLEND_MODE_REPLACE" value="3" enum="EnvironmentGlowBlendMode"> + Shows the glow effect by itself without the underlying scene. </constant> <constant name="ENV_TONE_MAPPER_LINEAR" value="0" enum="EnvironmentToneMapper"> + Output color as they came in. </constant> <constant name="ENV_TONE_MAPPER_REINHARD" value="1" enum="EnvironmentToneMapper"> + Use the Reinhard tonemapper. </constant> <constant name="ENV_TONE_MAPPER_FILMIC" value="2" enum="EnvironmentToneMapper"> + Use the filmic tonemapper. </constant> <constant name="ENV_TONE_MAPPER_ACES" value="3" enum="EnvironmentToneMapper"> + Use the ACES tonemapper. </constant> <constant name="ENV_SSAO_QUALITY_LOW" value="0" enum="EnvironmentSSAOQuality"> + Lowest quality of screen space ambient occlusion. </constant> <constant name="ENV_SSAO_QUALITY_MEDIUM" value="1" enum="EnvironmentSSAOQuality"> + Medium quality screen space ambient occlusion. </constant> <constant name="ENV_SSAO_QUALITY_HIGH" value="2" enum="EnvironmentSSAOQuality"> + Highest quality screen space ambient occlusion. </constant> <constant name="ENV_SSAO_BLUR_DISABLED" value="0" enum="EnvironmentSSAOBlur"> + Disables the blur set for SSAO. Will make SSAO look noisier. </constant> <constant name="ENV_SSAO_BLUR_1x1" value="1" enum="EnvironmentSSAOBlur"> + Perform a 1x1 blur on the SSAO output. </constant> <constant name="ENV_SSAO_BLUR_2x2" value="2" enum="EnvironmentSSAOBlur"> + Performs a 2x2 blur on the SSAO output. </constant> <constant name="ENV_SSAO_BLUR_3x3" value="3" enum="EnvironmentSSAOBlur"> + Performs a 3x3 blur on the SSAO output. Use this for smoothest SSAO. </constant> </constants> </class> diff --git a/doc/classes/float.xml b/doc/classes/float.xml index 7164e8cb0a..1571bae847 100644 --- a/doc/classes/float.xml +++ b/doc/classes/float.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="float" category="Built-In Types" version="3.2"> <brief_description> - Float built-in type + Float built-in type. </brief_description> <description> Float built-in type. diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 167159c7e9..00758a73a4 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -742,6 +742,8 @@ public: int get_captured_render_info(VS::RenderInfo p_info) { return 0; } int get_render_info(VS::RenderInfo p_info) { return 0; } + String get_video_adapter_name() const { return String(); } + String get_video_adapter_vendor() const { return String(); } static RasterizerStorage *base_singleton; diff --git a/drivers/gles2/rasterizer_gles2.cpp b/drivers/gles2/rasterizer_gles2.cpp index cac124ff5f..c4e9541a36 100644 --- a/drivers/gles2/rasterizer_gles2.cpp +++ b/drivers/gles2/rasterizer_gles2.cpp @@ -263,8 +263,7 @@ void RasterizerGLES2::initialize() { #endif // GLES_OVER_GL #endif // CAN_DEBUG - const GLubyte *renderer = glGetString(GL_RENDERER); - print_line("OpenGL ES 2.0 Renderer: " + String((const char *)renderer)); + print_line("OpenGL ES 2.0 Renderer: " + VisualServer::get_singleton()->get_video_adapter_name()); storage->initialize(); canvas->initialize(); scene->initialize(); diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 657148cb40..bb087a7f47 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -2435,22 +2435,10 @@ void RasterizerStorageGLES2::mesh_add_surface(RID p_mesh, uint32_t p_format, VS: if (surface->blend_shape_data.size()) { ERR_PRINT_ONCE("Blend shapes are not supported in OpenGL ES 2.0"); } - surface->data = array; - surface->index_data = p_index_array; -#else - // Even on non-tools builds, a copy of the surface->data is needed in certain circumstances. - // Rigged meshes using the USE_SKELETON_SOFTWARE path need to read bone data - // from surface->data. - - // if USE_SKELETON_SOFTWARE is active - if (config.use_skeleton_software) { - // if this geometry is used specifically for skinning - if (p_format & (VS::ARRAY_FORMAT_BONES | VS::ARRAY_FORMAT_WEIGHTS)) - surface->data = array; - } - // An alternative is to always make a copy of surface->data. #endif + surface->data = array; + surface->index_data = p_index_array; surface->total_data_size += surface->array_byte_size + surface->index_array_byte_size; for (int i = 0; i < surface->skeleton_bone_used.size(); i++) { @@ -5778,6 +5766,16 @@ int RasterizerStorageGLES2::get_render_info(VS::RenderInfo p_info) { } } +String RasterizerStorageGLES2::get_video_adapter_name() const { + + return (const char *)glGetString(GL_RENDERER); +} + +String RasterizerStorageGLES2::get_video_adapter_vendor() const { + + return (const char *)glGetString(GL_VENDOR); +} + void RasterizerStorageGLES2::initialize() { RasterizerStorageGLES2::system_fbo = 0; diff --git a/drivers/gles2/rasterizer_storage_gles2.h b/drivers/gles2/rasterizer_storage_gles2.h index 62926ce713..83697b9872 100644 --- a/drivers/gles2/rasterizer_storage_gles2.h +++ b/drivers/gles2/rasterizer_storage_gles2.h @@ -1307,6 +1307,8 @@ public: virtual int get_captured_render_info(VS::RenderInfo p_info); virtual int get_render_info(VS::RenderInfo p_info); + virtual String get_video_adapter_name() const; + virtual String get_video_adapter_vendor() const; RasterizerStorageGLES2(); }; diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index d428fe33ee..ef2c318807 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -186,8 +186,7 @@ void RasterizerGLES3::initialize() { } */ - const GLubyte *renderer = glGetString(GL_RENDERER); - print_line("OpenGL ES 3.0 Renderer: " + String((const char *)renderer)); + print_line("OpenGL ES 3.0 Renderer: " + VisualServer::get_singleton()->get_video_adapter_name()); storage->initialize(); canvas->initialize(); scene->initialize(); diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index bf742f4d25..c02f17ab95 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -8095,6 +8095,16 @@ int RasterizerStorageGLES3::get_render_info(VS::RenderInfo p_info) { } } +String RasterizerStorageGLES3::get_video_adapter_name() const { + + return (const char *)glGetString(GL_RENDERER); +} + +String RasterizerStorageGLES3::get_video_adapter_vendor() const { + + return (const char *)glGetString(GL_VENDOR); +} + void RasterizerStorageGLES3::initialize() { RasterizerStorageGLES3::system_fbo = 0; diff --git a/drivers/gles3/rasterizer_storage_gles3.h b/drivers/gles3/rasterizer_storage_gles3.h index 833cdb4a94..bd853852fe 100644 --- a/drivers/gles3/rasterizer_storage_gles3.h +++ b/drivers/gles3/rasterizer_storage_gles3.h @@ -1467,6 +1467,8 @@ public: virtual int get_captured_render_info(VS::RenderInfo p_info); virtual int get_render_info(VS::RenderInfo p_info); + virtual String get_video_adapter_name() const; + virtual String get_video_adapter_vendor() const; RasterizerStorageGLES3(); }; diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 731c089c83..c5eb343cc8 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -59,6 +59,7 @@ #include <poll.h> #include <signal.h> #include <stdarg.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> @@ -554,23 +555,38 @@ void UnixTerminalLogger::log_error(const char *p_function, const char *p_file, i else err_details = p_code; + // Disable color codes if stdout is not a TTY. + // This prevents Godot from writing ANSI escape codes when redirecting + // stdout and stderr to a file. + const bool tty = isatty(fileno(stdout)); + const char *red = tty ? "\E[0;31m" : ""; + const char *red_bold = tty ? "\E[1;31m" : ""; + const char *yellow = tty ? "\E[0;33m" : ""; + const char *yellow_bold = tty ? "\E[1;33m" : ""; + const char *magenta = tty ? "\E[0;35m" : ""; + const char *magenta_bold = tty ? "\E[1;35m" : ""; + const char *cyan = tty ? "\E[0;36m" : ""; + const char *cyan_bold = tty ? "\E[1;36m" : ""; + const char *reset = tty ? "\E[0m" : ""; + const char *bold = tty ? "\E[1m" : ""; + switch (p_type) { case ERR_WARNING: - logf_error("\E[1;33mWARNING: %s: \E[0m\E[1m%s\n", p_function, err_details); - logf_error("\E[0;33m At: %s:%i.\E[0m\n", p_file, p_line); + logf_error("%sWARNING: %s: %s%s%s\n", yellow_bold, p_function, reset, bold, err_details); + logf_error("%s At: %s:%i.%s\n", yellow, p_file, p_line, reset); break; case ERR_SCRIPT: - logf_error("\E[1;35mSCRIPT ERROR: %s: \E[0m\E[1m%s\n", p_function, err_details); - logf_error("\E[0;35m At: %s:%i.\E[0m\n", p_file, p_line); + logf_error("%sSCRIPT ERROR: %s: %s%s%s\n", magenta_bold, p_function, reset, bold, err_details); + logf_error("%s At: %s:%i.%s\n", magenta, p_file, p_line, reset); break; case ERR_SHADER: - logf_error("\E[1;36mSHADER ERROR: %s: \E[0m\E[1m%s\n", p_function, err_details); - logf_error("\E[0;36m At: %s:%i.\E[0m\n", p_file, p_line); + logf_error("%sSHADER ERROR: %s: %s%s%s\n", cyan_bold, p_function, reset, bold, err_details); + logf_error("%s At: %s:%i.%s\n", cyan, p_file, p_line, reset); break; case ERR_ERROR: default: - logf_error("\E[1;31mERROR: %s: \E[0m\E[1m%s\n", p_function, err_details); - logf_error("\E[0;31m At: %s:%i.\E[0m\n", p_file, p_line); + logf_error("%sERROR: %s: %s%s%s\n", red_bold, p_function, reset, bold, err_details); + logf_error("%s At: %s:%i.%s\n", red, p_file, p_line, reset); break; } } diff --git a/editor/editor_asset_installer.cpp b/editor/editor_asset_installer.cpp index ea76aad168..86611bd20a 100644 --- a/editor/editor_asset_installer.cpp +++ b/editor/editor_asset_installer.cpp @@ -195,7 +195,7 @@ void EditorAssetInstaller::open(const String &p_path, int p_depth) { String res_path = "res://" + path; if (FileAccess::exists(res_path)) { ti->set_custom_color(0, get_color("error_color", "Editor")); - ti->set_tooltip(0, res_path + " (Already Exists)"); + ti->set_tooltip(0, vformat(TTR("%s (Already Exists)"), res_path)); ti->set_checked(0, false); } else { ti->set_tooltip(0, res_path); @@ -288,11 +288,11 @@ void EditorAssetInstaller::ok_pressed() { unzClose(pkg); if (failed_files.size()) { - String msg = "The following files failed extraction from package:\n\n"; + String msg = TTR("The following files failed extraction from package:") + "\n\n"; for (int i = 0; i < failed_files.size(); i++) { if (i > 15) { - msg += "\nAnd " + itos(failed_files.size() - i) + " more files."; + msg += "\n" + vformat(TTR("And %s more files."), itos(failed_files.size() - i)); break; } msg += failed_files[i]; @@ -317,7 +317,7 @@ EditorAssetInstaller::EditorAssetInstaller() { add_child(vb); tree = memnew(Tree); - vb->add_margin_child("Package Contents:", tree, true); + vb->add_margin_child(TTR("Package Contents:"), tree, true); tree->connect("item_edited", this, "_item_edited"); error = memnew(AcceptDialog); diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp index 07b4a1ea31..365238222f 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -1290,7 +1290,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) { Error err = ResourceSaver::save(p_string, AudioServer::get_singleton()->generate_bus_layout()); if (err != OK) { - EditorNode::get_singleton()->show_warning("Error saving file: " + p_string); + EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file: %s"), p_string)); return; } diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index 72530e23b9..d66b386f93 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -32,6 +32,7 @@ #include "core/crypto/crypto_core.h" #include "core/io/config_file.h" +#include "core/io/file_access_pack.h" // PACK_HEADER_MAGIC, PACK_FORMAT_VERSION #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/io/zip_io.h" @@ -970,11 +971,12 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c int64_t pck_start_pos = f->get_position(); - f->store_32(0x43504447); //GDPC - f->store_32(1); //pack version + f->store_32(PACK_HEADER_MAGIC); + f->store_32(PACK_FORMAT_VERSION); f->store_32(VERSION_MAJOR); f->store_32(VERSION_MINOR); - f->store_32(0); //hmph + f->store_32(VERSION_PATCH); + for (int i = 0; i < 16; i++) { //reserved f->store_32(0); @@ -1049,7 +1051,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c int64_t pck_size = f->get_position() - pck_start_pos; f->store_64(pck_size); - f->store_32(0x43504447); //GDPC + f->store_32(PACK_HEADER_MAGIC); if (r_embedded_size) { *r_embedded_size = f->get_position() - embed_pos; @@ -1483,41 +1485,29 @@ Ref<Texture> EditorExportPlatformPC::get_logo() const { bool EditorExportPlatformPC::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { String err; - bool valid = true; - bool use64 = p_preset->get("binary_format/64_bits"); - - if (use64 && (!exists_export_template(debug_file_64, &err) || !exists_export_template(release_file_64, &err))) { - valid = false; - } - - if (!use64 && (!exists_export_template(debug_file_32, &err) || !exists_export_template(release_file_32, &err))) { - valid = false; - } + bool valid = false; - String custom_debug_binary = p_preset->get("custom_template/debug"); - String custom_release_binary = p_preset->get("custom_template/release"); + // Look for export templates (first official, and if defined custom templates). - if (custom_debug_binary == "" && custom_release_binary == "") { - if (!err.empty()) - r_error = err; - r_missing_templates = !valid; - return valid; - } - - bool dvalid = true; - bool rvalid = true; + bool use64 = p_preset->get("binary_format/64_bits"); + bool dvalid = exists_export_template(use64 ? debug_file_64 : debug_file_32, &err); + bool rvalid = exists_export_template(use64 ? release_file_64 : release_file_32, &err); - if (!FileAccess::exists(custom_debug_binary)) { - dvalid = false; - err += TTR("Custom debug template not found.") + "\n"; + if (p_preset->get("custom_template/debug") != "") { + dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); + if (!dvalid) { + err += TTR("Custom debug template not found.") + "\n"; + } } - - if (!FileAccess::exists(custom_release_binary)) { - rvalid = false; - err += TTR("Custom release template not found.") + "\n"; + if (p_preset->get("custom_template/release") != "") { + rvalid = FileAccess::exists(p_preset->get("custom_template/release")); + if (!rvalid) { + err += TTR("Custom release template not found.") + "\n"; + } } valid = dvalid || rvalid; + r_missing_templates = !valid; if (!err.empty()) r_error = err; @@ -1598,7 +1588,7 @@ Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_pr if (embedded_size >= 0x100000000 && !p_preset->get("binary_format/64_bits")) { EditorNode::get_singleton()->show_warning(TTR("On 32-bit exports the embedded PCK cannot be bigger than 4 GiB.")); - return ERR_UNAVAILABLE; + return ERR_INVALID_PARAMETER; } FixUpEmbeddedPckFunc fixup_func = get_fixup_embedded_pck_func(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 8e6668bc89..d7bc959729 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -562,46 +562,68 @@ void EditorNode::_fs_changed() { _mark_unsaved_scenes(); + // FIXME: Move this to a cleaner location, it's hacky to do this is _fs_changed. + String export_error; if (export_defer.preset != "" && !EditorFileSystem::get_singleton()->is_scanning()) { + String preset_name = export_defer.preset; + // Ensures export_project does not loop infinitely, because notifications may + // come during the export. + export_defer.preset = ""; Ref<EditorExportPreset> preset; for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); ++i) { preset = EditorExport::get_singleton()->get_export_preset(i); - if (preset->get_name() == export_defer.preset) { + if (preset->get_name() == preset_name) { break; } preset.unref(); } if (preset.is_null()) { - String errstr = "Unknown export preset: " + export_defer.preset; - ERR_PRINTS(errstr); - OS::get_singleton()->set_exit_code(EXIT_FAILURE); + export_error = vformat("Invalid export preset name: %s.", preset_name); } else { Ref<EditorExportPlatform> platform = preset->get_platform(); if (platform.is_null()) { - String errstr = "Preset \"" + export_defer.preset + "\" doesn't have a platform."; - ERR_PRINTS(errstr); - OS::get_singleton()->set_exit_code(EXIT_FAILURE); + export_error = vformat("Export preset '%s' doesn't have a matching platform.", preset_name); } else { - // ensures export_project does not loop infinitely, because notifications may - // come during the export - export_defer.preset = ""; Error err = OK; + // FIXME: This way to export only resources .pck or .zip is pretty hacky + // and undocumented, and might be problematic for platforms where .zip is + // a valid project export format (e.g. macOS). if (export_defer.path.ends_with(".pck") || export_defer.path.ends_with(".zip")) { if (export_defer.path.ends_with(".zip")) { err = platform->export_zip(preset, export_defer.debug, export_defer.path); } else if (export_defer.path.ends_with(".pck")) { err = platform->export_pack(preset, export_defer.debug, export_defer.path); } - } else { - err = platform->export_project(preset, export_defer.debug, export_defer.path); + } else { // Normal project export. + String config_error; + bool missing_templates; + if (!platform->can_export(preset, config_error, missing_templates)) { + ERR_PRINT(vformat("Cannot export project with preset '%s' due to configuration errors:\n%s", preset_name, config_error)); + err = missing_templates ? ERR_FILE_NOT_FOUND : ERR_UNCONFIGURED; + } else { + err = platform->export_project(preset, export_defer.debug, export_defer.path); + } } - if (err != OK) { - ERR_PRINTS(vformat(TTR("Project export failed with error code %d."), (int)err)); - OS::get_singleton()->set_exit_code(EXIT_FAILURE); + switch (err) { + case OK: + break; + case ERR_FILE_NOT_FOUND: + export_error = vformat("Project export failed for preset '%s', the export template appears to be missing.", preset_name); + break; + case ERR_FILE_BAD_PATH: + export_error = vformat("Project export failed for preset '%s', the target path '%s' appears to be invalid.", preset_name, export_defer.path); + break; + default: + export_error = vformat("Project export failed with error code %d for preset '%s'.", (int)err, preset_name); + break; } } } + if (!export_error.empty()) { + ERR_PRINT(export_error); + OS::get_singleton()->set_exit_code(EXIT_FAILURE); + } _exit_editor(); } } @@ -3920,12 +3942,11 @@ void EditorNode::_editor_file_dialog_unregister(EditorFileDialog *p_dialog) { Vector<EditorNodeInitCallback> EditorNode::_init_callbacks; -Error EditorNode::export_preset(const String &p_preset, const String &p_path, bool p_debug, const String &p_password, bool p_quit_after) { +Error EditorNode::export_preset(const String &p_preset, const String &p_path, bool p_debug) { export_defer.preset = p_preset; export_defer.path = p_path; export_defer.debug = p_debug; - export_defer.password = p_password; disable_progress_dialog = true; return OK; } @@ -6531,12 +6552,6 @@ EditorNode::EditorNode() { gui_base->add_child(file); file->set_current_dir("res://"); - file_export = memnew(EditorFileDialog); - file_export->set_access(EditorFileDialog::ACCESS_FILESYSTEM); - gui_base->add_child(file_export); - file_export->set_title(TTR("Export Project")); - file_export->connect("file_selected", this, "_dialog_action"); - file_export_lib = memnew(EditorFileDialog); file_export_lib->set_title(TTR("Export Library")); file_export_lib->set_mode(EditorFileDialog::MODE_SAVE_FILE); @@ -6547,11 +6562,6 @@ EditorNode::EditorNode() { file_export_lib->get_vbox()->add_child(file_export_lib_merge); gui_base->add_child(file_export_lib); - file_export_password = memnew(LineEdit); - file_export_password->set_secret(true); - file_export_password->set_editable(false); - file_export->get_vbox()->add_margin_child(TTR("Password:"), file_export_password); - file_script = memnew(EditorFileDialog); file_script->set_title(TTR("Open & Run a Script")); file_script->set_access(EditorFileDialog::ACCESS_FILESYSTEM); diff --git a/editor/editor_node.h b/editor/editor_node.h index acff91790d..469ba76872 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -325,18 +325,13 @@ private: ExportTemplateManager *export_template_manager; EditorFeatureProfileManager *feature_profile_manager; EditorFileDialog *file_templates; - EditorFileDialog *file_export; EditorFileDialog *file_export_lib; EditorFileDialog *file_script; CheckBox *file_export_lib_merge; - LineEdit *file_export_password; String current_path; MenuButton *update_spinner; String defer_load_scene; - String defer_export; - String defer_export_platform; - bool defer_export_debug; Node *_last_instanced_scene; EditorLog *log; @@ -563,8 +558,6 @@ private: String preset; String path; bool debug; - String password; - } export_defer; bool disable_progress_dialog; @@ -786,7 +779,7 @@ public: void _copy_warning(const String &p_str); - Error export_preset(const String &p_preset, const String &p_path, bool p_debug, const String &p_password, bool p_quit_after = false); + Error export_preset(const String &p_preset, const String &p_path, bool p_debug); static void register_editor_types(); static void unregister_editor_types(); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 15fb6a9521..2cacc767c8 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1012,6 +1012,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("grid_major", "GraphEdit", Color(0.0, 0.0, 0.0, 0.15)); theme->set_color("grid_minor", "GraphEdit", Color(0.0, 0.0, 0.0, 0.07)); } + theme->set_color("selection_fill", "GraphEdit", theme->get_color("box_selection_fill_color", "Editor")); + theme->set_color("selection_stroke", "GraphEdit", theme->get_color("box_selection_stroke_color", "Editor")); theme->set_color("activity", "GraphEdit", accent_color); theme->set_icon("minus", "GraphEdit", theme->get_icon("ZoomLess", "EditorIcons")); theme->set_icon("more", "GraphEdit", theme->get_icon("ZoomMore", "EditorIcons")); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 1fecdfe555..fb5c2b25c9 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -90,6 +90,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory // Create all items for the files in the subdirectory. if (display_mode == DISPLAY_MODE_TREE_ONLY) { + String main_scene = ProjectSettings::get_singleton()->get("application/run/main_scene"); for (int i = 0; i < p_dir->get_file_count(); i++) { String file_type = p_dir->get_file_type(i); @@ -119,7 +120,6 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory file_item->select(0); file_item->set_as_cursor(0); } - String main_scene = ProjectSettings::get_singleton()->get("application/run/main_scene"); if (main_scene == file_metadata) { file_item->set_custom_color(0, get_color("accent_color", "Editor")); } @@ -750,6 +750,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } // Fills the ItemList control node from the FileInfos. + String main_scene = ProjectSettings::get_singleton()->get("application/run/main_scene"); String oi = "Object"; for (List<FileInfo>::Element *E = filelist.front(); E; E = E->next()) { FileInfo *finfo = &(E->get()); @@ -786,6 +787,10 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->set_item_metadata(item_index, fpath); } + if (fpath == main_scene) { + files->set_item_custom_fg_color(item_index, get_color("accent_color", "Editor")); + } + // Generate the preview. if (!finfo->import_broken) { Array udata; @@ -1756,7 +1761,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected fpath = fpath.get_base_dir(); } make_script_dialog->config("Node", fpath.plus_file("new_script.gd"), false); - make_script_dialog->popup_centered(Size2(300, 300) * EDSCALE); + make_script_dialog->popup_centered(); } break; case FILE_COPY_PATH: { diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 71afddba11..2b0c582b49 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4216,11 +4216,13 @@ void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) { void CanvasItemEditor::_update_zoom_label() { String zoom_text; + // The zoom level displayed is relative to the editor scale + // (like in most image editors). if (zoom >= 10) { - // Don't show a decimal when the zoom level is higher than 1000 % - zoom_text = rtos(Math::round(zoom * 100)) + " %"; + // Don't show a decimal when the zoom level is higher than 1000 %. + zoom_text = rtos(Math::round((zoom / EDSCALE) * 100)) + " %"; } else { - zoom_text = rtos(Math::stepify(zoom * 100, 0.1)) + " %"; + zoom_text = rtos(Math::stepify((zoom / EDSCALE) * 100, 0.1)) + " %"; } zoom_reset->set_text(zoom_text); @@ -4231,7 +4233,7 @@ void CanvasItemEditor::_button_zoom_minus() { } void CanvasItemEditor::_button_zoom_reset() { - _zoom_on_position(1.0, viewport_scrollable->get_size() / 2.0); + _zoom_on_position(1.0 * EDSCALE, viewport_scrollable->get_size() / 2.0); } void CanvasItemEditor::_button_zoom_plus() { @@ -4993,7 +4995,8 @@ void CanvasItemEditor::_bind_methods() { Dictionary CanvasItemEditor::get_state() const { Dictionary state; - state["zoom"] = zoom; + // Take the editor scale into account. + state["zoom"] = zoom / EDSCALE; state["ofs"] = view_offset; state["grid_offset"] = grid_offset; state["grid_step"] = grid_step; @@ -5030,7 +5033,9 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { bool update_scrollbars = false; Dictionary state = p_state; if (state.has("zoom")) { - zoom = p_state["zoom"]; + // Compensate the editor scale, so that the editor scale can be changed + // and the zoom level will still be the same (relative to the editor scale). + zoom = float(p_state["zoom"]) * EDSCALE; _update_zoom_label(); } @@ -5246,7 +5251,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { show_rulers = true; show_guides = true; show_edit_locks = true; - zoom = 1; + zoom = 1.0 / EDSCALE; view_offset = Point2(-150 - RULER_WIDTH, -95 - RULER_WIDTH); previous_update_view_offset = view_offset; // Moves the view a little bit to the left so that (0,0) is visible. The values a relative to a 16/10 screen grid_offset = Point2(); diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp index 33ae9363dc..655048c271 100644 --- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_2d_editor_plugin.cpp @@ -296,9 +296,9 @@ CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin(EditorNode *p_node) { emission_mask->add_child(emvb); emission_mask_mode = memnew(OptionButton); emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode); - emission_mask_mode->add_item("Solid Pixels", EMISSION_MODE_SOLID); - emission_mask_mode->add_item("Border Pixels", EMISSION_MODE_BORDER); - emission_mask_mode->add_item("Directed Border Pixels", EMISSION_MODE_BORDER_DIRECTED); + emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID); + emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER); + emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED); emission_colors = memnew(CheckBox); emission_colors->set_text(TTR("Capture from Pixel")); emvb->add_margin_child(TTR("Emission Colors"), emission_colors); diff --git a/editor/plugins/particles_2d_editor_plugin.cpp b/editor/plugins/particles_2d_editor_plugin.cpp index 441739de5f..b036368bc8 100644 --- a/editor/plugins/particles_2d_editor_plugin.cpp +++ b/editor/plugins/particles_2d_editor_plugin.cpp @@ -424,9 +424,9 @@ Particles2DEditorPlugin::Particles2DEditorPlugin(EditorNode *p_node) { emission_mask->add_child(emvb); emission_mask_mode = memnew(OptionButton); emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode); - emission_mask_mode->add_item("Solid Pixels", EMISSION_MODE_SOLID); - emission_mask_mode->add_item("Border Pixels", EMISSION_MODE_BORDER); - emission_mask_mode->add_item("Directed Border Pixels", EMISSION_MODE_BORDER_DIRECTED); + emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID); + emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER); + emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED); emission_colors = memnew(CheckBox); emission_colors->set_text(TTR("Capture from Pixel")); emvb->add_margin_child(TTR("Emission Colors"), emission_colors); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 335466b0c5..d0bd57d658 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1004,8 +1004,8 @@ void ScriptEditor::_menu_option(int p_option) { ScriptEditorBase *current = _get_current_editor(); switch (p_option) { case FILE_NEW: { - script_create_dialog->config("Node", "new_script"); - script_create_dialog->popup_centered(Size2(300, 300) * EDSCALE); + script_create_dialog->config("Node", "new_script", false); + script_create_dialog->popup_centered(); } break; case FILE_NEW_TEXTFILE: { file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index 26f2968369..294bedd598 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -136,13 +136,19 @@ void TextureRegionEditor::_region_draw() { Ref<Texture> select_handle = get_icon("EditorHandle", "EditorIcons"); - Rect2 scroll_rect; + Rect2 scroll_rect(Point2(), base_tex->get_size()); - Vector2 endpoints[4] = { - mtx.basis_xform(rect.position), - mtx.basis_xform(rect.position + Vector2(rect.size.x, 0)), - mtx.basis_xform(rect.position + rect.size), - mtx.basis_xform(rect.position + Vector2(0, rect.size.y)) + const Vector2 raw_endpoints[4] = { + rect.position, + rect.position + Vector2(rect.size.x, 0), + rect.position + rect.size, + rect.position + Vector2(0, rect.size.y) + }; + const Vector2 endpoints[4] = { + mtx.basis_xform(raw_endpoints[0]), + mtx.basis_xform(raw_endpoints[1]), + mtx.basis_xform(raw_endpoints[2]), + mtx.basis_xform(raw_endpoints[3]) }; Color color = get_color("mono_color", "Editor"); for (int i = 0; i < 4; i++) { @@ -164,31 +170,32 @@ void TextureRegionEditor::_region_draw() { if (snap_mode != SNAP_AUTOSLICE) edit_draw->draw_texture(select_handle, (endpoints[i] + ofs - (select_handle->get_size() / 2)).floor() - draw_ofs * draw_zoom); - scroll_rect.expand_to(endpoints[i]); + scroll_rect.expand_to(raw_endpoints[i]); } - scroll_rect.position -= edit_draw->get_size(); - scroll_rect.size += edit_draw->get_size() * 2.0; + const Size2 scroll_margin = edit_draw->get_size() / draw_zoom; + scroll_rect.position -= scroll_margin; + scroll_rect.size += scroll_margin * 2; updating_scroll = true; hscroll->set_min(scroll_rect.position.x); hscroll->set_max(scroll_rect.position.x + scroll_rect.size.x); - if (ABS(scroll_rect.position.x - (scroll_rect.position.x + scroll_rect.size.x)) <= edit_draw->get_size().x) { + if (ABS(scroll_rect.position.x - (scroll_rect.position.x + scroll_rect.size.x)) <= scroll_margin.x) { hscroll->hide(); } else { hscroll->show(); - hscroll->set_page(edit_draw->get_size().x); + hscroll->set_page(scroll_margin.x); hscroll->set_value(draw_ofs.x); } vscroll->set_min(scroll_rect.position.y); vscroll->set_max(scroll_rect.position.y + scroll_rect.size.y); - if (ABS(scroll_rect.position.y - (scroll_rect.position.y + scroll_rect.size.y)) <= edit_draw->get_size().y) { + if (ABS(scroll_rect.position.y - (scroll_rect.position.y + scroll_rect.size.y)) <= scroll_margin.y) { vscroll->hide(); draw_ofs.y = scroll_rect.position.y; } else { vscroll->show(); - vscroll->set_page(edit_draw->get_size().y); + vscroll->set_page(scroll_margin.y); vscroll->set_value(draw_ofs.y); } updating_scroll = false; diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 4c36e15eb4..25bc13033c 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -46,7 +46,7 @@ void ScriptCreateDialog::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_ENTER_TREE: { for (int i = 0; i < ScriptServer::get_language_count(); i++) { - String lang = ScriptServer::get_language(i)->get_name(); + String lang = ScriptServer::get_language(i)->get_type(); Ref<Texture> lang_icon = get_icon(lang, "EditorIcons"); if (lang_icon.is_valid()) { language_menu->set_item_icon(i, lang_icon); @@ -678,6 +678,11 @@ void ScriptCreateDialog::_update_dialog() { } } + if (!_can_be_built_in()) { + internal->set_pressed(false); + } + internal->set_disabled(!_can_be_built_in()); + /* Is Script created or loaded from existing file */ if (is_built_in) { @@ -685,8 +690,6 @@ void ScriptCreateDialog::_update_dialog() { parent_name->set_editable(true); parent_search_button->set_disabled(false); parent_browse_button->set_disabled(!can_inherit_from_file); - internal->set_visible(_can_be_built_in()); - internal_label->set_visible(_can_be_built_in()); _msg_path_valid(true, TTR("Built-in script (into scene file).")); } else if (is_new_script_created) { // New Script Created @@ -694,8 +697,6 @@ void ScriptCreateDialog::_update_dialog() { parent_name->set_editable(true); parent_search_button->set_disabled(false); parent_browse_button->set_disabled(!can_inherit_from_file); - internal->set_visible(_can_be_built_in()); - internal_label->set_visible(_can_be_built_in()); if (is_path_valid) { _msg_path_valid(true, TTR("Will create a new script file.")); } @@ -705,7 +706,6 @@ void ScriptCreateDialog::_update_dialog() { parent_name->set_editable(false); parent_search_button->set_disabled(true); parent_browse_button->set_disabled(true); - internal->set_disabled(!_can_be_built_in()); if (is_path_valid) { _msg_path_valid(true, TTR("Will load an existing script file.")); } @@ -834,8 +834,7 @@ ScriptCreateDialog::ScriptCreateDialog() { internal = memnew(CheckBox); internal->set_text(TTR("On")); internal->connect("pressed", this, "_built_in_pressed"); - internal_label = memnew(Label(TTR("Built-in Script:"))); - gc->add_child(internal_label); + gc->add_child(memnew(Label(TTR("Built-in Script:")))); gc->add_child(internal); /* Path */ diff --git a/editor/script_create_dialog.h b/editor/script_create_dialog.h index 4d13bc9291..91d6315a78 100644 --- a/editor/script_create_dialog.h +++ b/editor/script_create_dialog.h @@ -58,7 +58,6 @@ class ScriptCreateDialog : public ConfirmationDialog { Button *path_button; EditorFileDialog *file_browse; CheckBox *internal; - Label *internal_label; VBoxContainer *path_vb; AcceptDialog *alert; CreateDialog *select_class; diff --git a/main/main.cpp b/main/main.cpp index 2bcea5cb7d..308c53de13 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -286,8 +286,10 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print(" -s, --script <script> Run a script.\n"); OS::get_singleton()->print(" --check-only Only parse for errors and quit (use with --script).\n"); #ifdef TOOLS_ENABLED - OS::get_singleton()->print(" --export <target> <path> Export the project using the given export target. Export only main pack if path ends with .pck or .zip. <path> is relative to the project directory.\n"); - OS::get_singleton()->print(" --export-debug <target> <path> Like --export, but use debug template.\n"); + OS::get_singleton()->print(" --export <preset> <path> Export the project using the given preset and matching release template. The preset name should match one defined in export_presets.cfg.\n"); + OS::get_singleton()->print(" <path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. 'builds/game.exe').\n"); + OS::get_singleton()->print(" The target directory should exist. Only the data pack is exported if <path> ends with .pck or .zip.\n"); + OS::get_singleton()->print(" --export-debug <preset> <path> Same as --export, but using the debug template.\n"); OS::get_singleton()->print(" --doctool <path> Dump the engine API reference to the given <path> in XML format, merging if existing files are found.\n"); OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n"); OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects).\n"); @@ -1513,11 +1515,11 @@ bool Main::start() { if (_export_preset != "") { if (game_path == "") { - String err = "Command line param "; + String err = "Command line parameter "; err += export_debug ? "--export-debug" : "--export"; err += " passed but no destination path given.\n"; err += "Please specify the binary's file path to export to. Aborting export."; - ERR_PRINT(err.utf8().get_data()); + ERR_PRINT(err); return false; } } @@ -1698,20 +1700,14 @@ bool Main::start() { } #ifdef TOOLS_ENABLED - EditorNode *editor_node = NULL; if (editor) { - editor_node = memnew(EditorNode); sml->get_root()->add_child(editor_node); - //root_node->set_editor(editor); - //startup editor - if (_export_preset != "") { - - editor_node->export_preset(_export_preset, game_path, export_debug, "", true); - game_path = ""; //no load anything + editor_node->export_preset(_export_preset, game_path, export_debug); + game_path = ""; // Do not load anything. } } #endif diff --git a/methods.py b/methods.py index 23d66f7f1e..33b8f1cbe7 100644 --- a/methods.py +++ b/methods.py @@ -67,8 +67,7 @@ def update_version(module_version_string=""): f.write("#define VERSION_NAME \"" + str(version.name) + "\"\n") f.write("#define VERSION_MAJOR " + str(version.major) + "\n") f.write("#define VERSION_MINOR " + str(version.minor) + "\n") - if hasattr(version, 'patch'): - f.write("#define VERSION_PATCH " + str(version.patch) + "\n") + f.write("#define VERSION_PATCH " + str(version.patch) + "\n") f.write("#define VERSION_STATUS \"" + str(version.status) + "\"\n") f.write("#define VERSION_BUILD \"" + str(build_name) + "\"\n") f.write("#define VERSION_MODULE_CONFIG \"" + str(version.module_config) + module_version_string + "\"\n") diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml index 78a8e94012..9509adfb18 100644 --- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml +++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml @@ -49,7 +49,7 @@ <argument index="3" name="out_bandwidth" type="int" default="0"> </argument> <description> - Create server that listens to connections via [code]port[/code]. The port needs to be an available, unused port between 0 and 65535. Note that ports below 1024 are privileged and may require elevated permissions depending on the platform. To change the interface the server listens on, use [method set_bind_ip]. The default IP is the wildcard [code]"*"[/code], which listens on all available interfaces. [code]max_clients[/code] is the maximum number of clients that are allowed at once, any number up to 4096 may be used, although the achievable number of simultaneous clients may be far lower and depends on the application. For additional details on the bandwidth parameters, see [method create_client]. Returns [constant OK] if a server was created, [constant ERR_ALREADY_IN_USE] if this NetworkedMultiplayerENet instance already has an open connection (in which case you need to call [method close_connection] first) or [constant ERR_CANT_CREATE] if the server could not be created. + Create server that listens to connections via [code]port[/code]. The port needs to be an available, unused port between 0 and 65535. Note that ports below 1024 are privileged and may require elevated permissions depending on the platform. To change the interface the server listens on, use [method set_bind_ip]. The default IP is the wildcard [code]"*"[/code], which listens on all available interfaces. [code]max_clients[/code] is the maximum number of clients that are allowed at once, any number up to 4095 may be used, although the achievable number of simultaneous clients may be far lower and depends on the application. For additional details on the bandwidth parameters, see [method create_client]. Returns [constant OK] if a server was created, [constant ERR_ALREADY_IN_USE] if this NetworkedMultiplayerENet instance already has an open connection (in which case you need to call [method close_connection] first) or [constant ERR_CANT_CREATE] if the server could not be created. </description> </method> <method name="disconnect_peer"> diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp index 0e75f8fd37..61fc7688c5 100644 --- a/modules/enet/networked_multiplayer_enet.cpp +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -75,7 +75,7 @@ Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int ERR_FAIL_COND_V(active, ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(p_port < 0 || p_port > 65535, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(p_max_clients < 0, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_max_clients < 1 || p_max_clients > 4095, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_in_bandwidth < 0, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_out_bandwidth < 0, ERR_INVALID_PARAMETER); diff --git a/modules/etc/image_etc.cpp b/modules/etc/image_etc.cpp index f0b95c893d..b80138c99d 100644 --- a/modules/etc/image_etc.cpp +++ b/modules/etc/image_etc.cpp @@ -139,11 +139,16 @@ static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_f return; } - if (img_format >= Image::FORMAT_RGBA8 && force_etc1_format) { - // If VRAM compression is using ETC, but image has alpha, convert to RGBA4444 + if (force_etc1_format) { + // If VRAM compression is using ETC, but image has alpha, convert to RGBA4444 or LA8 // This saves space while maintaining the alpha channel - p_img->convert(Image::FORMAT_RGBA4444); - return; + if (detected_channels == Image::DETECTED_RGBA) { + p_img->convert(Image::FORMAT_RGBA4444); + return; + } else if (detected_channels == Image::DETECTED_LA) { + p_img->convert(Image::FORMAT_LA8); + return; + } } uint32_t imgw = p_img->get_width(), imgh = p_img->get_height(); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 94cd2536e3..b3d1b67af5 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -49,6 +49,7 @@ void GDScriptTextDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("colorPresentation"), &GDScriptTextDocument::colorPresentation); ClassDB::bind_method(D_METHOD("hover"), &GDScriptTextDocument::hover); ClassDB::bind_method(D_METHOD("definition"), &GDScriptTextDocument::definition); + ClassDB::bind_method(D_METHOD("declaration"), &GDScriptTextDocument::declaration); ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor"), &GDScriptTextDocument::show_native_symbol_in_editor); } @@ -340,84 +341,96 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) { } Array GDScriptTextDocument::definition(const Dictionary &p_params) { - Array arr; + lsp::TextDocumentPositionParams params; + params.load(p_params); + List<const lsp::DocumentSymbol *> symbols; + Array arr = this->find_symbols(params, symbols); + return arr; +} +Variant GDScriptTextDocument::declaration(const Dictionary &p_params) { lsp::TextDocumentPositionParams params; params.load(p_params); + List<const lsp::DocumentSymbol *> symbols; + Array arr = this->find_symbols(params, symbols); + if (arr.empty() && !symbols.empty() && !symbols.front()->get()->native_class.empty()) { // Find a native symbol + const lsp::DocumentSymbol *symbol = symbols.front()->get(); + if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { + String id; + switch (symbol->kind) { + case lsp::SymbolKind::Class: + id = "class_name:" + symbol->name; + break; + case lsp::SymbolKind::Constant: + id = "class_constant:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Property: + case lsp::SymbolKind::Variable: + id = "class_property:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Enum: + id = "class_enum:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Method: + case lsp::SymbolKind::Function: + id = "class_method:" + symbol->native_class + ":" + symbol->name; + break; + default: + id = "class_global:" + symbol->native_class + ":" + symbol->name; + break; + } + call_deferred("show_native_symbol_in_editor", id); + } else { + notify_client_show_symbol(symbol); + } + } + return arr; +} - const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params); +GDScriptTextDocument::GDScriptTextDocument() { + file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES); +} + +GDScriptTextDocument::~GDScriptTextDocument() { + memdelete(file_checker); +} + +void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) { + String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path); + GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); +} + +void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) { + ScriptEditor::get_singleton()->call_deferred("_help_class_goto", p_symbol_id); + OS::get_singleton()->move_window_to_foreground(); +} + +Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams &p_location, List<const lsp::DocumentSymbol *> &r_list) { + Array arr; + const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(p_location); if (symbol) { lsp::Location location; location.uri = symbol->uri; location.range = symbol->range; - const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri); if (file_checker->file_exists(path)) { arr.push_back(location.to_json()); - } else if (!symbol->native_class.empty()) { - if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { - String id; - switch (symbol->kind) { - case lsp::SymbolKind::Class: - id = "class_name:" + symbol->name; - break; - case lsp::SymbolKind::Constant: - id = "class_constant:" + symbol->native_class + ":" + symbol->name; - break; - case lsp::SymbolKind::Property: - case lsp::SymbolKind::Variable: - id = "class_property:" + symbol->native_class + ":" + symbol->name; - break; - case lsp::SymbolKind::Enum: - id = "class_enum:" + symbol->native_class + ":" + symbol->name; - break; - case lsp::SymbolKind::Method: - case lsp::SymbolKind::Function: - id = "class_method:" + symbol->native_class + ":" + symbol->name; - break; - default: - id = "class_global:" + symbol->native_class + ":" + symbol->name; - break; - } - call_deferred("show_native_symbol_in_editor", id); - } else { - notify_client_show_symbol(symbol); - } } + r_list.push_back(symbol); } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - List<const lsp::DocumentSymbol *> list; - GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list); + GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(p_location, list); for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { - if (const lsp::DocumentSymbol *s = E->get()) { if (!s->uri.empty()) { lsp::Location location; location.uri = s->uri; location.range = s->range; arr.push_back(location.to_json()); + r_list.push_back(s); } } } } - return arr; } - -GDScriptTextDocument::GDScriptTextDocument() { - file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES); -} - -GDScriptTextDocument::~GDScriptTextDocument() { - memdelete(file_checker); -} - -void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) { - String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path); - GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); -} - -void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) { - ScriptEditor::get_singleton()->call_deferred("_help_class_goto", p_symbol_id); - OS::get_singleton()->move_window_to_foreground(); -} diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index 8a7c6fb98c..d93d828003 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -51,6 +51,7 @@ protected: Array native_member_completions; private: + Array find_symbols(const lsp::TextDocumentPositionParams &p_location, List<const lsp::DocumentSymbol *> &r_list); lsp::TextDocumentItem load_document_item(const Variant &p_param); void notify_client_show_symbol(const lsp::DocumentSymbol *symbol); @@ -65,6 +66,7 @@ public: Array colorPresentation(const Dictionary &p_params); Variant hover(const Dictionary &p_params); Array definition(const Dictionary &p_params); + Variant declaration(const Dictionary &p_params); void initialize(); diff --git a/modules/mono/icons/icon_c_#.svg b/modules/mono/icons/icon_c_sharp_script.svg index 69664ca553..69664ca553 100644 --- a/modules/mono/icons/icon_c_#.svg +++ b/modules/mono/icons/icon_c_sharp_script.svg diff --git a/modules/opensimplex/noise_texture.cpp b/modules/opensimplex/noise_texture.cpp index e0cdfb33b4..aa1c822813 100644 --- a/modules/opensimplex/noise_texture.cpp +++ b/modules/opensimplex/noise_texture.cpp @@ -53,6 +53,10 @@ NoiseTexture::NoiseTexture() { NoiseTexture::~NoiseTexture() { VS::get_singleton()->free(texture); + if (noise_thread) { + Thread::wait_to_finish(noise_thread); + memdelete(noise_thread); + } } void NoiseTexture::_bind_methods() { @@ -73,6 +77,7 @@ void NoiseTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bump_strength"), &NoiseTexture::get_bump_strength); ClassDB::bind_method(D_METHOD("_update_texture"), &NoiseTexture::_update_texture); + ClassDB::bind_method(D_METHOD("_queue_update"), &NoiseTexture::_queue_update); ClassDB::bind_method(D_METHOD("_generate_texture"), &NoiseTexture::_generate_texture); ClassDB::bind_method(D_METHOD("_thread_done", "image"), &NoiseTexture::_thread_done); @@ -130,8 +135,6 @@ void NoiseTexture::_queue_update() { Ref<Image> NoiseTexture::_generate_texture() { - update_queued = false; - if (noise.is_null()) return Ref<Image>(); Ref<Image> image; @@ -171,17 +174,18 @@ void NoiseTexture::_update_texture() { Ref<Image> image = _generate_texture(); _set_texture_data(image); } + update_queued = false; } void NoiseTexture::set_noise(Ref<OpenSimplexNoise> p_noise) { if (p_noise == noise) return; if (noise.is_valid()) { - noise->disconnect(CoreStringNames::get_singleton()->changed, this, "_update_texture"); + noise->disconnect(CoreStringNames::get_singleton()->changed, this, "_queue_update"); } noise = p_noise; if (noise.is_valid()) { - noise->connect(CoreStringNames::get_singleton()->changed, this, "_update_texture"); + noise->connect(CoreStringNames::get_singleton()->changed, this, "_queue_update"); } _queue_update(); } diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 9167a291f8..d7a72779e6 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -1299,9 +1299,9 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "graphics/degrees_of_freedom", PROPERTY_HINT_ENUM, "None,3DOF and 6DOF,6DOF"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/32_bits_framebuffer"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_package/use_custom_build"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0")); @@ -1577,29 +1577,34 @@ public: virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { String err; + bool valid = false; - if (!bool(p_preset->get("custom_package/use_custom_build"))) { + // Look for export templates (first official, and if defined custom templates). - r_missing_templates = find_export_template("android_debug.apk") == String() || find_export_template("android_release.apk") == String(); + if (!bool(p_preset->get("custom_template/use_custom_build"))) { + bool dvalid = exists_export_template("android_debug.apk", &err); + bool rvalid = exists_export_template("android_release.apk", &err); - if (p_preset->get("custom_package/debug") != "") { - if (FileAccess::exists(p_preset->get("custom_package/debug"))) { - r_missing_templates = false; - } else { + if (p_preset->get("custom_template/debug") != "") { + dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); + if (!dvalid) { err += TTR("Custom debug template not found.") + "\n"; } } - - if (p_preset->get("custom_package/release") != "") { - if (FileAccess::exists(p_preset->get("custom_package/release"))) { - r_missing_templates = false; - } else { + if (p_preset->get("custom_template/release") != "") { + rvalid = FileAccess::exists(p_preset->get("custom_template/release")); + if (!rvalid) { err += TTR("Custom release template not found.") + "\n"; } } + + valid = dvalid || rvalid; + } else { + valid = exists_export_template("android_source.zip", &err); } + r_missing_templates = !valid; - bool valid = !r_missing_templates; + // Validate the rest of the configuration. String adb = EditorSettings::get_singleton()->get("export/android/adb"); @@ -1628,7 +1633,7 @@ public: } } - if (bool(p_preset->get("custom_package/use_custom_build"))) { + if (bool(p_preset->get("custom_template/use_custom_build"))) { String sdk_path = EditorSettings::get_singleton()->get("export/android/custom_build_sdk_path"); if (sdk_path == "") { err += TTR("Custom build requires a valid Android SDK path in Editor Settings.") + "\n"; @@ -1949,7 +1954,7 @@ public: EditorProgress ep("export", "Exporting for Android", 105, true); - if (bool(p_preset->get("custom_package/use_custom_build"))) { //custom build + if (bool(p_preset->get("custom_template/use_custom_build"))) { //custom build //re-generate build.gradle and AndroidManifest.xml { //test that installed build version is alright @@ -2017,9 +2022,9 @@ public: } else { if (p_debug) - src_apk = p_preset->get("custom_package/debug"); + src_apk = p_preset->get("custom_template/debug"); else - src_apk = p_preset->get("custom_package/release"); + src_apk = p_preset->get("custom_template/release"); src_apk = src_apk.strip_edges(); if (src_apk == "") { diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index c80370847d..b606d570c2 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -206,8 +206,8 @@ static const LoadingScreenInfo loading_screen_infos[] = { void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) { - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "")); @@ -848,9 +848,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p ERR_FAIL_COND_V_MSG(team_id.length() == 0, ERR_CANT_OPEN, "App Store Team ID not specified - cannot configure the project."); if (p_debug) - src_pkg_name = p_preset->get("custom_package/debug"); + src_pkg_name = p_preset->get("custom_template/debug"); else - src_pkg_name = p_preset->get("custom_package/release"); + src_pkg_name = p_preset->get("custom_template/release"); if (src_pkg_name == "") { String err; @@ -1156,25 +1156,30 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { String err; - r_missing_templates = find_export_template("iphone.zip") == String(); + bool valid = false; - if (p_preset->get("custom_package/debug") != "") { - if (FileAccess::exists(p_preset->get("custom_package/debug"))) { - r_missing_templates = false; - } else { + // Look for export templates (first official, and if defined custom templates). + + bool dvalid = exists_export_template("iphone.zip", &err); + bool rvalid = dvalid; // Both in the same ZIP. + + if (p_preset->get("custom_template/debug") != "") { + dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); + if (!dvalid) { err += TTR("Custom debug template not found.") + "\n"; } } - - if (p_preset->get("custom_package/release") != "") { - if (FileAccess::exists(p_preset->get("custom_package/release"))) { - r_missing_templates = false; - } else { + if (p_preset->get("custom_template/release") != "") { + rvalid = FileAccess::exists(p_preset->get("custom_template/release")); + if (!rvalid) { err += TTR("Custom release template not found.") + "\n"; } } - bool valid = !r_missing_templates; + valid = dvalid || rvalid; + r_missing_templates = !valid; + + // Validate the rest of the configuration. String team_id = p_preset->get("application/app_store_team_id"); if (team_id.length() == 0) { diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index bbdf6a1f57..93c83f4ff4 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -288,32 +288,32 @@ Ref<Texture> EditorExportPlatformJavaScript::get_logo() const { bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { - bool valid = false; String err; + bool valid = false; - if (find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE) != "") - valid = true; - else if (find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG) != "") - valid = true; + // Look for export templates (first official, and if defined custom templates). + + bool dvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG, &err); + bool rvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE, &err); if (p_preset->get("custom_template/debug") != "") { - if (FileAccess::exists(p_preset->get("custom_template/debug"))) { - valid = true; - } else { + dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); + if (!dvalid) { err += TTR("Custom debug template not found.") + "\n"; } } - if (p_preset->get("custom_template/release") != "") { - if (FileAccess::exists(p_preset->get("custom_template/release"))) { - valid = true; - } else { + rvalid = FileAccess::exists(p_preset->get("custom_template/release")); + if (!rvalid) { err += TTR("Custom release template not found.") + "\n"; } } + valid = dvalid || rvalid; r_missing_templates = !valid; + // Validate the rest of the configuration. + if (p_preset->get("vram_texture_compression/for_mobile")) { String etc_error = test_etc2(); if (etc_error != String()) { diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index dd98a98996..ce7b47c414 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -118,8 +118,8 @@ void EditorExportPlatformOSX::get_preset_features(const Ref<EditorExportPreset> void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) { - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); @@ -459,9 +459,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p EditorProgress ep("export", "Exporting for OSX", 3, true); if (p_debug) - src_pkg_name = p_preset->get("custom_package/debug"); + src_pkg_name = p_preset->get("custom_template/debug"); else - src_pkg_name = p_preset->get("custom_package/release"); + src_pkg_name = p_preset->get("custom_template/release"); if (src_pkg_name == "") { String err; @@ -796,33 +796,32 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { - bool valid = false; String err; + bool valid = false; - if (exists_export_template("osx.zip", &err)) { - valid = true; - } + // Look for export templates (first official, and if defined custom templates). - if (p_preset->get("custom_package/debug") != "") { - if (FileAccess::exists(p_preset->get("custom_package/debug"))) { - valid = true; - } else { + bool dvalid = exists_export_template("osx.zip", &err); + bool rvalid = dvalid; // Both in the same ZIP. + + if (p_preset->get("custom_template/debug") != "") { + dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); + if (!dvalid) { err += TTR("Custom debug template not found.") + "\n"; } } - - if (p_preset->get("custom_package/release") != "") { - if (FileAccess::exists(p_preset->get("custom_package/release"))) { - valid = true; - } else { + if (p_preset->get("custom_template/release") != "") { + rvalid = FileAccess::exists(p_preset->get("custom_template/release")); + if (!rvalid) { err += TTR("Custom release template not found.") + "\n"; } } + valid = dvalid || rvalid; + r_missing_templates = !valid; + if (!err.empty()) r_error = err; - - r_missing_templates = !valid; return valid; } diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 53b44af867..44c5412a39 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -121,6 +121,7 @@ public: bool maximized; bool zoomed; bool resizable; + bool window_focused; Size2 window_size; Rect2 restore_rect; @@ -274,6 +275,7 @@ public: virtual bool is_window_maximized() const; virtual void set_window_always_on_top(bool p_enabled); virtual bool is_window_always_on_top() const; + virtual bool is_window_focused() const; virtual void request_attention(); virtual String get_joy_guid(int p_device) const; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 7225fbf43d..95c103bd28 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -393,9 +393,6 @@ static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFacto } - (void)windowDidBecomeKey:(NSNotification *)notification { - //_GodotInputWindowFocus(window, GL_TRUE); - //_GodotPlatformSetCursorMode(window, window->cursorMode); - if (OS_OSX::singleton->get_main_loop()) { get_mouse_pos( [OS_OSX::singleton->window_object mouseLocationOutsideOfEventStream], @@ -404,25 +401,31 @@ static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFacto OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); } + + OS_OSX::singleton->window_focused = true; } - (void)windowDidResignKey:(NSNotification *)notification { - //_GodotInputWindowFocus(window, GL_FALSE); - //_GodotPlatformSetCursorMode(window, Godot_CURSOR_NORMAL); if (OS_OSX::singleton->get_main_loop()) OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); + + OS_OSX::singleton->window_focused = false; } - (void)windowDidMiniaturize:(NSNotification *)notification { OS_OSX::singleton->wm_minimized(true); if (OS_OSX::singleton->get_main_loop()) OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); + + OS_OSX::singleton->window_focused = false; }; - (void)windowDidDeminiaturize:(NSNotification *)notification { OS_OSX::singleton->wm_minimized(false); if (OS_OSX::singleton->get_main_loop()) OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); + + OS_OSX::singleton->window_focused = true; }; @end @@ -2607,6 +2610,10 @@ bool OS_OSX::is_window_always_on_top() const { return [window_object level] == NSFloatingWindowLevel; } +bool OS_OSX::is_window_focused() const { + return window_focused; +} + void OS_OSX::request_attention() { [NSApp requestUserAttention:NSCriticalRequest]; @@ -3059,6 +3066,7 @@ OS_OSX::OS_OSX() { window_size = Vector2(1024, 600); zoomed = false; resizable = false; + window_focused = true; Vector<Logger *> loggers; loggers.push_back(memnew(OSXTerminalLogger)); diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index 3fa5512819..57fb9004b8 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -1090,15 +1090,14 @@ public: } virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { + String err; - bool valid = true; - Platform arch = (Platform)(int)(p_preset->get("architecture/target")); + bool valid = false; - String custom_debug_binary = p_preset->get("custom_template/debug"); - String custom_release_binary = p_preset->get("custom_template/release"); + // Look for export templates (first official, and if defined custom templates). + Platform arch = (Platform)(int)(p_preset->get("architecture/target")); String platform_infix; - switch (arch) { case EditorExportPlatformUWP::ARM: { platform_infix = "arm"; @@ -1111,38 +1110,26 @@ public: } break; } - if (!exists_export_template("uwp_" + platform_infix + "_debug.zip", &err) || !exists_export_template("uwp_" + platform_infix + "_release.zip", &err)) { - valid = false; - r_missing_templates = true; - } + bool dvalid = exists_export_template("uwp_" + platform_infix + "_debug.zip", &err); + bool rvalid = exists_export_template("uwp_" + platform_infix + "_release.zip", &err); - if (!valid && custom_debug_binary == "" && custom_release_binary == "") { - if (!err.empty()) { - r_error = err; + if (p_preset->get("custom_template/debug") != "") { + dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); + if (!dvalid) { + err += TTR("Custom debug template not found.") + "\n"; } - return valid; } - - bool dvalid = true; - bool rvalid = true; - - if (!FileAccess::exists(custom_debug_binary)) { - dvalid = false; - err += TTR("Custom debug template not found.") + "\n"; - } - - if (!FileAccess::exists(custom_release_binary)) { - rvalid = false; - err += TTR("Custom release template not found.") + "\n"; + if (p_preset->get("custom_template/release") != "") { + rvalid = FileAccess::exists(p_preset->get("custom_template/release")); + if (!rvalid) { + err += TTR("Custom release template not found.") + "\n"; + } } - if (dvalid || rvalid) - valid = true; + valid = dvalid || rvalid; + r_missing_templates = !valid; - if (!valid) { - r_error = err; - return valid; - } + // Validate the rest of the configuration. if (!_valid_resource_name(p_preset->get("package/short_name"))) { valid = false; diff --git a/platform/windows/godot_res.rc b/platform/windows/godot_res.rc index 1fa8957f15..0593c8b069 100644 --- a/platform/windows/godot_res.rc +++ b/platform/windows/godot_res.rc @@ -4,10 +4,6 @@ #define _MKSTR(m_x) _STR(m_x) #endif -#ifndef VERSION_PATCH -#define VERSION_PATCH 0 -#endif - GODOT_ICON ICON platform/windows/godot.ico 1 VERSIONINFO diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index cee848f270..3868d0bc63 100755 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -352,12 +352,14 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) { main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); + window_focused = true; alt_mem = false; control_mem = false; shift_mem = false; } else { // WM_INACTIVE input->release_pressed_events(); main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); + window_focused = false; alt_mem = false; }; @@ -2095,6 +2097,11 @@ bool OS_Windows::is_window_always_on_top() const { return video_mode.always_on_top; } +bool OS_Windows::is_window_focused() const { + + return window_focused; +} + void OS_Windows::set_console_visible(bool p_enabled) { if (console_visible == p_enabled) return; @@ -3372,6 +3379,7 @@ OS_Windows::OS_Windows(HINSTANCE _hInstance) { meta_mem = false; minimized = false; was_maximized = false; + window_focused = true; console_visible = IsWindowVisible(GetConsoleWindow()); //Note: Functions for pen input, available on Windows 8+ diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 20b61e3dbe..65d08f5d36 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -274,6 +274,7 @@ protected: bool maximized; bool minimized; bool borderless; + bool window_focused; bool console_visible; bool was_maximized; @@ -322,6 +323,7 @@ public: virtual bool is_window_maximized() const; virtual void set_window_always_on_top(bool p_enabled); virtual bool is_window_always_on_top() const; + virtual bool is_window_focused() const; virtual void set_console_visible(bool p_enabled); virtual bool is_console_visible() const; virtual void request_attention(); diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index b9cfd6f386..2f0d49e6dd 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -1684,6 +1684,10 @@ bool OS_X11::is_window_always_on_top() const { return current_videomode.always_on_top; } +bool OS_X11::is_window_focused() const { + return window_focused; +} + void OS_X11::set_borderless_window(bool p_borderless) { if (get_borderless_window() == p_borderless) @@ -2276,6 +2280,8 @@ void OS_X11::process_xevents() { minimized = false; window_has_focus = true; main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); + window_focused = true; + if (mouse_mode_grab) { // Show and update the cursor if confined and the window regained focus. if (mouse_mode == MOUSE_MODE_CONFINED) @@ -2303,6 +2309,7 @@ void OS_X11::process_xevents() { window_has_focus = false; input->release_pressed_events(); main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); + window_focused = false; if (mouse_mode_grab) { //dear X11, I try, I really try, but you never work, you do whathever you want. @@ -3497,6 +3504,7 @@ OS_X11::OS_X11() { xi.last_relative_time = 0; layered_window = false; minimized = false; + window_focused = true; xim_style = 0L; mouse_mode = MOUSE_MODE_VISIBLE; } diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index 35c55aab54..01e5aac3df 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -195,6 +195,7 @@ class OS_X11 : public OS_Unix { int video_driver_index; bool maximized; + bool window_focused; //void set_wm_border(bool p_enabled); void set_wm_fullscreen(bool p_enabled); void set_wm_above(bool p_enabled); @@ -284,6 +285,7 @@ public: virtual bool is_window_maximized() const; virtual void set_window_always_on_top(bool p_enabled); virtual bool is_window_always_on_top() const; + virtual bool is_window_focused() const; virtual void request_attention(); virtual void set_borderless_window(bool p_borderless); diff --git a/scene/3d/physics_body.cpp b/scene/3d/physics_body.cpp index 44dc3d0c26..c2860c25d8 100644 --- a/scene/3d/physics_body.cpp +++ b/scene/3d/physics_body.cpp @@ -1151,17 +1151,8 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve } } - Vector3 current_floor_velocity = floor_velocity; - if (on_floor && on_floor_body.is_valid()) { - //this approach makes sure there is less delay between the actual body velocity and the one we saved - PhysicsDirectBodyState *bs = PhysicsServer::get_singleton()->body_get_direct_state(on_floor_body); - if (bs) { - current_floor_velocity = bs->get_linear_velocity(); - } - } - // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky - Vector3 motion = (current_floor_velocity + body_velocity) * (Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time()); + Vector3 motion = (floor_velocity + body_velocity) * (Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time()); on_floor = false; on_floor_body = RID(); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index bab8d9167b..01f4070883 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -573,9 +573,7 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { } if (index < 0 || index >= presets.size()) return; - preset->set_tooltip("Color: #" + presets[index].to_html(presets[index].a < 1) + "\n" - "LMB: Set color\n" - "RMB: Remove preset"); + preset->set_tooltip(vformat(RTR("Color: #%s\nLMB: Set color\nRMB: Remove preset"), presets[index].to_html(presets[index].a < 1))); } } diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index d1650fea1e..be465751b6 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -777,14 +777,8 @@ void GraphEdit::_top_layer_draw() { } if (box_selecting) { - top_layer->draw_rect( - box_selecting_rect, - get_color("box_selection_fill_color", "Editor")); - - top_layer->draw_rect( - box_selecting_rect, - get_color("box_selection_stroke_color", "Editor"), - false); + top_layer->draw_rect(box_selecting_rect, get_color("selection_fill")); + top_layer->draw_rect(box_selecting_rect, get_color("selection_stroke"), false); } } diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 11172ac7a9..6b12947651 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -103,8 +103,7 @@ void Label::_notification(int p_what) { int lines_visible = (size.y + line_spacing) / font_h; - // ceiling to ensure autowrapping does not cut text - int space_w = Math::ceil(font->get_char_size(' ').width); + real_t space_w = font->get_char_size(' ').width; int chars_total = 0; int vbegin = 0, vsep = 0; @@ -225,6 +224,7 @@ void Label::_notification(int p_what) { return; } if (from->space_count) { + chars_total += from->space_count; /* spacing */ x_ofs += space_w * from->space_count; if (can_fill && align == ALIGN_FILL && spaces) { @@ -313,8 +313,8 @@ Size2 Label::get_minimum_size() const { int Label::get_longest_line_width() const { Ref<Font> font = get_font("font"); - int max_line_width = 0; - int line_width = 0; + real_t max_line_width = 0; + real_t line_width = 0; for (int i = 0; i < xl_text.size(); i++) { @@ -332,8 +332,7 @@ int Label::get_longest_line_width() const { } } else { - // ceiling to ensure autowrapping does not cut text - int char_width = Math::ceil(font->get_char_size(current, xl_text[i + 1]).width); + real_t char_width = font->get_char_size(current, xl_text[i + 1]).width; line_width += char_width; } } @@ -341,7 +340,8 @@ int Label::get_longest_line_width() const { if (line_width > max_line_width) max_line_width = line_width; - return max_line_width; + // ceiling to ensure autowrapping does not cut text + return Math::ceil(max_line_width); } int Label::get_line_count() const { @@ -388,12 +388,11 @@ void Label::regenerate_word_cache() { Ref<Font> font = get_font("font"); - int current_word_size = 0; + real_t current_word_size = 0; int word_pos = 0; - int line_width = 0; + real_t line_width = 0; int space_count = 0; - // ceiling to ensure autowrapping does not cut text - int space_width = Math::ceil(font->get_char_size(' ').width); + real_t space_width = font->get_char_size(' ').width; int line_spacing = get_constant("line_spacing"); line_count = 1; total_char_cache = 0; @@ -413,7 +412,7 @@ void Label::regenerate_word_cache() { bool separatable = (current >= 0x2E08 && current <= 0xFAFF) || (current >= 0xFE30 && current <= 0xFE4F); //current>=33 && (current < 65||current >90) && (current<97||current>122) && (current<48||current>57); bool insert_newline = false; - int char_width = 0; + real_t char_width = 0; if (current < 33) { @@ -454,8 +453,7 @@ void Label::regenerate_word_cache() { if (current_word_size == 0) { word_pos = i; } - // ceiling to ensure autowrapping does not cut text - char_width = Math::ceil(font->get_char_size(current, xl_text[i + 1]).width); + char_width = font->get_char_size(current, xl_text[i + 1]).width; current_word_size += char_width; line_width += char_width; total_char_cache++; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 5b7d7403ae..100c06955a 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -253,24 +253,25 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & } \ } -#define ENSURE_WIDTH(m_width) \ - if (p_mode == PROCESS_CACHE) { \ - l.maximum_width = MAX(l.maximum_width, MIN(p_width, wofs + m_width)); \ - l.minimum_width = MAX(l.minimum_width, m_width); \ - } \ - if (wofs + m_width > p_width) { \ - line_wrapped = true; \ - if (p_mode == PROCESS_CACHE) { \ - if (spaces > 0) \ - spaces -= 1; \ - } \ - if (p_mode == PROCESS_POINTER && r_click_item && p_click_pos.y >= p_ofs.y + y && p_click_pos.y <= p_ofs.y + y + lh && p_click_pos.x > p_ofs.x + wofs) { \ - if (r_outside) *r_outside = true; \ - *r_click_item = it; \ - *r_click_char = rchar; \ - RETURN; \ - } \ - NEW_LINE \ +#define ENSURE_WIDTH(m_width) \ + if (p_mode == PROCESS_CACHE) { \ + l.maximum_width = MAX(l.maximum_width, MIN(p_width, wofs + m_width)); \ + l.minimum_width = MAX(l.minimum_width, m_width); \ + } \ + if (wofs + m_width > p_width) { \ + line_wrapped = true; \ + if (p_mode == PROCESS_CACHE) { \ + if (spaces > 0) \ + spaces -= 1; \ + } \ + const bool x_in_range = (p_click_pos.x > p_ofs.x + wofs) && (!p_frame->cell || p_click_pos.x < p_ofs.x + p_width); \ + if (p_mode == PROCESS_POINTER && r_click_item && p_click_pos.y >= p_ofs.y + y && p_click_pos.y <= p_ofs.y + y + lh && x_in_range) { \ + if (r_outside) *r_outside = true; \ + *r_click_item = it; \ + *r_click_char = rchar; \ + RETURN; \ + } \ + NEW_LINE \ } #define ADVANCE(m_width) \ @@ -1063,10 +1064,10 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const int line = 0; Item *item = NULL; + bool outside; + ((RichTextLabel *)(this))->_find_click(main, p_pos, &item, &line, &outside); - ((RichTextLabel *)(this))->_find_click(main, p_pos, &item, &line); - - if (item && ((RichTextLabel *)(this))->_find_meta(item, NULL)) + if (item && !outside && ((RichTextLabel *)(this))->_find_meta(item, NULL)) return CURSOR_POINTING_HAND; return CURSOR_ARROW; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index d7e469ca26..6de2f0b570 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -7070,6 +7070,10 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_smooth_scroll_enabled"), &TextEdit::is_smooth_scroll_enabled); ClassDB::bind_method(D_METHOD("set_v_scroll_speed", "speed"), &TextEdit::set_v_scroll_speed); ClassDB::bind_method(D_METHOD("get_v_scroll_speed"), &TextEdit::get_v_scroll_speed); + ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &TextEdit::set_v_scroll); + ClassDB::bind_method(D_METHOD("get_v_scroll"), &TextEdit::get_v_scroll); + ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &TextEdit::set_h_scroll); + ClassDB::bind_method(D_METHOD("get_h_scroll"), &TextEdit::get_h_scroll); ClassDB::bind_method(D_METHOD("add_keyword_color", "keyword", "color"), &TextEdit::add_keyword_color); ClassDB::bind_method(D_METHOD("has_keyword_color", "keyword"), &TextEdit::has_keyword_color); @@ -7105,6 +7109,8 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hiding_enabled"), "set_hiding_enabled", "is_hiding_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_enabled"), "set_wrap_enabled", "is_wrap_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "scroll_vertical"), "set_v_scroll", "get_v_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll"); ADD_GROUP("Minimap", "minimap_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "draw_minimap", "is_drawing_minimap"); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 1e2b7c094e..350959dcc3 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -2680,7 +2680,8 @@ void Node::clear_internal_tree_resource_paths() { String Node::get_configuration_warning() const { - if (get_script_instance() && get_script_instance()->has_method("_get_configuration_warning")) { + if (get_script_instance() && get_script_instance()->get_script().is_valid() && + get_script_instance()->get_script()->is_tool() && get_script_instance()->has_method("_get_configuration_warning")) { return get_script_instance()->call("_get_configuration_warning"); } return String(); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 9e9a25883e..a56903636f 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -30,6 +30,7 @@ #include "viewport.h" +#include "core/core_string_names.h" #include "core/os/input.h" #include "core/os/os.h" #include "core/project_settings.h" @@ -251,6 +252,27 @@ void Viewport::_collision_object_input_event(CollisionObject *p_object, Camera * physics_last_id = id; } +void Viewport::_own_world_changed() { + ERR_FAIL_COND(world.is_null()); + ERR_FAIL_COND(own_world.is_null()); + + if (is_inside_tree()) { + _propagate_exit_world(this); + } + + own_world = world->duplicate(); + + if (is_inside_tree()) { + _propagate_enter_world(this); + } + + if (is_inside_tree()) { + VisualServer::get_singleton()->viewport_set_scenario(viewport, find_world()->get_scenario()); + } + + _update_listener(); +} + void Viewport::_notification(int p_what) { switch (p_what) { @@ -1105,8 +1127,21 @@ void Viewport::set_world(const Ref<World> &p_world) { if (is_inside_tree()) _propagate_exit_world(this); + if (own_world.is_valid() && world.is_valid()) { + world->disconnect(CoreStringNames::get_singleton()->changed, this, "_own_world_changed"); + } + world = p_world; + if (own_world.is_valid()) { + if (world.is_valid()) { + own_world = world->duplicate(); + world->connect(CoreStringNames::get_singleton()->changed, this, "_own_world_changed"); + } else { + own_world = Ref<World>(memnew(World)); + } + } + if (is_inside_tree()) _propagate_enter_world(this); @@ -2826,10 +2861,19 @@ void Viewport::set_use_own_world(bool p_world) { if (is_inside_tree()) _propagate_exit_world(this); - if (!p_world) + if (!p_world) { own_world = Ref<World>(); - else - own_world = Ref<World>(memnew(World)); + if (world.is_valid()) { + world->disconnect(CoreStringNames::get_singleton()->changed, this, "_own_world_changed"); + } + } else { + if (world.is_valid()) { + own_world = world->duplicate(); + world->connect(CoreStringNames::get_singleton()->changed, this, "_own_world_changed"); + } else { + own_world = Ref<World>(memnew(World)); + } + } if (is_inside_tree()) _propagate_enter_world(this); @@ -3178,6 +3222,8 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("_subwindow_visibility_changed"), &Viewport::_subwindow_visibility_changed); + ClassDB::bind_method(D_METHOD("_own_world_changed"), &Viewport::_own_world_changed); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "arvr"), "set_use_arvr", "use_arvr"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 396bbc91cd..79b606cda3 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -406,6 +406,8 @@ private: void _update_canvas_items(Node *p_node); + void _own_world_changed(); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 21fc9ccf6a..09d4505458 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -372,6 +372,28 @@ void register_scene_types() { ClassDB::register_class<AnimationPlayer>(); ClassDB::register_class<Tween>(); + ClassDB::register_class<AnimationTreePlayer>(); + ClassDB::register_class<AnimationTree>(); + ClassDB::register_class<AnimationNode>(); + ClassDB::register_class<AnimationRootNode>(); + ClassDB::register_class<AnimationNodeBlendTree>(); + ClassDB::register_class<AnimationNodeBlendSpace1D>(); + ClassDB::register_class<AnimationNodeBlendSpace2D>(); + ClassDB::register_class<AnimationNodeStateMachine>(); + ClassDB::register_class<AnimationNodeStateMachinePlayback>(); + + ClassDB::register_class<AnimationNodeStateMachineTransition>(); + ClassDB::register_class<AnimationNodeOutput>(); + ClassDB::register_class<AnimationNodeOneShot>(); + ClassDB::register_class<AnimationNodeAnimation>(); + ClassDB::register_class<AnimationNodeAdd2>(); + ClassDB::register_class<AnimationNodeAdd3>(); + ClassDB::register_class<AnimationNodeBlend2>(); + ClassDB::register_class<AnimationNodeBlend3>(); + ClassDB::register_class<AnimationNodeTimeScale>(); + ClassDB::register_class<AnimationNodeTimeSeek>(); + ClassDB::register_class<AnimationNodeTransition>(); + OS::get_singleton()->yield(); //may take time to init #ifndef _3D_DISABLED @@ -399,7 +421,6 @@ void register_scene_types() { ClassDB::register_class<GIProbeData>(); ClassDB::register_class<BakedLightmap>(); ClassDB::register_class<BakedLightmapData>(); - ClassDB::register_class<AnimationTreePlayer>(); ClassDB::register_class<Particles>(); ClassDB::register_class<CPUParticles>(); ClassDB::register_class<Position3D>(); @@ -410,27 +431,6 @@ void register_scene_types() { ClassDB::register_class<RootMotionView>(); ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor - ClassDB::register_class<AnimationTree>(); - ClassDB::register_class<AnimationNode>(); - ClassDB::register_class<AnimationRootNode>(); - ClassDB::register_class<AnimationNodeBlendTree>(); - ClassDB::register_class<AnimationNodeBlendSpace1D>(); - ClassDB::register_class<AnimationNodeBlendSpace2D>(); - ClassDB::register_class<AnimationNodeStateMachine>(); - ClassDB::register_class<AnimationNodeStateMachinePlayback>(); - - ClassDB::register_class<AnimationNodeStateMachineTransition>(); - ClassDB::register_class<AnimationNodeOutput>(); - ClassDB::register_class<AnimationNodeOneShot>(); - ClassDB::register_class<AnimationNodeAnimation>(); - ClassDB::register_class<AnimationNodeAdd2>(); - ClassDB::register_class<AnimationNodeAdd3>(); - ClassDB::register_class<AnimationNodeBlend2>(); - ClassDB::register_class<AnimationNodeBlend3>(); - ClassDB::register_class<AnimationNodeTimeScale>(); - ClassDB::register_class<AnimationNodeTimeSeek>(); - ClassDB::register_class<AnimationNodeTransition>(); - OS::get_singleton()->yield(); //may take time to init ClassDB::register_virtual_class<CollisionObject>(); diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 67351f07d2..cc76df62e5 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -851,6 +851,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("bg", "GraphEdit", make_stylebox(tree_bg_png, 4, 4, 4, 5)); theme->set_color("grid_minor", "GraphEdit", Color(1, 1, 1, 0.05)); theme->set_color("grid_major", "GraphEdit", Color(1, 1, 1, 0.2)); + theme->set_color("selection_fill", "GraphEdit", Color(1, 1, 1, 0.3)); + theme->set_color("selection_stroke", "GraphEdit", Color(1, 1, 1, 0.8)); theme->set_color("activity", "GraphEdit", Color(1, 1, 1)); theme->set_constant("bezier_len_pos", "GraphEdit", 80 * scale); theme->set_constant("bezier_len_neg", "GraphEdit", 160 * scale); diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp index 42fec3a0a7..10d871aa92 100644 --- a/scene/resources/dynamic_font.cpp +++ b/scene/resources/dynamic_font.cpp @@ -195,13 +195,13 @@ Error DynamicFontAtSize::_load() { if (FT_HAS_COLOR(face) && face->num_fixed_sizes > 0) { int best_match = 0; int diff = ABS(id.size - ((int64_t)face->available_sizes[0].width)); - scale_color_font = float(id.size) / face->available_sizes[0].width; + scale_color_font = float(id.size * oversampling) / face->available_sizes[0].width; for (int i = 1; i < face->num_fixed_sizes; i++) { int ndiff = ABS(id.size - ((int64_t)face->available_sizes[i].width)); if (ndiff < diff) { best_match = i; diff = ndiff; - scale_color_font = float(id.size) / face->available_sizes[i].width; + scale_color_font = float(id.size * oversampling) / face->available_sizes[i].width; } } FT_Select_Size(face, best_match); @@ -299,7 +299,7 @@ void DynamicFontAtSize::set_texture_flags(uint32_t p_flags) { } } -float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks, bool p_advance_only) const { +float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks, bool p_advance_only, bool p_outline) const { if (!valid) return 0; @@ -314,6 +314,20 @@ float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharT float advance = 0.0; + // use normal character size if there's no outline charater + if (p_outline && !ch->found) { + FT_GlyphSlot slot = face->glyph; + int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT); + if (!error) { + error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + if (!error) { + Character character = Character::not_found(); + character = const_cast<DynamicFontAtSize *>(this)->_bitmap_to_character(slot->bitmap, slot->bitmap_top, slot->bitmap_left, slot->advance.x / 64.0); + advance = character.advance; + } + } + } + if (ch->found) { ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), 0); @@ -875,7 +889,7 @@ float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_ // If requested outline draw, but no outline is present, simply return advance without drawing anything bool advance_only = p_outline && outline_cache_id.outline_size == 0; - return font_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, color, fallbacks, advance_only) + spacing_char; + return font_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, color, fallbacks, advance_only, p_outline) + spacing_char; } void DynamicFont::set_fallback(int p_idx, const Ref<DynamicFontData> &p_data) { diff --git a/scene/resources/dynamic_font.h b/scene/resources/dynamic_font.h index ece150c4ce..2dafd3ce4f 100644 --- a/scene/resources/dynamic_font.h +++ b/scene/resources/dynamic_font.h @@ -192,7 +192,7 @@ public: Size2 get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const; - float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks, bool p_advance_only = false) const; + float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks, bool p_advance_only = false, bool p_outline = false) const; void set_texture_flags(uint32_t p_flags); void update_oversampling(); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 13721191c0..0599920303 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -75,6 +75,7 @@ Ref<TriangleMesh> Mesh::generate_triangle_mesh() const { continue; Array a = surface_get_arrays(i); + ERR_FAIL_COND_V(a.empty(), Ref<TriangleMesh>()); int vc = surface_get_array_len(i); PoolVector<Vector3> vertices = a[ARRAY_VERTEX]; @@ -234,6 +235,7 @@ Ref<Shape> Mesh::create_convex_shape() const { for (int i = 0; i < get_surface_count(); i++) { Array a = surface_get_arrays(i); + ERR_FAIL_COND_V(a.empty(), Ref<ConvexPolygonShape>()); PoolVector<Vector3> v = a[ARRAY_VERTEX]; vertices.append_array(v); } @@ -273,6 +275,7 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { continue; Array a = surface_get_arrays(i); + ERR_FAIL_COND_V(a.empty(), Ref<ArrayMesh>()); if (i == 0) { arrays = a; @@ -378,6 +381,7 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { PoolVector<Vector3>::Write r = vertices.write(); if (indices.size()) { + ERR_FAIL_COND_V(indices.size() % 3 != 0, Ref<ArrayMesh>()); vc = indices.size(); ir = indices.write(); has_indices = true; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 5a2cd9a1c2..3e7d350eec 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -39,7 +39,7 @@ #include "scene/gui/control.h" #include "scene/main/instance_placeholder.h" -#define PACK_VERSION 2 +#define PACKED_SCENE_VERSION 2 bool SceneState::can_instance() const { @@ -1095,7 +1095,15 @@ void SceneState::set_bundled_scene(const Dictionary &p_dictionary) { if (p_dictionary.has("version")) version = p_dictionary["version"]; - ERR_FAIL_COND_MSG(version > PACK_VERSION, "Save format version too new."); + ERR_FAIL_COND_MSG(version > PACKED_SCENE_VERSION, "Save format version too new."); + + const int node_count = p_dictionary["node_count"]; + const PoolVector<int> snodes = p_dictionary["nodes"]; + ERR_FAIL_COND(snodes.size() < node_count); + + const int conn_count = p_dictionary["conn_count"]; + const PoolVector<int> sconns = p_dictionary["conns"]; + ERR_FAIL_COND(sconns.size() < conn_count); PoolVector<String> snames = p_dictionary["names"]; if (snames.size()) { @@ -1121,13 +1129,11 @@ void SceneState::set_bundled_scene(const Dictionary &p_dictionary) { variants.clear(); } - nodes.resize(p_dictionary["node_count"]); - int nc = nodes.size(); - if (nc) { - PoolVector<int> snodes = p_dictionary["nodes"]; + nodes.resize(node_count); + if (node_count) { PoolVector<int>::Read r = snodes.read(); int idx = 0; - for (int i = 0; i < nc; i++) { + for (int i = 0; i < node_count; i++) { NodeData &nd = nodes.write[i]; nd.parent = r[idx++]; nd.owner = r[idx++]; @@ -1151,15 +1157,11 @@ void SceneState::set_bundled_scene(const Dictionary &p_dictionary) { } } - connections.resize(p_dictionary["conn_count"]); - int cc = connections.size(); - - if (cc) { - - PoolVector<int> sconns = p_dictionary["conns"]; + connections.resize(conn_count); + if (conn_count) { PoolVector<int>::Read r = sconns.read(); int idx = 0; - for (int i = 0; i < cc; i++) { + for (int i = 0; i < conn_count; i++) { ConnectionData &cd = connections.write[i]; cd.from = r[idx++]; cd.to = r[idx++]; @@ -1283,9 +1285,7 @@ Dictionary SceneState::get_bundled_scene() const { d["base_scene"] = base_scene_idx; } - d["version"] = PACK_VERSION; - - //d["path"]=path; + d["version"] = PACKED_SCENE_VERSION; return d; } diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index c59fa8cab8..9408d1aa71 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -719,6 +719,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { bool rounded_corners = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0); bool aa_on = rounded_corners && anti_aliased; + float aa_size_grow = 0.5 * ((float)aa_size + 1.0); bool blend_on = blend_border && draw_border; @@ -744,7 +745,6 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { Rect2 border_style_rect = style_rect; if (aa_on) { - float aa_size_grow = 0.5 * ((aa_size + 1) / 2); for (int i = 0; i < 4; i++) { if (border_width[i] > 0) { border_style_rect = border_style_rect.grow_margin((Margin)i, -aa_size_grow); @@ -789,7 +789,6 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { } if (aa_on) { - float aa_size_grow = 0.5 * ((aa_size + 1) / 2); int aa_border_width[4]; int aa_fill_width[4]; if (draw_border) { @@ -804,6 +803,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { } } else { for (int i = 0; i < 4; i++) { + aa_border_width[i] = 0; aa_fill_width[i] = aa_size_grow; } } @@ -844,7 +844,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { } //COMPUTE UV COORDINATES - Rect2 uv_rect = style_rect.grow(aa_on ? aa_size : 0); + Rect2 uv_rect = style_rect.grow(aa_on ? aa_size_grow : 0); uvs.resize(verts.size()); for (int i = 0; i < verts.size(); i++) { uvs.write[i].x = (verts[i].x - uv_rect.position.x) / uv_rect.size.width; diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index dc2b708ae7..4d23f0eb41 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -2271,6 +2271,7 @@ void TextureLayered::create(uint32_t p_width, uint32_t p_height, uint32_t p_dept void TextureLayered::set_layer_data(const Ref<Image> &p_image, int p_layer) { ERR_FAIL_COND(!texture.is_valid()); + ERR_FAIL_COND(!p_image.is_valid()); VS::get_singleton()->texture_set_data(texture, p_image, p_layer); } @@ -2282,6 +2283,7 @@ Ref<Image> TextureLayered::get_layer_data(int p_layer) const { void TextureLayered::set_data_partial(const Ref<Image> &p_image, int p_x_ofs, int p_y_ofs, int p_z, int p_mipmap) { ERR_FAIL_COND(!texture.is_valid()); + ERR_FAIL_COND(!p_image.is_valid()); VS::get_singleton()->texture_set_data_partial(texture, p_image, 0, 0, p_image->get_width(), p_image->get_height(), p_x_ofs, p_y_ofs, p_mipmap, p_z); } diff --git a/scene/resources/world.cpp b/scene/resources/world.cpp index b498acd707..1099852098 100644 --- a/scene/resources/world.cpp +++ b/scene/resources/world.cpp @@ -268,12 +268,17 @@ RID World::get_scenario() const { } void World::set_environment(const Ref<Environment> &p_environment) { + if (environment == p_environment) { + return; + } environment = p_environment; if (environment.is_valid()) VS::get_singleton()->scenario_set_environment(scenario, environment->get_rid()); else VS::get_singleton()->scenario_set_environment(scenario, RID()); + + emit_changed(); } Ref<Environment> World::get_environment() const { @@ -282,12 +287,17 @@ Ref<Environment> World::get_environment() const { } void World::set_fallback_environment(const Ref<Environment> &p_environment) { + if (fallback_environment == p_environment) { + return; + } fallback_environment = p_environment; if (fallback_environment.is_valid()) VS::get_singleton()->scenario_set_fallback_environment(scenario, p_environment->get_rid()); else VS::get_singleton()->scenario_set_fallback_environment(scenario, RID()); + + emit_changed(); } Ref<Environment> World::get_fallback_environment() const { diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h index 74741a946c..0008b809b7 100644 --- a/servers/visual/rasterizer.h +++ b/servers/visual/rasterizer.h @@ -588,6 +588,8 @@ public: virtual int get_captured_render_info(VS::RenderInfo p_info) = 0; virtual int get_render_info(VS::RenderInfo p_info) = 0; + virtual String get_video_adapter_name() const = 0; + virtual String get_video_adapter_vendor() const = 0; static RasterizerStorage *base_singleton; RasterizerStorage(); diff --git a/servers/visual/visual_server_raster.cpp b/servers/visual/visual_server_raster.cpp index 4d680667cb..23736b5e63 100644 --- a/servers/visual/visual_server_raster.cpp +++ b/servers/visual/visual_server_raster.cpp @@ -153,6 +153,16 @@ int VisualServerRaster::get_render_info(RenderInfo p_info) { return VSG::storage->get_render_info(p_info); } +String VisualServerRaster::get_video_adapter_name() const { + + return VSG::storage->get_video_adapter_name(); +} + +String VisualServerRaster::get_video_adapter_vendor() const { + + return VSG::storage->get_video_adapter_vendor(); +} + /* TESTING */ void VisualServerRaster::set_boot_image(const Ref<Image> &p_image, const Color &p_color, bool p_scale, bool p_use_filter) { diff --git a/servers/visual/visual_server_raster.h b/servers/visual/visual_server_raster.h index 54c46b1812..1a1e86833e 100644 --- a/servers/visual/visual_server_raster.h +++ b/servers/visual/visual_server_raster.h @@ -680,6 +680,8 @@ public: /* STATUS INFORMATION */ virtual int get_render_info(RenderInfo p_info); + virtual String get_video_adapter_name() const; + virtual String get_video_adapter_vendor() const; virtual RID get_test_cube(); diff --git a/servers/visual/visual_server_wrap_mt.h b/servers/visual/visual_server_wrap_mt.h index f5875f4fad..9535c7f50d 100644 --- a/servers/visual/visual_server_wrap_mt.h +++ b/servers/visual/visual_server_wrap_mt.h @@ -602,6 +602,14 @@ public: return visual_server->get_render_info(p_info); } + virtual String get_video_adapter_name() const { + return visual_server->get_video_adapter_name(); + } + + virtual String get_video_adapter_vendor() const { + return visual_server->get_video_adapter_vendor(); + } + FUNC4(set_boot_image, const Ref<Image> &, const Color &, bool, bool) FUNC1(set_default_clear_color, const Color &) diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index 1f72848b15..b9b492e758 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -2032,6 +2032,8 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("init"), &VisualServer::init); ClassDB::bind_method(D_METHOD("finish"), &VisualServer::finish); ClassDB::bind_method(D_METHOD("get_render_info", "info"), &VisualServer::get_render_info); + ClassDB::bind_method(D_METHOD("get_video_adapter_name"), &VisualServer::get_video_adapter_name); + ClassDB::bind_method(D_METHOD("get_video_adapter_vendor"), &VisualServer::get_video_adapter_vendor); #ifndef _3D_DISABLED ClassDB::bind_method(D_METHOD("make_sphere_mesh", "latitudes", "longitudes", "radius"), &VisualServer::make_sphere_mesh); diff --git a/servers/visual_server.h b/servers/visual_server.h index 9aeee731b3..33a9e8d72b 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -1017,6 +1017,8 @@ public: }; virtual int get_render_info(RenderInfo p_info) = 0; + virtual String get_video_adapter_name() const = 0; + virtual String get_video_adapter_vendor() const = 0; /* Materials for 2D on 3D */ diff --git a/thirdparty/README.md b/thirdparty/README.md index 2ab0aedf1c..4c4bb4dfac 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -46,7 +46,7 @@ Files extracted from upstream source: ## enet - Upstream: http://enet.bespin.org -- Version: 1.3.13 +- Version: 1.3.14 (0eaf48e, 2019) - License: MIT Files extracted from upstream source: @@ -57,12 +57,14 @@ Files extracted from upstream source: Important: enet.h, host.c, protocol.c have been slightly modified to be usable by godot socket implementation and allow IPv6. +Apply the patch in the `patches/` folder when syncing on newer upstream +commits. + Two files (godot.cpp and enet/godot.h) have been added to provide enet socket implementation using Godot classes. + It is still possible to build against a system wide ENet but doing so will limit it's functionality to IPv4 only. -Check the diff of enet.h, protocol.c, and host.c with the 1.3.13 -tarball before the next update. ## etc2comp @@ -496,15 +498,13 @@ File extracted from upstream release tarball: ## xatlas - Upstream: https://github.com/jpcy/xatlas -- Version: git (b4b5426, 2019) +- Version: git (e12ea82, 2019) - License: MIT Files extracted from upstream source: - `xatlas.{cpp,h}` - -Note: License is marked as Public Domain in the files, but it was -later clarified upstream to MIT license. +- `LICENSE` ## zlib diff --git a/thirdparty/enet/LICENSE b/thirdparty/enet/LICENSE index 39af84a8f6..78d9dcf613 100644 --- a/thirdparty/enet/LICENSE +++ b/thirdparty/enet/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2002-2016 Lee Salzman +Copyright (c) 2002-2019 Lee Salzman 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: diff --git a/thirdparty/enet/enet/enet.h b/thirdparty/enet/enet/enet.h index 246cbb0a62..966e3a465d 100644 --- a/thirdparty/enet/enet/enet.h +++ b/thirdparty/enet/enet/enet.h @@ -22,7 +22,7 @@ extern "C" #define ENET_VERSION_MAJOR 1 #define ENET_VERSION_MINOR 3 -#define ENET_VERSION_PATCH 13 +#define ENET_VERSION_PATCH 14 #define ENET_VERSION_CREATE(major, minor, patch) (((major)<<16) | ((minor)<<8) | (patch)) #define ENET_VERSION_GET_MAJOR(version) (((version)>>16)&0xFF) #define ENET_VERSION_GET_MINOR(version) (((version)>>8)&0xFF) @@ -507,6 +507,17 @@ ENET_API int enet_socketset_select (ENetSocket, ENetSocketSet *, ENetSock /** @defgroup Address ENet address functions @{ */ + +/** Attempts to parse the printable form of the IP address in the parameter hostName + and sets the host field in the address parameter if successful. + @param address destination to store the parsed IP address + @param hostName IP address to parse + @retval 0 on success + @retval < 0 on failure + @returns the address of the given hostName in address on success +*/ +ENET_API int enet_address_set_host_ip (ENetAddress * address, const char * hostName); + /** Attempts to resolve the host named by the parameter hostName and sets the host field in the address parameter if successful. @param address destination to store resolved address diff --git a/thirdparty/enet/patches/ipv6_support.patch b/thirdparty/enet/patches/ipv6_support.patch new file mode 100644 index 0000000000..1f79863645 --- /dev/null +++ b/thirdparty/enet/patches/ipv6_support.patch @@ -0,0 +1,105 @@ +diff --git a/thirdparty/enet/enet/enet.h b/thirdparty/enet/enet/enet.h +index 650b199ee5..246cbb0a62 100644 +--- a/thirdparty/enet/enet/enet.h ++++ b/thirdparty/enet/enet/enet.h +@@ -10,13 +10,10 @@ extern "C" + { + #endif + ++#include <stdint.h> + #include <stdlib.h> + +-#ifdef _WIN32 +-#include "enet/win32.h" +-#else +-#include "enet/unix.h" +-#endif ++#include "enet/godot.h" + + #include "enet/types.h" + #include "enet/protocol.h" +@@ -72,7 +69,6 @@ typedef enum _ENetSocketShutdown + ENET_SOCKET_SHUTDOWN_READ_WRITE = 2 + } ENetSocketShutdown; + +-#define ENET_HOST_ANY 0 + #define ENET_HOST_BROADCAST 0xFFFFFFFFU + #define ENET_PORT_ANY 0 + +@@ -88,9 +84,11 @@ typedef enum _ENetSocketShutdown + */ + typedef struct _ENetAddress + { +- enet_uint32 host; ++ uint8_t host[16]; + enet_uint16 port; ++ uint8_t wildcard; + } ENetAddress; ++#define enet_host_equal(host_a, host_b) (memcmp(&host_a, &host_b,16) == 0) + + /** + * Packet flag bit constants. +@@ -519,6 +517,16 @@ ENET_API int enet_socketset_select (ENetSocket, ENetSocketSet *, ENetSock + */ + ENET_API int enet_address_set_host (ENetAddress * address, const char * hostName); + ++/** Sets the host field in the address parameter from ip struct. ++ @param address destination to store resolved address ++ @param ip the ip struct to read from ++ @param size the size of the ip struct. ++ @retval 0 on success ++ @retval != 0 on failure ++ @returns the address of the given ip in address on success. ++*/ ++ENET_API void enet_address_set_ip(ENetAddress * address, const uint8_t * ip, size_t size); ++ + /** Gives the printable form of the IP address specified in the address parameter. + @param address address printed + @param hostName destination for name, must not be NULL +diff --git a/thirdparty/enet/host.c b/thirdparty/enet/host.c +index 3be6c0922c..fc4da4ca67 100644 +--- a/thirdparty/enet/host.c ++++ b/thirdparty/enet/host.c +@@ -87,7 +87,7 @@ enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelL + host -> commandCount = 0; + host -> bufferCount = 0; + host -> checksum = NULL; +- host -> receivedAddress.host = ENET_HOST_ANY; ++ memset(host -> receivedAddress.host, 0, 16); + host -> receivedAddress.port = 0; + host -> receivedData = NULL; + host -> receivedDataLength = 0; +diff --git a/thirdparty/enet/protocol.c b/thirdparty/enet/protocol.c +index 29d648732d..ab26886de4 100644 +--- a/thirdparty/enet/protocol.c ++++ b/thirdparty/enet/protocol.c +@@ -298,7 +298,7 @@ enet_protocol_handle_connect (ENetHost * host, ENetProtocolHeader * header, ENet + } + else + if (currentPeer -> state != ENET_PEER_STATE_CONNECTING && +- currentPeer -> address.host == host -> receivedAddress.host) ++ enet_host_equal(currentPeer -> address.host, host -> receivedAddress.host)) + { + if (currentPeer -> address.port == host -> receivedAddress.port && + currentPeer -> connectID == command -> connect.connectID) +@@ -1010,9 +1010,8 @@ enet_protocol_handle_incoming_commands (ENetHost * host, ENetEvent * event) + + if (peer -> state == ENET_PEER_STATE_DISCONNECTED || + peer -> state == ENET_PEER_STATE_ZOMBIE || +- ((host -> receivedAddress.host != peer -> address.host || +- host -> receivedAddress.port != peer -> address.port) && +- peer -> address.host != ENET_HOST_BROADCAST) || ++ (!enet_host_equal(host -> receivedAddress.host, peer -> address.host) || ++ host -> receivedAddress.port != peer -> address.port) || + (peer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID && + sessionID != peer -> incomingSessionID)) + return 0; +@@ -1054,7 +1053,7 @@ enet_protocol_handle_incoming_commands (ENetHost * host, ENetEvent * event) + + if (peer != NULL) + { +- peer -> address.host = host -> receivedAddress.host; ++ enet_address_set_ip(&(peer -> address), host -> receivedAddress.host, 16); + peer -> address.port = host -> receivedAddress.port; + peer -> incomingDataTotal += host -> receivedDataLength; + } diff --git a/thirdparty/enet/protocol.c b/thirdparty/enet/protocol.c index cbeea1a763..28ad5fc41c 100644 --- a/thirdparty/enet/protocol.c +++ b/thirdparty/enet/protocol.c @@ -9,7 +9,6 @@ #include "enet/time.h" #include "enet/enet.h" - static size_t commandSizes [ENET_PROTOCOL_COMMAND_COUNT] = { 0, @@ -164,7 +163,10 @@ enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer) { ENetOutgoingCommand * outgoingCommand; - while (! enet_list_empty (& peer -> sentUnreliableCommands)) + if (enet_list_empty (& peer -> sentUnreliableCommands)) + return; + + do { outgoingCommand = (ENetOutgoingCommand *) enet_list_front (& peer -> sentUnreliableCommands); @@ -183,7 +185,13 @@ enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer) } enet_free (outgoingCommand); - } + } while (! enet_list_empty (& peer -> sentUnreliableCommands)); + + if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER && + enet_list_empty (& peer -> outgoingReliableCommands) && + enet_list_empty (& peer -> outgoingUnreliableCommands) && + enet_list_empty (& peer -> sentReliableCommands)) + enet_peer_disconnect (peer, peer -> eventData); } static ENetProtocolCommand @@ -1406,7 +1414,8 @@ enet_protocol_send_unreliable_outgoing_commands (ENetHost * host, ENetPeer * pee if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER && enet_list_empty (& peer -> outgoingReliableCommands) && enet_list_empty (& peer -> outgoingUnreliableCommands) && - enet_list_empty (& peer -> sentReliableCommands)) + enet_list_empty (& peer -> sentReliableCommands) && + enet_list_empty (& peer -> sentUnreliableCommands)) enet_peer_disconnect (peer, peer -> eventData); } @@ -1691,7 +1700,7 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch & host -> buffers [1], host -> bufferCount - 1, originalSize, host -> packetData [1], - originalSize); + originalSize); if (compressedSize > 0 && compressedSize < originalSize) { host -> headerFlags |= ENET_PROTOCOL_HEADER_FLAG_COMPRESSED; diff --git a/thirdparty/xatlas/LICENSE b/thirdparty/xatlas/LICENSE index 94d0c60485..9e61e15fb7 100644 --- a/thirdparty/xatlas/LICENSE +++ b/thirdparty/xatlas/LICENSE @@ -1,14 +1,21 @@ -xatlas -https://github.com/jpcy/xatlas -Copyright (c) 2018 Jonathan Young +MIT License -thekla_atlas -https://github.com/Thekla/thekla_atlas -Copyright (c) 2013 Thekla, Inc -Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com> +Copyright (c) 2018-2019 Jonathan Young -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: +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 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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
\ No newline at end of file diff --git a/thirdparty/xatlas/xatlas.cpp b/thirdparty/xatlas/xatlas.cpp index 56794211a6..80cacf9746 100644 --- a/thirdparty/xatlas/xatlas.cpp +++ b/thirdparty/xatlas/xatlas.cpp @@ -33,7 +33,6 @@ https://github.com/brandonpelfrey/Fast-BVH MIT License Copyright (c) 2012 Brandon Pelfrey */ -#include <algorithm> #include <atomic> #include <condition_variable> #include <mutex> @@ -114,22 +113,25 @@ Copyright (c) 2012 Brandon Pelfrey #define XA_UNUSED(a) ((void)(a)) -#define XA_GROW_CHARTS_COPLANAR 1 #define XA_MERGE_CHARTS 1 #define XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION 0.5f #define XA_RECOMPUTE_CHARTS 1 -#define XA_SKIP_PARAMETERIZATION 0 // Use the orthogonal parameterization from segment::Atlas #define XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION 0 +#define XA_FIX_INTERNAL_BOUNDARY_LOOPS 1 +#define XA_PRINT_CHART_WARNINGS 0 #define XA_DEBUG_HEAP 0 #define XA_DEBUG_SINGLE_CHART 0 #define XA_DEBUG_EXPORT_ATLAS_IMAGES 0 +#define XA_DEBUG_EXPORT_ATLAS_IMAGES_PER_CHART 0 // Export an atlas image after each chart is added. +#define XA_DEBUG_EXPORT_BOUNDARY_GRID 0 +#define XA_DEBUG_EXPORT_TGA (XA_DEBUG_EXPORT_ATLAS_IMAGES || XA_DEBUG_EXPORT_BOUNDARY_GRID) #define XA_DEBUG_EXPORT_OBJ_SOURCE_MESHES 0 #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_NOT_DISK 0 #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 @@ -137,10 +139,10 @@ Copyright (c) 2012 Brandon Pelfrey #define XA_DEBUG_EXPORT_OBJ (0 \ || XA_DEBUG_EXPORT_OBJ_SOURCE_MESHES \ || 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_NOT_DISK \ || XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION \ || XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION \ || XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS) @@ -166,6 +168,10 @@ struct MemTag enum { Default, + BitImage, + BVH, + FullVector, + Matrix, Mesh, MeshBoundaries, MeshColocals, @@ -174,6 +180,10 @@ struct MemTag MeshNormals, MeshPositions, MeshTexcoords, + SegmentAtlasChartCandidates, + SegmentAtlasChartFaces, + SegmentAtlasMeshData, + SegmentAtlasPlanarRegions, Count }; }; @@ -192,7 +202,7 @@ struct AllocHeader static std::mutex s_allocMutex; static AllocHeader *s_allocRoot = nullptr; -static size_t s_allocTotalSize = 0, s_allocPeakSize = 0, s_allocTotalTagSize[MemTag::Count] = { 0 }, s_allocPeakTagSize[MemTag::Count] = { 0 }; +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 constexpr uint32_t kAllocRedzone = 0x12345678; @@ -245,9 +255,11 @@ static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line s_allocRoot = header; header->next->prev = header; } + s_allocTotalCount++; s_allocTotalSize += size; if (s_allocTotalSize > s_allocPeakSize) s_allocPeakSize = s_allocTotalSize; + s_allocCount[tag]++; s_allocTotalTagSize[tag] += size; if (s_allocTotalTagSize[tag] > s_allocPeakTagSize[tag]) s_allocPeakTagSize[tag] = s_allocTotalTagSize[tag]; @@ -287,9 +299,14 @@ static void ReportLeaks() 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 "Default", + "BitImage", + "BVH", + "FullVector", + "Matrix", "Mesh", "MeshBoundaries", "MeshColocals", @@ -297,10 +314,14 @@ static void PrintMemoryUsage() "MeshIndices", "MeshNormals", "MeshPositions", - "MeshTexcoords" + "MeshTexcoords", + "SegmentAtlasChartCandidates", + "SegmentAtlasChartFaces", + "SegmentAtlasMeshData", + "SegmentAtlasPlanarRegions" }; for (int i = 0; i < MemTag::Count; i++) { - XA_PRINT(" %s: %0.2fMB current, %0.2fMB peak\n", labels[i], internal::s_allocTotalTagSize[i] / 1024.0f / 1024.0f, internal::s_allocPeakTagSize[i] / 1024.0f / 1024.0f); + XA_PRINT(" %s: %zu allocations, %0.2fMB current, %0.2fMB peak\n", labels[i], internal::s_allocCount[i], internal::s_allocTotalTagSize[i] / 1024.0f / 1024.0f, internal::s_allocPeakTagSize[i] / 1024.0f / 1024.0f); } } @@ -308,7 +329,9 @@ static void PrintMemoryUsage() #else static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/, int /*line*/) { - if (ptr && size == 0 && s_free) { + if (size == 0 && !ptr) + return nullptr; + if (size == 0 && s_free) { s_free(ptr); return nullptr; } @@ -333,7 +356,6 @@ struct ProfileData std::atomic<clock_t> addMeshThread; std::atomic<clock_t> addMeshCreateColocals; std::atomic<clock_t> addMeshCreateFaceGroups; - std::atomic<clock_t> addMeshCreateBoundaries; std::atomic<clock_t> addMeshCreateChartGroupsReal; std::atomic<clock_t> addMeshCreateChartGroupsThread; clock_t computeChartsReal; @@ -362,7 +384,6 @@ struct ProfileData clock_t packChartsRasterize; clock_t packChartsDilate; clock_t packChartsFindLocation; - std::atomic<clock_t> packChartsFindLocationThread; clock_t packChartsBlit; clock_t buildOutputMeshes; }; @@ -386,6 +407,7 @@ static double clockToSeconds(clock_t c) static constexpr float kPi = 3.14159265358979323846f; static constexpr float kPi2 = 6.28318530717958647692f; +static constexpr float kPi4 = 12.56637061435917295384f; static constexpr float kEpsilon = 0.0001f; static constexpr float kAreaEpsilon = FLT_EPSILON; static constexpr float kNormalEpsilon = 0.001f; @@ -430,11 +452,9 @@ static T clamp(const T &x, const T &a, const T &b) template <typename T> static void swap(T &a, T &b) { - T temp; - temp = a; + T temp = a; a = b; b = temp; - temp = T(); } union FloatUint32 @@ -620,6 +640,14 @@ static Vector2 normalize(const Vector2 &v, float epsilon) 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 bool equal(const Vector2 &v1, const Vector2 &v2, float epsilon) { return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon); @@ -640,23 +668,19 @@ static bool isFinite(const Vector2 &v) return isFinite(v.x) && isFinite(v.y); } -// Note, this is the area scaled by 2! -static float triangleArea(const Vector2 &v0, const Vector2 &v1) -{ - return (v0.x * v1.y - v0.y * v1.x); // * 0.5f; -} - 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; + //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 // numbers and the results becomes very unstable and dependent on the order of the factors. // Instead, it's preferable to subtract the vertices first, and multiply the resulting small values together. The result // in this case is always much more accurate (as long as the triangle is small) and less dependent of the location of // the triangle. - //return ((a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)); // * 0.5f; - return triangleArea(a - c, b - c); + //return ((a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)) * 0.5f; + const Vector2 v0 = a - c; + const Vector2 v1 = b - 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) @@ -746,11 +770,6 @@ public: float x, y, z; }; -static bool operator!=(const Vector3 &a, const Vector3 &b) -{ - return a.x != b.x || a.y != b.y || a.z != b.z; -} - static Vector3 operator+(const Vector3 &a, const Vector3 &b) { return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); @@ -837,6 +856,33 @@ bool isFinite(const Vector3 &v) } #endif +struct Extents2 +{ + Vector2 min, max; + + void reset() + { + min.x = min.y = FLT_MAX; + max.x = max.y = -FLT_MAX; + } + + void add(Vector2 p) + { + min = xatlas::internal::min(min, p); + max = xatlas::internal::max(max, p); + } + + Vector2 midpoint() const + { + return Vector2(min.x + (max.x - min.x) * 0.5f, min.y + (max.y - min.y) * 0.5f); + } + + static bool intersect(Extents2 e1, 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; @@ -979,7 +1025,14 @@ struct AABB struct ArrayBase { - ArrayBase(uint32_t elementSize, int memTag = MemTag::Default) : buffer(nullptr), elementSize(elementSize), size(0), capacity(0), memTag(memTag) {} + ArrayBase(uint32_t elementSize, int memTag = MemTag::Default) : buffer(nullptr), elementSize(elementSize), size(0), capacity(0) + { +#if XA_DEBUG_HEAP + this->memTag = memTag; +#else + XA_UNUSED(memTag); +#endif + } ~ArrayBase() { @@ -991,6 +1044,12 @@ struct ArrayBase size = 0; } + void copyFrom(const uint8_t *data, uint32_t length) + { + resize(length, true); + memcpy(buffer, data, length * elementSize); + } + void copyTo(ArrayBase &other) const { XA_DEBUG_ASSERT(elementSize == other.elementSize); @@ -1025,7 +1084,9 @@ struct ArrayBase other.elementSize = elementSize; other.size = size; other.capacity = capacity; +#if XA_DEBUG_HEAP other.memTag = memTag; +#endif buffer = nullptr; elementSize = size = capacity = 0; } @@ -1043,6 +1104,16 @@ struct ArrayBase memcpy(&buffer[(size - 1) * elementSize], value, elementSize); } + 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); + } + // Remove the element at the given index. This is an expensive operation! void removeAt(uint32_t index) { @@ -1083,16 +1154,29 @@ struct ArrayBase } } else { // realloc the buffer +#if XA_DEBUG_HEAP buffer = XA_REALLOC_SIZE(memTag, buffer, newCapacity * elementSize); +#else + buffer = XA_REALLOC_SIZE(MemTag::Default, buffer, newCapacity * elementSize); +#endif } capacity = newCapacity; } +#if XA_DEBUG_HEAP + void setMemTag(int memTag) + { + this->memTag = memTag; + } +#endif + uint8_t *buffer; uint32_t elementSize; uint32_t size; uint32_t capacity; +#if XA_DEBUG_HEAP int memTag; +#endif }; template<typename T> @@ -1101,7 +1185,7 @@ class Array public: Array(int memTag = MemTag::Default) : m_base(sizeof(T), memTag) {} Array(const Array&) = delete; - const Array &operator=(const Array &) = delete; + Array &operator=(const Array &) = delete; XA_INLINE const T &operator[](uint32_t index) const { @@ -1123,6 +1207,17 @@ public: XA_INLINE T *begin() { return (T *)m_base.buffer; } XA_INLINE void clear() { m_base.clear(); } + + 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; + } + return false; + } + + void copyFrom(const T *data, uint32_t length) { m_base.copyFrom((const uint8_t *)data, length); } void copyTo(Array &other) const { m_base.copyTo(other.m_base); } XA_INLINE const T *data() const { return (const T *)m_base.buffer; } XA_INLINE T *data() { return (T *)m_base.buffer; } @@ -1131,11 +1226,24 @@ public: void insertAt(uint32_t index, const T &value) { m_base.insertAt(index, (const uint8_t *)&value); } void moveTo(Array &other) { m_base.moveTo(other.m_base); } void push_back(const T &value) { m_base.push_back((const uint8_t *)&value); } + void push_back(const Array &other) { m_base.push_back(other.m_base); } void pop_back() { m_base.pop_back(); } void removeAt(uint32_t index) { m_base.removeAt(index); } void reserve(uint32_t desiredSize) { m_base.reserve(desiredSize); } void resize(uint32_t newSize) { m_base.resize(newSize, true); } + void runCtors() + { + for (uint32_t i = 0; i < m_base.size; i++) + new (&((T *)m_base.buffer)[i]) T; + } + + void runDtors() + { + for (uint32_t i = 0; i < m_base.size; i++) + ((T *)m_base.buffer)[i].~T(); + } + void setAll(const T &value) { auto buffer = (T *)m_base.buffer; @@ -1143,6 +1251,10 @@ public: buffer[i] = value; } +#if XA_DEBUG_HEAP + void setMemTag(int memTag) { m_base.setMemTag(memTag); } +#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); } @@ -1150,6 +1262,28 @@ private: ArrayBase m_base; }; +template<typename T> +struct ArrayView +{ + 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]; } + T *data; + uint32_t length; +}; + +template<typename T> +struct ConstArrayView +{ + 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]; } + 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 { @@ -1197,40 +1331,34 @@ public: m_wordArray.resize((m_size + 31) >> 5); } - /// Get bit. - bool bitAt(uint32_t b) const + bool get(uint32_t index) const { - XA_DEBUG_ASSERT( b < m_size ); - return (m_wordArray[b >> 5] & (1 << (b & 31))) != 0; + XA_DEBUG_ASSERT(index < m_size); + return (m_wordArray[index >> 5] & (1 << (index & 31))) != 0; } - // Set a bit. - void setBitAt(uint32_t idx) + void set(uint32_t index) { - XA_DEBUG_ASSERT(idx < m_size); - m_wordArray[idx >> 5] |= (1 << (idx & 31)); + XA_DEBUG_ASSERT(index < m_size); + m_wordArray[index >> 5] |= (1 << (index & 31)); } - // Clear all the bits. - void clearAll() + void zeroOutMemory() { - memset(m_wordArray.data(), 0, m_wordArray.size() * sizeof(uint32_t)); + m_wordArray.zeroOutMemory(); } private: - // Number of bits stored. - uint32_t m_size; - - // Array of bits. + uint32_t m_size; // Number of bits stored. Array<uint32_t> m_wordArray; }; class BitImage { public: - BitImage() : m_width(0), m_height(0), m_rowStride(0) {} + 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) + 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); @@ -1238,7 +1366,7 @@ public: } BitImage(const BitImage &other) = delete; - const BitImage &operator=(const BitImage &other) = delete; + BitImage &operator=(const BitImage &other) = delete; uint32_t width() const { return m_width; } uint32_t height() const { return m_height; } @@ -1275,22 +1403,22 @@ public: m_rowStride = rowStride; } - bool bitAt(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 setBitAt(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(bitAt(x, y)); + XA_DEBUG_ASSERT(get(x, y)); } - void clearAll() + void zeroOutMemory() { m_data.zeroOutMemory(); } @@ -1324,26 +1452,26 @@ public: { BitImage tmp(m_width, m_height); for (uint32_t p = 0; p < padding; p++) { - tmp.clearAll(); + tmp.zeroOutMemory(); for (uint32_t y = 0; y < m_height; y++) { for (uint32_t x = 0; x < m_width; x++) { - bool b = bitAt(x, y); + bool b = get(x, y); if (!b) { if (x > 0) { - b |= bitAt(x - 1, y); - if (y > 0) b |= bitAt(x - 1, y - 1); - if (y < m_height - 1) b |= bitAt(x - 1, y + 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 |= bitAt(x, y - 1); - if (y < m_height - 1) b |= bitAt(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 |= bitAt(x + 1, y); - if (y > 0) b |= bitAt(x + 1, y - 1); - if (y < m_height - 1) b |= bitAt(x + 1, y + 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 (b) - tmp.setBitAt(x, y); + tmp.set(x, y); } } tmp.m_data.copyTo(m_data); @@ -1361,7 +1489,7 @@ private: class BVH { public: - BVH(const Array<AABB> &objectAabbs, uint32_t leafSize = 4) + BVH(const Array<AABB> &objectAabbs, uint32_t leafSize = 4) : m_objectIds(MemTag::BVH), m_nodes(MemTag::BVH) { m_objectAabbs = &objectAabbs; if (m_objectAabbs->isEmpty()) @@ -1494,9 +1622,118 @@ private: Array<Node> m_nodes; }; -class Fit +struct Fit { -public: + static bool computeBasis(const Vector3 *points, uint32_t pointsCount, Basis *basis) + { + if (computeLeastSquaresNormal(points, pointsCount, &basis->normal)) { + basis->tangent = Basis::computeTangent(basis->normal); + basis->bitangent = Basis::computeBitangent(basis->normal, basis->tangent); + return true; + } + return computeEigen(points, pointsCount, basis); + } + +private: + // Fit a plane to a collection of points. + // 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); + return true; + } + const float invN = 1.0f / float(pointsCount); + Vector3 centroid(0.0f); + for (uint32_t i = 0; i < pointsCount; 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++) { + Vector3 r = points[i] - centroid; + xx += r.x * r.x; + xy += r.x * r.y; + xz += r.x * r.z; + yy += r.y * r.y; + yz += r.y * r.z; + zz += r.z * r.z; + } +#if 0 + xx *= invN; + xy *= invN; + xz *= invN; + yy *= invN; + yz *= invN; + zz *= invN; + Vector3 weighted_dir(0.0f); + { + float det_x = yy * zz - yz * yz; + const Vector3 axis_dir(det_x, xz * yz - xy * zz, xy * yz - xz * yy); + float weight = det_x * det_x; + if (dot(weighted_dir, axis_dir) < 0.0f) + weight = -weight; + weighted_dir += axis_dir * weight; + } + { + float det_y = xx * zz - xz * xz; + const Vector3 axis_dir(xz * yz - xy * zz, det_y, xy * xz - yz * xx); + float weight = det_y * det_y; + if (dot(weighted_dir, axis_dir) < 0.0f) + weight = -weight; + weighted_dir += axis_dir * weight; + } + { + float det_z = xx * yy - xy * xy; + const Vector3 axis_dir(xy * yz - xz * yy, xy * xz - yz * xx, det_z); + float weight = det_z * det_z; + if (dot(weighted_dir, axis_dir) < 0.0f) + weight = -weight; + weighted_dir += axis_dir * weight; + } + *normal = normalize(weighted_dir, kEpsilon); +#else + const float det_x = yy * zz - yz * yz; + const float det_y = xx * zz - xz * xz; + const float det_z = xx * yy - xy * xy; + const float det_max = max(det_x, max(det_y, det_z)); + if (det_max <= 0.0f) + return false; // The points don't span a plane + // 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); + else if (det_max == det_y) + dir = Vector3(xz * yz - xy * zz, det_y, xy * xz - yz * xx); + else if (det_max == det_z) + dir = Vector3(xy * yz - xz * yy, xy * xz - yz * xx, det_z); + const float len = length(dir); + if (isZero(len, kEpsilon)) + return false; + *normal = dir * (1.0f / len); +#endif + return isNormalized(*normal); + } + + static bool computeEigen(const Vector3 *points, uint32_t pointsCount, Basis *basis) + { + float matrix[6]; + computeCovariance(pointsCount, 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); + return true; + } + static Vector3 computeCentroid(int n, const Vector3 * points) { Vector3 centroid(0.0f); @@ -1694,9 +1931,9 @@ private: class FullVector { public: - FullVector(uint32_t dim) { m_array.resize(dim); } - FullVector(const FullVector &v) { v.m_array.copyTo(m_array); } - const FullVector &operator=(const FullVector &v) = delete; + FullVector(uint32_t dim) : m_array(MemTag::FullVector) { m_array.resize(dim); } + FullVector(const FullVector &v) : m_array(MemTag::FullVector) { v.m_array.copyTo(m_array); } + FullVector &operator=(const FullVector &v) = delete; XA_INLINE uint32_t dimension() const { return m_array.size(); } XA_INLINE const float &operator[](uint32_t index) const { return m_array[index]; } XA_INLINE float &operator[](uint32_t index) { return m_array[index]; } @@ -1767,7 +2004,10 @@ private: void alloc() { XA_DEBUG_ASSERT(m_size > 0); - m_numSlots = (uint32_t)(m_size * 1.3); + m_numSlots = nextPowerOfTwo(m_size); + auto minNumSlots = uint32_t(m_size * 1.3); + if (m_numSlots < minNumSlots) + m_numSlots = nextPowerOfTwo(minNumSlots); m_slots = XA_ALLOC_ARRAY(m_memTag, uint32_t, m_numSlots); for (uint32_t i = 0; i < m_numSlots; i++) m_slots[i] = UINT32_MAX; @@ -1778,7 +2018,7 @@ private: uint32_t computeHash(const Key &key) const { H hash; - return hash(key) % m_numSlots; + return hash(key) & (m_numSlots - 1); } int m_memTag; @@ -1806,6 +2046,16 @@ static void insertionSort(T *data, uint32_t length) class KISSRng { public: + KISSRng() { reset(); } + + void reset() + { + x = 123456789; + y = 362436000; + z = 521288629; + c = 7654321; + } + uint32_t getRange(uint32_t range) { if (range == 0) @@ -1820,7 +2070,7 @@ public: } private: - uint32_t x = 123456789, y = 362436000, z = 521288629, c = 7654321; + uint32_t x, y, z, c; }; // Based on Pierre Terdiman's and Michael Herf's source code. @@ -2009,15 +2259,27 @@ private: class BoundingBox2D { public: - Vector2 majorAxis() const { return m_majorAxis; } - Vector2 minorAxis() const { return m_minorAxis; } - Vector2 minCorner() const { return m_minCorner; } - Vector2 maxCorner() const { return m_maxCorner; } + Vector2 majorAxis, minorAxis, minCorner, maxCorner; + + void clear() + { + m_boundaryVertices.clear(); + } + + 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. - void compute(const Vector2 *boundaryVertices, uint32_t boundaryVertexCount, const Vector2 *vertices, uint32_t vertexCount) + // If vertices is null or vertexCount is 0, the boundary vertices are used. + void compute(const Vector2 *vertices = nullptr, uint32_t vertexCount = 0) { - convexHull(boundaryVertices, boundaryVertexCount, m_hull, 0.00001f); + if (!vertices || vertexCount == 0) { + vertices = m_boundaryVertices.data(); + vertexCount = m_boundaryVertices.size(); + } + convexHull(m_boundaryVertices.data(), m_boundaryVertices.size(), 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); @@ -2051,11 +2313,11 @@ public: best_axis = axis; } } - m_majorAxis = best_axis; - m_minorAxis = Vector2(-best_axis.y, best_axis.x); - m_minCorner = best_min; - m_maxCorner = best_max; - XA_ASSERT(isFinite(m_majorAxis) && isFinite(m_minorAxis) && isFinite(m_minCorner)); + majorAxis = best_axis; + minorAxis = Vector2(-best_axis.y, best_axis.x); + minCorner = best_min; + maxCorner = best_max; + XA_ASSERT(isFinite(majorAxis) && isFinite(minorAxis) && isFinite(minCorner)); } private: @@ -2088,6 +2350,7 @@ private: } // Filter top list. output.clear(); + 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(); ) { @@ -2103,6 +2366,7 @@ private: } } uint32_t top_count = output.size(); + 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(); ) { @@ -2122,9 +2386,9 @@ private: output.pop_back(); } + Array<Vector2> m_boundaryVertices; Array<float> m_coords; Array<Vector2> m_top, m_bottom, m_hull; - Vector2 m_majorAxis, m_minorAxis, m_minCorner, m_maxCorner; }; static uint32_t meshEdgeFace(uint32_t edge) { return edge / 3; } @@ -2152,7 +2416,7 @@ static void meshGetBoundaryLoops(const Mesh &mesh, Array<uint32_t> &boundaryLoop 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_faceGroups(MemTag::Mesh), m_indices(MemTag::MeshIndices), m_positions(MemTag::MeshPositions), m_normals(MemTag::MeshNormals), m_texcoords(MemTag::MeshTexcoords), m_colocalVertexCount(0), m_nextColocalVertex(MemTag::MeshColocals), m_boundaryVertices(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_ignoredFaceCount(0), m_indices(MemTag::MeshIndices), m_positions(MemTag::MeshPositions), m_normals(MemTag::MeshNormals), m_texcoords(MemTag::MeshTexcoords), m_faceGroups(MemTag::Mesh), m_faceGroupFirstFace(MemTag::Mesh), m_faceGroupNextFace(MemTag::Mesh), m_faceGroupFaceCounts(MemTag::Mesh), m_colocalVertexCount(0), m_nextColocalVertex(MemTag::MeshColocals), m_boundaryEdges(MemTag::MeshBoundaries), m_oppositeEdges(MemTag::MeshBoundaries), m_nextBoundaryEdges(MemTag::MeshBoundaries), m_edgeMap(MemTag::MeshEdgeMap, approxFaceCount * 3) { m_indices.reserve(approxFaceCount * 3); m_positions.reserve(approxVertexCount); @@ -2165,6 +2429,7 @@ public: m_normals.reserve(approxVertexCount); } + static constexpr uint16_t kInvalidFaceGroup = UINT16_MAX; uint32_t flags() const { return m_flags; } uint32_t id() const { return m_id; } @@ -2199,9 +2464,12 @@ public: { AddFaceResult::Enum result = AddFaceResult::OK; if (m_flags & MeshFlags::HasFaceGroups) - m_faceGroups.push_back(UINT32_MAX); - if (m_flags & MeshFlags::HasIgnoredFaces) + m_faceGroups.push_back(kInvalidFaceGroup); + if (m_flags & MeshFlags::HasIgnoredFaces) { m_faceIgnore.push_back(ignore); + if (ignore) + m_ignoredFaceCount++; + } const uint32_t firstIndex = m_indices.size(); for (uint32_t i = 0; i < 3; i++) m_indices.push_back(indices[i]); @@ -2221,13 +2489,13 @@ public: void createColocals() { const uint32_t vertexCount = m_positions.size(); - Array<AABB> aabbs; + Array<AABB> aabbs(MemTag::BVH); aabbs.resize(vertexCount); for (uint32_t i = 0; i < m_positions.size(); i++) aabbs[i] = AABB(m_positions[i], m_epsilon); BVH bvh(aabbs); - Array<uint32_t> colocals; - Array<uint32_t> potential; + Array<uint32_t> colocals(MemTag::MeshColocals); + Array<uint32_t> potential(MemTag::MeshColocals); m_colocalVertexCount = 0; m_nextColocalVertex.resize(vertexCount); for (uint32_t i = 0; i < vertexCount; i++) @@ -2259,7 +2527,7 @@ public: } // Check if the face duplicates any edges of any face already in the group. - bool faceDuplicatesGroupEdge(uint32_t group, uint32_t face) const + bool faceDuplicatesGroupEdge(uint16_t group, uint32_t face) const { for (FaceEdgeIterator edgeIt(this, face); !edgeIt.isDone(); edgeIt.advance()) { for (ColocalEdgeIterator colocalEdgeIt(this, edgeIt.vertex0(), edgeIt.vertex1()); !colocalEdgeIt.isDone(); colocalEdgeIt.advance()) { @@ -2270,55 +2538,31 @@ public: return false; } - // Check if the face mirrors any face already in the group. - // i.e. don't want two-sided faces in the same group. - // A face mirrors another face if all edges match with opposite winding. - bool faceMirrorsGroupFace(uint32_t group, uint32_t face) const - { - FaceEdgeIterator edgeIt(this, face); - for (ColocalEdgeIterator colocalEdgeIt(this, edgeIt.vertex1(), edgeIt.vertex0()); !colocalEdgeIt.isDone(); colocalEdgeIt.advance()) { - const uint32_t candidateFace = meshEdgeFace(colocalEdgeIt.edge()); - if (m_faceGroups[candidateFace] == group) { - // Found a match for mirrored first edge, try the other edges. - bool match = false; - for (; !edgeIt.isDone(); edgeIt.advance()) { - match = false; - for (ColocalEdgeIterator colocalEdgeIt2(this, edgeIt.vertex1(), edgeIt.vertex0()); !colocalEdgeIt2.isDone(); colocalEdgeIt2.advance()) { - if (meshEdgeFace(colocalEdgeIt2.edge()) == candidateFace) { - match = true; - break; - } - } - if (!match) - break; - } - if (match) - return true; // All edges are mirrored in this face. - // Try the next face. - edgeIt = FaceEdgeIterator(this, candidateFace); - } - } - return false; - } - void createFaceGroups() { - uint32_t group = 0; + uint32_t firstUnassignedFace = 0; + uint16_t group = 0; Array<uint32_t> growFaces; + const uint32_t n = faceCount(); + m_faceGroupNextFace.resize(n); for (;;) { // Find an unassigned face. uint32_t face = UINT32_MAX; - for (uint32_t f = 0; f < faceCount(); f++) { - if (m_faceGroups[f] == UINT32_MAX && !isFaceIgnored(f)) { + for (uint32_t f = firstUnassignedFace; f < n; f++) { + if (m_faceGroups[f] == kInvalidFaceGroup && !isFaceIgnored(f)) { face = f; + firstUnassignedFace = f + 1; break; } } if (face == UINT32_MAX) break; // All faces assigned to a group (except ignored faces). m_faceGroups[face] = group; + m_faceGroupNextFace[face] = UINT32_MAX; + m_faceGroupFirstFace.push_back(face); growFaces.clear(); growFaces.push_back(face); + uint32_t prevFace = face, groupFaceCount = 1; // Find faces connected to the face and assign them to the same group as the face, unless they are already assigned to another group. for (;;) { if (growFaces.isEmpty()) @@ -2340,24 +2584,38 @@ public: alreadyAssignedToThisGroup = true; break; } - if (m_faceGroups[oppositeFace] != UINT32_MAX) + if (m_faceGroups[oppositeFace] != kInvalidFaceGroup) continue; // Connected face is already assigned to another group. if (faceDuplicatesGroupEdge(group, oppositeFace)) continue; // Don't want duplicate edges in a group. - if (faceMirrorsGroupFace(group, oppositeFace)) - continue; // Don't want two-sided faces in a group. const uint32_t oppositeVertex0 = m_indices[meshEdgeIndex0(oppositeEdge)]; const uint32_t oppositeVertex1 = m_indices[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_faceGroups[bestConnectedFace] = group; + m_faceGroupNextFace[bestConnectedFace] = UINT32_MAX; + if (prevFace != UINT32_MAX) + m_faceGroupNextFace[prevFace] = bestConnectedFace; + prevFace = bestConnectedFace; + groupFaceCount++; growFaces.push_back(bestConnectedFace); } } } + m_faceGroupFaceCounts.push_back(groupFaceCount); group++; + XA_ASSERT(group < kInvalidFaceGroup); } } @@ -2366,29 +2624,27 @@ public: const uint32_t edgeCount = m_indices.size(); const uint32_t vertexCount = m_positions.size(); m_oppositeEdges.resize(edgeCount); - m_boundaryVertices.resize(vertexCount); + m_boundaryEdges.reserve(uint32_t(edgeCount * 0.1f)); + m_isBoundaryVertex.resize(vertexCount); + m_isBoundaryVertex.zeroOutMemory(); for (uint32_t i = 0; i < edgeCount; i++) m_oppositeEdges[i] = UINT32_MAX; - for (uint32_t i = 0; i < vertexCount; i++) - m_boundaryVertices[i] = false; - const bool hasFaceGroups = m_flags & MeshFlags::HasFaceGroups; - for (uint32_t i = 0; i < faceCount(); i++) { + const uint32_t faceCount = m_indices.size() / 3; + for (uint32_t i = 0; i < faceCount; i++) { if (isFaceIgnored(i)) continue; for (uint32_t j = 0; j < 3; j++) { - const uint32_t vertex0 = m_indices[i * 3 + j]; + const uint32_t edge = i * 3 + j; + const uint32_t vertex0 = m_indices[edge]; const uint32_t vertex1 = m_indices[i * 3 + (j + 1) % 3]; // If there is an edge with opposite winding to this one, the edge isn't on a boundary. - const uint32_t oppositeEdge = findEdge(hasFaceGroups ? m_faceGroups[i] : UINT32_MAX, vertex1, vertex0); + const uint32_t oppositeEdge = findEdge(vertex1, vertex0); if (oppositeEdge != UINT32_MAX) { -#if XA_DEBUG - if (hasFaceGroups) - XA_DEBUG_ASSERT(m_faceGroups[meshEdgeFace(oppositeEdge)] == m_faceGroups[i]); -#endif - XA_DEBUG_ASSERT(!isFaceIgnored(meshEdgeFace(oppositeEdge))); - m_oppositeEdges[i * 3 + j] = oppositeEdge; + m_oppositeEdges[edge] = oppositeEdge; } else { - m_boundaryVertices[vertex0] = m_boundaryVertices[vertex1] = true; + m_boundaryEdges.push_back(edge); + m_isBoundaryVertex.set(vertex0); + m_isBoundaryVertex.set(vertex1); } } } @@ -2407,12 +2663,12 @@ public: m_nextBoundaryEdges[i] = UINT32_MAX; uint32_t numBoundaryLoops = 0, numUnclosedBoundaries = 0; BitArray linkedEdges(edgeCount); - linkedEdges.clearAll(); + 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.bitAt(i)) { + if (m_oppositeEdges[i] == UINT32_MAX && !linkedEdges.get(i)) { firstEdge = i; break; } @@ -2430,12 +2686,8 @@ public: 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.bitAt(otherEdge)) + if (linkedEdges.get(otherEdge)) goto next; // Already linked. - if (m_flags & MeshFlags::HasFaceGroups && m_faceGroups[meshEdgeFace(currentEdge)] != m_faceGroups[meshEdgeFace(otherEdge)]) - goto next; // Don't cross face groups. - if (isFaceIgnored(meshEdgeFace(otherEdge))) - goto next; // Face is ignored. 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. @@ -2449,11 +2701,11 @@ public: if (bestNextEdge == UINT32_MAX) { numUnclosedBoundaries++; if (currentEdge == firstEdge) - linkedEdges.setBitAt(firstEdge); // Only 1 edge in this boundary "loop". + linkedEdges.set(firstEdge); // Only 1 edge in this boundary "loop". break; // Can't find a next edge. } m_nextBoundaryEdges[currentEdge] = bestNextEdge; - linkedEdges.setBitAt(bestNextEdge); + linkedEdges.set(bestNextEdge); currentEdge = bestNextEdge; if (currentEdge == firstEdge) { numBoundaryLoops++; @@ -2461,6 +2713,7 @@ public: } } } +#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. @@ -2469,28 +2722,29 @@ public: fixInternalBoundary: meshGetBoundaryLoops(*this, boundaryLoops); for (uint32_t loop = 0; loop < boundaryLoops.size(); loop++) { - linkedEdges.clearAll(); - for (Mesh::BoundaryEdgeIterator it1(this, boundaryLoops[loop]); !it1.isDone(); it1.advance()) { + linkedEdges.zeroOutMemory(); + for (Mesh::BoundaryLoopEdgeIterator it1(this, boundaryLoops[loop]); !it1.isDone(); it1.advance()) { const uint32_t e1 = it1.edge(); - if (linkedEdges.bitAt(e1)) + if (linkedEdges.get(e1)) continue; - for (Mesh::BoundaryEdgeIterator it2(this, boundaryLoops[loop]); !it2.isDone(); it2.advance()) { + for (Mesh::BoundaryLoopEdgeIterator it2(this, boundaryLoops[loop]); !it2.isDone(); it2.advance()) { const uint32_t e2 = it2.edge(); - if (e1 == e2 || !isBoundaryEdge(e2) || linkedEdges.bitAt(e2)) + 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.setBitAt(e1); - linkedEdges.setBitAt(e2); + linkedEdges.set(e1); + linkedEdges.set(e2); goto fixInternalBoundary; // start over } } } +#endif } /// Find edge, test all colocals. - uint32_t findEdge(uint32_t faceGroup, uint32_t vertex0, uint32_t vertex1) const + uint32_t findEdge(uint32_t vertex0, uint32_t vertex1) const { uint32_t result = UINT32_MAX; if (m_nextColocalVertex.isEmpty()) { @@ -2498,7 +2752,7 @@ public: uint32_t edge = m_edgeMap.get(key); while (edge != UINT32_MAX) { // Don't find edges of ignored faces. - if ((faceGroup == UINT32_MAX || m_faceGroups[meshEdgeFace(edge)] == faceGroup) && !isFaceIgnored(meshEdgeFace(edge))) { + 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 @@ -2514,7 +2768,7 @@ public: uint32_t edge = m_edgeMap.get(key); while (edge != UINT32_MAX) { // Don't find edges of ignored faces. - if ((faceGroup == UINT32_MAX || m_faceGroups[meshEdgeFace(edge)] == faceGroup) && !isFaceIgnored(meshEdgeFace(edge))) { + 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 @@ -2607,7 +2861,7 @@ public: { float area = 0; for (uint32_t f = 0; f < faceCount(); f++) - area += faceArea(f); + area += computeFaceArea(f); XA_DEBUG_ASSERT(area >= 0); return area; } @@ -2616,11 +2870,11 @@ public: { float area = 0; for (uint32_t f = 0; f < faceCount(); f++) - area += faceParametricArea(f); + area += computeFaceParametricArea(f); return fabsf(area); // May be negative, depends on texcoord winding. } - float faceArea(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]]; @@ -2628,7 +2882,7 @@ public: return length(cross(p1 - p0, p2 - p0)) * 0.5f; } - Vector3 faceCentroid(uint32_t face) const + Vector3 computeFaceCentroid(uint32_t face) const { Vector3 sum(0.0f); for (uint32_t i = 0; i < 3; i++) @@ -2636,22 +2890,9 @@ public: return sum / 3.0f; } - Vector3 calculateFaceNormal(uint32_t face) const - { - return normalizeSafe(triangleNormalAreaScaled(face), Vector3(0, 0, 1), 0.0f); - } - - float faceParametricArea(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) * 0.5f; - } - // Average of the edge midpoints weighted by the edge length. // I want a point inside the triangle, but closer to the cirumcenter. - Vector3 triangleCenter(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]]; @@ -2665,22 +2906,25 @@ public: return m0 + m1 + m2; } - // Unnormalized face normal assuming it's a triangle. - Vector3 triangleNormal(uint32_t face) const - { - return normalizeSafe(triangleNormalAreaScaled(face), Vector3(0), 0.0f); - } - - Vector3 triangleNormalAreaScaled(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; - return cross(e0, e1); + const Vector3 normalAreaScaled = cross(e0, e1); + return normalizeSafe(normalAreaScaled, Vector3(0, 0, 1), 0.0f); } + 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 { @@ -2732,7 +2976,8 @@ public: XA_INLINE uint32_t edgeCount() const { return m_indices.size(); } XA_INLINE uint32_t oppositeEdge(uint32_t edge) const { return m_oppositeEdges[edge]; } XA_INLINE bool isBoundaryEdge(uint32_t edge) const { return m_oppositeEdges[edge] == UINT32_MAX; } - XA_INLINE bool isBoundaryVertex(uint32_t vertex) const { return m_boundaryVertices[vertex]; } + XA_INLINE const Array<uint32_t> &boundaryEdges() const { return m_boundaryEdges; } + XA_INLINE bool isBoundaryVertex(uint32_t vertex) const { return m_isBoundaryVertex.get(vertex); } XA_INLINE uint32_t colocalVertexCount() const { return m_colocalVertexCount; } XA_INLINE uint32_t vertexCount() const { return m_positions.size(); } XA_INLINE uint32_t vertexAt(uint32_t i) const { return m_indices[i]; } @@ -2740,10 +2985,14 @@ public: 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 uint32_t ignoredFaceCount() const { return m_ignoredFaceCount; } XA_INLINE uint32_t faceCount() const { return m_indices.size() / 3; } - XA_INLINE uint32_t faceGroupCount() const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups.size(); } - XA_INLINE uint32_t faceGroupAt(uint32_t face) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups[face]; } + XA_INLINE uint16_t faceGroupAt(uint32_t face) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups[face]; } + XA_INLINE uint32_t faceGroupCount() const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroupFaceCounts.size(); } + XA_INLINE uint32_t faceGroupNextFace(uint32_t face) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroupNextFace[face]; } + XA_INLINE uint32_t faceGroupFaceCount(uint32_t group) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroupFaceCounts[group]; } XA_INLINE const uint32_t *indices() const { return m_indices.data(); } XA_INLINE uint32_t indexCount() const { return m_indices.size(); } @@ -2754,18 +3003,25 @@ private: uint32_t m_flags; uint32_t m_id; Array<bool> m_faceIgnore; - Array<uint32_t> m_faceGroups; + uint32_t m_ignoredFaceCount; Array<uint32_t> m_indices; Array<Vector3> m_positions; Array<Vector3> m_normals; Array<Vector2> m_texcoords; + // Populated by createFaceGroups + Array<uint16_t> m_faceGroups; + Array<uint32_t> m_faceGroupFirstFace; + Array<uint32_t> m_faceGroupNextFace; // In: face. Out: the next face in the same group. + Array<uint32_t> m_faceGroupFaceCounts; // In: face group. Out: number of faces in the group. + // Populated by createColocals uint32_t m_colocalVertexCount; Array<uint32_t> m_nextColocalVertex; // In: vertex index. Out: the vertex index of the next colocal position. // Populated by createBoundaries - Array<bool> m_boundaryVertices; + 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 @@ -2776,28 +3032,24 @@ private: EdgeKey() {} EdgeKey(const EdgeKey &k) : v0(k.v0), v1(k.v1) {} EdgeKey(uint32_t v0, uint32_t v1) : v0(v0), v1(v1) {} - - void operator=(const EdgeKey &k) - { - v0 = k.v0; - v1 = k.v1; - } - bool operator==(const EdgeKey &k) const - { - return v0 == k.v0 && v1 == k.v1; - } + bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; } uint32_t v0; uint32_t v1; }; - HashMap<EdgeKey> m_edgeMap; + struct EdgeHash + { + uint32_t operator()(const EdgeKey &k) const { return k.v0 * 32768u + k.v1; } + }; + + HashMap<EdgeKey, EdgeHash> m_edgeMap; public: - class BoundaryEdgeIterator + class BoundaryLoopEdgeIterator { public: - BoundaryEdgeIterator(const Mesh *mesh, uint32_t edge) : m_mesh(mesh), m_first(UINT32_MAX), m_current(edge) {} + BoundaryLoopEdgeIterator(const Mesh *mesh, uint32_t edge) : m_mesh(mesh), m_first(UINT32_MAX), m_current(edge) {} void advance() { @@ -2866,7 +3118,14 @@ public: 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) { - resetElement(); + do { + if (!resetElement()) { + advanceVertex1(); + } + else { + break; + } + } while (!isDone()); } void advance() @@ -2885,7 +3144,7 @@ public: } private: - void resetElement() + bool resetElement() { m_edge = m_mesh->m_edgeMap.get(Mesh::EdgeKey(m_vertex0It.vertex(), m_vertex1It.vertex())); while (m_edge != UINT32_MAX) { @@ -2893,8 +3152,10 @@ public: break; m_edge = m_mesh->m_edgeMap.getNext(m_edge); } - if (m_edge == UINT32_MAX) - advanceVertex1(); + if (m_edge == UINT32_MAX) { + return false; + } + return true; } void advanceElement() @@ -2910,22 +3171,22 @@ public: advanceVertex1(); } - void advanceVertex0() - { - m_vertex0It.advance(); - if (m_vertex0It.isDone()) - return; - m_vertex1It = ColocalVertexIterator(m_mesh, m_vertex1); - resetElement(); - } - void advanceVertex1() { - m_vertex1It.advance(); - if (m_vertex1It.isDone()) - advanceVertex0(); - else - resetElement(); + 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 @@ -2976,16 +3237,8 @@ public: return meshEdgeFace(oedge); } - uint32_t vertex0() const - { - return m_mesh->m_indices[m_face * 3 + m_relativeEdge]; - } - - uint32_t vertex1() const - { - return m_mesh->m_indices[m_face * 3 + (m_relativeEdge + 1) % 3]; - } - + uint32_t vertex0() const { return m_mesh->m_indices[m_face * 3 + m_relativeEdge]; } + uint32_t vertex1() const { return m_mesh->m_indices[m_face * 3 + (m_relativeEdge + 1) % 3]; } const Vector3 &position0() const { return m_mesh->m_positions[vertex0()]; } const Vector3 &position1() const { return m_mesh->m_positions[vertex1()]; } const Vector3 &normal0() const { return m_mesh->m_normals[vertex0()]; } @@ -2999,8 +3252,39 @@ public: uint32_t m_edge; uint32_t m_relativeEdge; }; + + class GroupFaceIterator + { + public: + GroupFaceIterator(const Mesh *mesh, uint32_t group) : m_mesh(mesh) + { + XA_DEBUG_ASSERT(group != UINT32_MAX); + m_current = mesh->m_faceGroupFirstFace[group]; + } + + void advance() + { + m_current = m_mesh->m_faceGroupNextFace[m_current]; + } + + bool isDone() const + { + return m_current == UINT32_MAX; + } + + uint32_t face() const + { + return m_current; + } + + private: + const Mesh *m_mesh; + uint32_t m_current; + }; }; +constexpr uint16_t Mesh::kInvalidFaceGroup; + static bool meshCloseHole(Mesh *mesh, const Array<uint32_t> &holeVertices, const Vector3 &normal) { #if XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION @@ -3027,15 +3311,15 @@ static bool meshCloseHole(Mesh *mesh, const Array<uint32_t> &holeVertices, const const uint32_t i3 = (i + 1) % frontCount; const Vector3 edge1 = frontPoints[i1] - frontPoints[i2]; const Vector3 edge2 = frontPoints[i3] - frontPoints[i2]; - frontAngles[i] = acosf(dot(edge1, edge2) / (length(edge1) * length(edge2))); + 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(UINT32_MAX, frontVertices[i1], frontVertices[i2]) != UINT32_MAX) + if (mesh->findEdge(frontVertices[i1], frontVertices[i2]) != UINT32_MAX) continue; - if (mesh->findEdge(UINT32_MAX, frontVertices[i2], frontVertices[i3]) != UINT32_MAX) + if (mesh->findEdge(frontVertices[i2], frontVertices[i3]) != UINT32_MAX) continue; - if (mesh->findEdge(UINT32_MAX, frontVertices[i3], frontVertices[i1]) != UINT32_MAX) + 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. @@ -3128,9 +3412,10 @@ static bool meshCloseHole(Mesh *mesh, const Array<uint32_t> &holeVertices, const return true; } -static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, const Vector3 &normal, Array<uint32_t> &holeFaceCounts) +static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, const Vector3 &normal, uint32_t *holeCount, Array<uint32_t> *holeFaceCounts) { - holeFaceCounts.clear(); + if (holeFaceCounts) + holeFaceCounts->clear(); // Compute lengths. const uint32_t boundaryCount = boundaryLoops.size(); Array<float> boundaryLengths; @@ -3139,7 +3424,7 @@ static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, con for (uint32_t i = 0; i < boundaryCount; i++) { float boundaryLength = 0.0f; boundaryEdgeCounts[i] = 0; - for (Mesh::BoundaryEdgeIterator it(mesh, boundaryLoops[i]); !it.isDone(); it.advance()) { + 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); @@ -3167,7 +3452,7 @@ static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, con holePoints.resize(boundaryEdgeCounts[i]); // Winding is backwards for internal boundaries. uint32_t e = 0; - for (Mesh::BoundaryEdgeIterator it(mesh, boundaryLoops[i]); !it.isDone(); it.advance()) { + 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); @@ -3176,7 +3461,10 @@ static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, con 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. - holeFaceCounts.push_back(mesh->faceCount() - oldFaceCount); + if (holeCount) + (*holeCount)++; + if (holeFaceCounts) + holeFaceCounts->push_back(mesh->faceCount() - oldFaceCount); } return result; } @@ -3307,104 +3595,18 @@ static void meshGetBoundaryLoops(const Mesh &mesh, Array<uint32_t> &boundaryLoop { const uint32_t edgeCount = mesh.edgeCount(); BitArray bitFlags(edgeCount); - bitFlags.clearAll(); + 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.bitAt(e) || !mesh.isBoundaryEdge(e)) + if (bitFlags.get(e) || !mesh.isBoundaryEdge(e)) continue; - for (Mesh::BoundaryEdgeIterator it(&mesh, e); !it.isDone(); it.advance()) - bitFlags.setBitAt(it.edge()); + for (Mesh::BoundaryLoopEdgeIterator it(&mesh, e); !it.isDone(); it.advance()) + bitFlags.set(it.edge()); boundaryLoops.push_back(e); } } -class MeshTopology -{ -public: - MeshTopology(const Mesh *mesh) - { - const uint32_t vertexCount = mesh->colocalVertexCount(); - const uint32_t faceCount = mesh->faceCount(); - const uint32_t edgeCount = mesh->edgeCount(); - Array<uint32_t> stack(MemTag::Default); - stack.reserve(faceCount); - BitArray bitFlags(faceCount); - bitFlags.clearAll(); - // Compute connectivity. - m_connectedCount = 0; - for (uint32_t f = 0; f < faceCount; f++ ) { - if (bitFlags.bitAt(f) == false) { - m_connectedCount++; - stack.push_back(f); - while (!stack.isEmpty()) { - const uint32_t top = stack.back(); - XA_ASSERT(top != uint32_t(~0)); - stack.pop_back(); - if (bitFlags.bitAt(top) == false) { - bitFlags.setBitAt(top); - for (Mesh::FaceEdgeIterator it(mesh, top); !it.isDone(); it.advance()) { - const uint32_t oppositeFace = it.oppositeFace(); - if (oppositeFace != UINT32_MAX) - stack.push_back(oppositeFace); - } - } - } - } - } - XA_ASSERT(stack.isEmpty()); - // Count boundary loops. - m_boundaryCount = 0; - bitFlags.resize(edgeCount); - bitFlags.clearAll(); - // Don't forget to link the boundary otherwise this won't work. - for (uint32_t e = 0; e < edgeCount; e++) { - if (bitFlags.bitAt(e) || !mesh->isBoundaryEdge(e)) - continue; - m_boundaryCount++; - for (Mesh::BoundaryEdgeIterator it(mesh, e); !it.isDone(); it.advance()) - bitFlags.setBitAt(it.edge()); - } - // Compute euler number. - m_eulerNumber = vertexCount - edgeCount + faceCount; - // Compute genus. (only valid on closed connected surfaces) - m_genus = -1; - if (isClosed() && isConnected()) - m_genus = (2 - m_eulerNumber) / 2; - } - - /// Determine if the mesh is connected. - bool isConnected() const - { - return m_connectedCount == 1; - } - - /// Determine if the mesh is closed. (Each edge is shared by two faces) - bool isClosed() const - { - return m_boundaryCount == 0; - } - - /// Return true if the mesh has the topology of a disk. - bool isDisk() const - { - return isConnected() && m_boundaryCount == 1/* && m_eulerNumber == 1*/; - } - -private: - ///< Number of boundary loops. - int m_boundaryCount; - - ///< Number of connected components. - int m_connectedCount; - - ///< Euler number. - int m_eulerNumber; - - /// Mesh genus. - int m_genus; -}; - 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) @@ -3482,6 +3684,7 @@ class TaskScheduler public: 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; m_groups = XA_ALLOC_ARRAY(MemTag::Default, TaskGroup, m_maxGroups); @@ -3494,7 +3697,7 @@ public: for (uint32_t i = 0; i < m_workers.size(); i++) { new (&m_workers[i]) Worker(); m_workers[i].wakeup = false; - m_workers[i].thread = XA_NEW_ARGS(MemTag::Default, std::thread, workerThread, this, &m_workers[i]); + m_workers[i].thread = XA_NEW_ARGS(MemTag::Default, std::thread, workerThread, this, &m_workers[i], i + 1); } } @@ -3517,6 +3720,11 @@ public: XA_FREE(m_groups); } + uint32_t threadCount() const + { + return max(1u, std::thread::hardware_concurrency()); // Including the main thread. + } + TaskGroupHandle createTaskGroup(uint32_t reserveSize = 0) { // Claim the first free group. @@ -3581,6 +3789,8 @@ public: handle->value = UINT32_MAX; } + static uint32_t currentThreadIndex() { return m_threadIndex; } + private: struct TaskGroup { @@ -3603,9 +3813,11 @@ private: uint32_t m_maxGroups; Array<Worker> m_workers; std::atomic<bool> m_shutdown; + static thread_local uint32_t m_threadIndex; - static void workerThread(TaskScheduler *scheduler, Worker *worker) + 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(); }); @@ -3636,6 +3848,8 @@ private: } } }; + +thread_local uint32_t TaskScheduler::m_threadIndex; #else class TaskScheduler { @@ -3646,6 +3860,11 @@ public: destroyGroup({ i }); } + uint32_t threadCount() const + { + return 1; + } + TaskGroupHandle createTaskGroup(uint32_t reserveSize = 0) { TaskGroup *group = XA_NEW(MemTag::Default, TaskGroup); @@ -3675,6 +3894,8 @@ public: handle->value = UINT32_MAX; } + static uint32_t currentThreadIndex() { return 0; } + private: void destroyGroup(TaskGroupHandle handle) { @@ -3695,6 +3916,369 @@ private: }; #endif +#if XA_DEBUG_EXPORT_TGA +const uint8_t TGA_TYPE_RGB = 2; +const uint8_t TGA_ORIGIN_UPPER = 0x20; + +#pragma pack(push, 1) +struct TgaHeader +{ + uint8_t id_length; + uint8_t colormap_type; + uint8_t image_type; + uint16_t colormap_index; + uint16_t colormap_length; + uint8_t colormap_size; + uint16_t x_origin; + uint16_t y_origin; + uint16_t width; + uint16_t height; + uint8_t pixel_size; + uint8_t flags; + enum { Size = 18 }; +}; +#pragma pack(pop) + +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"); + if (!f) + return; + TgaHeader tga; + tga.id_length = 0; + tga.colormap_type = 0; + tga.image_type = TGA_TYPE_RGB; + tga.colormap_index = 0; + tga.colormap_length = 0; + tga.colormap_size = 0; + tga.x_origin = 0; + tga.y_origin = 0; + tga.width = (uint16_t)width; + tga.height = (uint16_t)height; + tga.pixel_size = 24; + tga.flags = TGA_ORIGIN_UPPER; + fwrite(&tga, sizeof(TgaHeader), 1, f); + fwrite(data, sizeof(uint8_t), width * height * 3, f); + fclose(f); +} +#endif + +template<typename T> +class ThreadLocal +{ +public: + ThreadLocal() + { +#if XA_MULTITHREADED + const uint32_t n = std::thread::hardware_concurrency(); +#else + const uint32_t n = 1; +#endif + m_array = XA_ALLOC_ARRAY(MemTag::Default, T, n); + for (uint32_t i = 0; i < n; i++) + new (&m_array[i]) T; + } + + ~ThreadLocal() + { +#if XA_MULTITHREADED + const uint32_t n = std::thread::hardware_concurrency(); +#else + const uint32_t n = 1; +#endif + for (uint32_t i = 0; i < n; i++) + m_array[i].~T(); + XA_FREE(m_array); + } + + T &get() const + { + return m_array[TaskScheduler::currentThreadIndex()]; + } + +private: + T *m_array; +}; + +class UniformGrid2 +{ +public: + void reset(const Vector2 *positions, const uint32_t *indices = nullptr, uint32_t reserveEdgeCount = 0) + { + m_edges.clear(); + if (reserveEdgeCount > 0) + m_edges.reserve(reserveEdgeCount); + m_positions = positions; + m_indices = indices; + m_cellDataOffsets.clear(); + } + + void append(uint32_t edge) + { + XA_DEBUG_ASSERT(m_cellDataOffsets.isEmpty()); + m_edges.push_back(edge); + } + + bool intersect(Vector2 v1, Vector2 v2, float epsilon) + { + const uint32_t edgeCount = m_edges.size(); + bool bruteForce = edgeCount <= 64; + if (!bruteForce && m_cellDataOffsets.isEmpty()) + bruteForce = !createGrid(); + if (bruteForce) { + for (uint32_t j = 0; j < edgeCount; j++) { + const uint32_t edge = m_edges[j]; + if (linesIntersect(v1, v2, edgePosition0(edge), edgePosition1(edge), epsilon)) + return true; + } + } else { + computePotentialEdges(v1, v2); + uint32_t prevEdge = UINT32_MAX; + for (uint32_t j = 0; j < m_potentialEdges.size(); j++) { + const uint32_t edge = m_potentialEdges[j]; + if (edge == prevEdge) + continue; + if (linesIntersect(v1, v2, edgePosition0(edge), edgePosition1(edge), epsilon)) + return true; + prevEdge = edge; + } + } + return false; + } + + bool intersectSelf(float epsilon) + { + const uint32_t edgeCount = m_edges.size(); + bool bruteForce = edgeCount <= 64; + if (!bruteForce && m_cellDataOffsets.isEmpty()) + bruteForce = !createGrid(); + for (uint32_t i = 0; i < edgeCount; i++) { + const uint32_t edge1 = m_edges[i]; + if (bruteForce) { + for (uint32_t j = 0; j < edgeCount; j++) { + const uint32_t edge2 = m_edges[j]; + if (edgesIntersect(edge1, edge2, epsilon)) + return true; + } + } else { + computePotentialEdges(edgePosition0(edge1), edgePosition1(edge1)); + uint32_t prevEdge = UINT32_MAX; + for (uint32_t j = 0; j < m_potentialEdges.size(); j++) { + const uint32_t edge2 = m_potentialEdges[j]; + if (edge2 == prevEdge) + continue; + if (edgesIntersect(edge1, edge2, epsilon)) + return true; + prevEdge = edge2; + } + } + } + return false; + } + +#if XA_DEBUG_EXPORT_BOUNDARY_GRID + 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++) { + for (uint32_t x = 0; x < m_gridWidth; x++) { + uint8_t *bgr = &image[(x + y * m_gridWidth) * 3]; + bgr[0] = bgr[1] = bgr[2] = 32; + uint32_t offset = m_cellDataOffsets[x + y * m_gridWidth]; + while (offset != UINT32_MAX) { + const uint32_t edge2 = m_cellData[offset]; + srand(edge2); + for (uint32_t i = 0; i < 3; i++) + bgr[i] = uint8_t(bgr[i] * 0.5f + (rand() % 255) * 0.5f); + offset = m_cellData[offset + 1]; + } + } + } + WriteTga(filename, image.data(), m_gridWidth, m_gridHeight); + } +#endif + +private: + bool createGrid() + { + // Compute edge extents. Min will be the grid origin. + const uint32_t edgeCount = m_edges.size(); + Extents2 edgeExtents; + edgeExtents.reset(); + for (uint32_t i = 0; i < edgeCount; i++) { + const uint32_t edge = m_edges[i]; + edgeExtents.add(edgePosition0(edge)); + edgeExtents.add(edgePosition1(edge)); + } + m_gridOrigin = edgeExtents.min; + // Size grid to approximately one edge per cell. + const Vector2 extentsSize(edgeExtents.max - edgeExtents.min); + m_cellSize = min(extentsSize.x, extentsSize.y) / sqrtf((float)edgeCount); + 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) + return false; + // Insert edges into cells. + m_cellDataOffsets.resize(m_gridWidth * m_gridHeight); + for (uint32_t i = 0; i < m_cellDataOffsets.size(); i++) + m_cellDataOffsets[i] = UINT32_MAX; + m_cellData.clear(); + m_cellData.reserve(edgeCount * 2); + for (uint32_t i = 0; i < edgeCount; i++) { + const uint32_t edge = m_edges[i]; + traverse(edgePosition0(edge), edgePosition1(edge)); + XA_DEBUG_ASSERT(!m_traversedCellOffsets.isEmpty()); + for (uint32_t j = 0; j < m_traversedCellOffsets.size(); j++) { + const uint32_t cell = m_traversedCellOffsets[j]; + uint32_t offset = m_cellDataOffsets[cell]; + if (offset == UINT32_MAX) + m_cellDataOffsets[cell] = m_cellData.size(); + else { + for (;;) { + uint32_t &nextOffset = m_cellData[offset + 1]; + if (nextOffset == UINT32_MAX) { + nextOffset = m_cellData.size(); + break; + } + offset = nextOffset; + } + } + m_cellData.push_back(edge); + m_cellData.push_back(UINT32_MAX); + } + } + return true; + } + + void computePotentialEdges(Vector2 p1, Vector2 p2) + { + m_potentialEdges.clear(); + traverse(p1, p2); + for (uint32_t j = 0; j < m_traversedCellOffsets.size(); j++) { + const uint32_t cell = m_traversedCellOffsets[j]; + uint32_t offset = m_cellDataOffsets[cell]; + while (offset != UINT32_MAX) { + const uint32_t edge2 = m_cellData[offset]; + m_potentialEdges.push_back(edge2); + offset = m_cellData[offset + 1]; + } + } + if (m_potentialEdges.isEmpty()) + return; + insertionSort(m_potentialEdges.data(), m_potentialEdges.size()); + } + + // "A Fast Voxel Traversal Algorithm for Ray Tracing" + void traverse(Vector2 p1, Vector2 p2) + { + const Vector2 dir = p2 - p1; + const Vector2 normal = normalizeSafe(dir, Vector2(0.0f), kEpsilon); + 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) }; + const uint32_t lastCell[2] = { cellX(p2.x), cellY(p2.y) }; + float distToNextCellX; + if (stepX == 1) + distToNextCellX = (firstCell[0] + 1) * m_cellSize - (p1.x - m_gridOrigin.x); + else + distToNextCellX = (p1.x - m_gridOrigin.x) - firstCell[0] * m_cellSize; + float distToNextCellY; + if (stepY == 1) + distToNextCellY = (firstCell[1] + 1) * m_cellSize - (p1.y - m_gridOrigin.y); + else + distToNextCellY = (p1.y - m_gridOrigin.y) - firstCell[1] * m_cellSize; + float tMaxX, tMaxY, tDeltaX, tDeltaY; + if (normal.x > kEpsilon || normal.x < -kEpsilon) { + tMaxX = (distToNextCellX * stepX) / normal.x; + tDeltaX = (m_cellSize * stepX) / normal.x; + } + else + tMaxX = tDeltaX = FLT_MAX; + if (normal.y > kEpsilon || normal.y < -kEpsilon) { + tMaxY = (distToNextCellY * stepY) / normal.y; + tDeltaY = (m_cellSize * stepY) / normal.y; + } + else + tMaxY = tDeltaY = FLT_MAX; + m_traversedCellOffsets.clear(); + m_traversedCellOffsets.push_back(firstCell[0] + firstCell[1] * m_gridWidth); + uint32_t currentCell[2] = { firstCell[0], firstCell[1] }; + while (!(currentCell[0] == lastCell[0] && currentCell[1] == lastCell[1])) { + if (tMaxX < tMaxY) { + tMaxX += tDeltaX; + currentCell[0] += stepX; + } else { + tMaxY += tDeltaY; + currentCell[1] += stepY; + } + if (currentCell[0] >= m_gridWidth || currentCell[1] >= m_gridHeight) + break; + if (stepX == 0 && currentCell[0] < lastCell[0]) + break; + if (stepX == 1 && currentCell[0] > lastCell[0]) + break; + if (stepY == 0 && currentCell[1] < lastCell[1]) + break; + if (stepY == 1 && currentCell[1] > lastCell[1]) + break; + m_traversedCellOffsets.push_back(currentCell[0] + currentCell[1] * m_gridWidth); + } + } + + bool edgesIntersect(uint32_t edge1, uint32_t edge2, float epsilon) const + { + if (edge1 == edge2) + return false; + const uint32_t ai[2] = { vertexAt(meshEdgeIndex0(edge1)), vertexAt(meshEdgeIndex1(edge1)) }; + const uint32_t bi[2] = { vertexAt(meshEdgeIndex0(edge2)), vertexAt(meshEdgeIndex1(edge2)) }; + // Ignore connected edges, since they can't intersect (only overlap), and may be detected as false positives. + if (ai[0] == bi[0] || ai[0] == bi[1] || ai[1] == bi[0] || ai[1] == bi[1]) + return false; + return linesIntersect(m_positions[ai[0]], m_positions[ai[1]], m_positions[bi[0]], m_positions[bi[1]], epsilon); + } + + 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 + { + return min((uint32_t)max(0.0f, (y - m_gridOrigin.y) / m_cellSize), m_gridHeight - 1u); + } + + Vector2 edgePosition0(uint32_t edge) const + { + return m_positions[vertexAt(meshEdgeIndex0(edge))]; + } + + 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; + } + + Array<uint32_t> m_edges; + const Vector2 *m_positions; + const uint32_t *m_indices; // Optional + float m_cellSize; + Vector2 m_gridOrigin; + uint32_t m_gridWidth, m_gridHeight; // in cells + Array<uint32_t> m_cellDataOffsets; + Array<uint32_t> m_cellData; + Array<uint32_t> m_potentialEdges; + Array<uint32_t> m_traversedCellOffsets; +}; + struct UvMeshChart { Array<uint32_t> faces; @@ -3834,15 +4418,16 @@ struct Triangle // make sure every triangle is front facing. flipBackface(); // Compute deltas. - computeUnitInwardNormals(); + if (isValid()) + computeUnitInwardNormals(); } bool isValid() { const Vector2 e0 = v3 - v1; const Vector2 e1 = v2 - v1; - const float denom = 1.0f / (e0.y * e1.x - e1.y * e0.x); - return isFinite(denom); + const float area = e0.y * e1.x - e1.y * e0.x; + return area != 0.0f; } // extents has to be multiple of BK_SIZE!! @@ -3926,6 +4511,7 @@ struct Triangle return true; } +private: void flipBackface() { // check if triangle is backfacing, if so, swap two vertices @@ -3941,13 +4527,13 @@ struct Triangle { n1 = v1 - v2; n1 = Vector2(-n1.y, n1.x); - n1 = n1 * (1.0f / sqrtf(n1.x * n1.x + n1.y * n1.y)); + n1 = n1 * (1.0f / sqrtf(dot(n1, n1))); n2 = v2 - v3; n2 = Vector2(-n2.y, n2.x); - n2 = n2 * (1.0f / sqrtf(n2.x * n2.x + n2.y * n2.y)); + n2 = n2 * (1.0f / sqrtf(dot(n2, n2))); n3 = v3 - v1; n3 = Vector2(-n3.y, n3.x); - n3 = n3 * (1.0f / sqrtf(n3.x * n3.x + n3.y * n3.y)); + n3 = n3 * (1.0f / sqrtf(dot(n3, n3))); } // Vertices. @@ -3990,28 +4576,33 @@ public: float v; // value }; - Matrix(uint32_t d) : m_width(d) + Matrix(uint32_t d) : m_width(d), m_array(MemTag::Matrix) { m_array.resize(d); - for (uint32_t i = 0; i < m_array.size(); i++) - new (&m_array[i]) Array<Coefficient>(); + m_array.runCtors(); +#if XA_DEBUG_HEAP + for (uint32_t i = 0; i < d; i++) + m_array[i].setMemTag(MemTag::Matrix); +#endif } - Matrix(uint32_t w, uint32_t h) : m_width(w) + Matrix(uint32_t w, uint32_t h) : m_width(w), m_array(MemTag::Matrix) { m_array.resize(h); - for (uint32_t i = 0; i < m_array.size(); i++) - new (&m_array[i]) Array<Coefficient>(); + m_array.runCtors(); +#if XA_DEBUG_HEAP + for (uint32_t i = 0; i < h; i++) + m_array[i].setMemTag(MemTag::Matrix); +#endif } ~Matrix() { - for (uint32_t i = 0; i < m_array.size(); i++) - m_array[i].~Array(); + m_array.runDtors(); } Matrix(const Matrix &m) = delete; - const Matrix &operator=(const Matrix &m) = delete; + Matrix &operator=(const Matrix &m) = delete; uint32_t width() const { return m_width; } uint32_t height() const { return m_array.size(); } bool isSquare() const { return width() == height(); } @@ -4211,164 +4802,164 @@ static void mult(const Matrix &A, const Matrix &B, Matrix &C) namespace segment { -// Dummy implementation of a priority queue using sort at insertion. // - Insertion is o(n) // - Smallest element goes at the end, so that popping it is o(1). -// - Resorting is n*log(n) -// @@ Number of elements in the queue is usually small, and we'd have to rebalance often. I'm not sure it's worth implementing a heap. -// @@ Searcing at removal would remove the need for sorting when priorities change. -struct PriorityQueue +struct CostQueue { - PriorityQueue(uint32_t size = UINT32_MAX) : maxSize(size) {} + CostQueue(uint32_t size = UINT32_MAX) : m_maxSize(size), m_pairs(MemTag::SegmentAtlasChartCandidates) {} - void push(float priority, uint32_t face) + float peekCost() const { - uint32_t i = 0; - const uint32_t count = pairs.size(); - for (; i < count; i++) { - if (pairs[i].priority > priority) break; - } - Pair p = { priority, face }; - pairs.insertAt(i, p); - if (pairs.size() > maxSize) - pairs.removeAt(0); + return m_pairs.back().cost; } - // push face out of order, to be sorted later. - void push(uint32_t face) + uint32_t peekFace() const { - Pair p = { 0.0f, face }; - pairs.push_back(p); + return m_pairs.back().face; } - uint32_t pop() + void push(float cost, uint32_t face) { - XA_DEBUG_ASSERT(!pairs.isEmpty()); - uint32_t f = pairs.back().face; - pairs.pop_back(); - return f; + const Pair p = { cost, face }; + if (m_pairs.isEmpty() || cost < peekCost()) + m_pairs.push_back(p); + else { + uint32_t i = 0; + const uint32_t count = m_pairs.size(); + for (; i < count; i++) { + if (m_pairs[i].cost < cost) + break; + } + m_pairs.insertAt(i, p); + if (m_pairs.size() > m_maxSize) + m_pairs.removeAt(0); + } } - void sort() + uint32_t pop() { - //sort(pairs); // @@ My intro sort appears to be much slower than it should! - std::sort(pairs.begin(), pairs.end()); + XA_DEBUG_ASSERT(!m_pairs.isEmpty()); + uint32_t f = m_pairs.back().face; + m_pairs.pop_back(); + return f; } XA_INLINE void clear() { - pairs.clear(); + m_pairs.clear(); } XA_INLINE uint32_t count() const { - return pairs.size(); + return m_pairs.size(); } - float firstPriority() const - { - return pairs.back().priority; - } - - const uint32_t maxSize; +private: + const uint32_t m_maxSize; struct Pair { - bool operator<(const Pair &p) const - { - return priority > p.priority; // !! Sort in inverse priority order! - } - - float priority; + float cost; uint32_t face; }; - Array<Pair> pairs; + Array<Pair> m_pairs; }; struct Chart { + Chart() : faces(MemTag::SegmentAtlasChartFaces) {} + int id = -1; - Vector3 averageNormal = Vector3(0.0f); + Basis basis; // Best fit normal. float area = 0.0f; float boundaryLength = 0.0f; - Vector3 normalSum = Vector3(0.0f); Vector3 centroidSum = Vector3(0.0f); // Sum of chart face centroids. Vector3 centroid = Vector3(0.0f); // Average centroid of chart faces. Array<uint32_t> seeds; Array<uint32_t> faces; - PriorityQueue candidates; - Basis basis; // Of first face. + Array<uint32_t> failedPlanarRegions; + CostQueue candidates; }; struct Atlas { - // @@ Hardcoded to 10? - Atlas(const Mesh *mesh, Array<uint32_t> *meshFaces, const ChartOptions &options) : m_mesh(mesh), m_meshFaces(meshFaces), m_facesLeft(mesh->faceCount()), m_bestTriangles(10), m_options(options) + Atlas() : m_edgeLengths(MemTag::SegmentAtlasMeshData), m_faceAreas(MemTag::SegmentAtlasMeshData), m_faceNormals(MemTag::SegmentAtlasMeshData), m_texcoords(MemTag::SegmentAtlasMeshData), m_bestTriangles(10), m_nextPlanarRegionFace(MemTag::SegmentAtlasPlanarRegions), m_facePlanarRegionId(MemTag::SegmentAtlasPlanarRegions) {} + + ~Atlas() + { + const uint32_t chartCount = m_charts.size(); + for (uint32_t i = 0; i < chartCount; i++) { + m_charts[i]->~Chart(); + XA_FREE(m_charts[i]); + } + } + + uint32_t facesLeft() const { return m_facesLeft; } + uint32_t chartCount() const { return m_charts.size(); } + const Array<uint32_t> &chartFaces(uint32_t i) const { return m_charts[i]->faces; } + const Basis &chartBasis(uint32_t chartIndex) const { return m_charts[chartIndex]->basis; } + + void reset(uint32_t meshId, uint32_t chartGroupId, const Mesh *mesh, const ChartOptions &options) { + XA_UNUSED(meshId); + XA_UNUSED(chartGroupId); XA_PROFILE_START(buildAtlasInit) + m_mesh = mesh; const uint32_t faceCount = m_mesh->faceCount(); - if (meshFaces) { - m_ignoreFaces.resize(faceCount); - m_ignoreFaces.setAll(true); - for (uint32_t f = 0; f < meshFaces->size(); f++) - m_ignoreFaces[(*meshFaces)[f]] = false; - m_facesLeft = meshFaces->size(); - } else { - m_ignoreFaces.resize(faceCount); - m_ignoreFaces.setAll(false); + m_facesLeft = faceCount; + m_options = options; + m_rand.reset(); + const uint32_t chartCount = m_charts.size(); + for (uint32_t i = 0; i < chartCount; i++) { + m_charts[i]->~Chart(); + XA_FREE(m_charts[i]); } - m_faceChartArray.resize(faceCount); - m_faceChartArray.setAll(-1); - m_faceCandidateCharts.resize(faceCount); - m_faceCandidateCosts.resize(faceCount); + m_charts.clear(); + m_faceCharts.resize(faceCount); + m_faceCharts.setAll(-1); m_texcoords.resize(faceCount * 3); - // @@ Floyd for the whole mesh is too slow. We could compute floyd progressively per patch as the patch grows. We need a better solution to compute most central faces. - //computeShortestPaths(); // Precompute edge lengths and face areas. const uint32_t edgeCount = m_mesh->edgeCount(); m_edgeLengths.resize(edgeCount); - m_edgeLengths.zeroOutMemory(); m_faceAreas.resize(faceCount); - m_faceAreas.zeroOutMemory(); m_faceNormals.resize(faceCount); - m_faceTangents.resize(faceCount); - m_faceBitangents.resize(faceCount); for (uint32_t f = 0; f < faceCount; f++) { - if (m_ignoreFaces[f]) - continue; - for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { - m_edgeLengths[it.edge()] = internal::length(it.position1() - it.position0()); - XA_DEBUG_ASSERT(m_edgeLengths[it.edge()] > 0.0f); + for (uint32_t i = 0; i < 3; i++) { + const uint32_t edge = f * 3 + i; + const Vector3 &p0 = mesh->position(m_mesh->vertexAt(meshEdgeIndex0(edge))); + const Vector3 &p1 = mesh->position(m_mesh->vertexAt(meshEdgeIndex1(edge))); + m_edgeLengths[edge] = length(p1 - p0); + XA_DEBUG_ASSERT(m_edgeLengths[edge] > 0.0f); } - m_faceAreas[f] = mesh->faceArea(f); + m_faceAreas[f] = m_mesh->computeFaceArea(f); XA_DEBUG_ASSERT(m_faceAreas[f] > 0.0f); - m_faceNormals[f] = m_mesh->triangleNormal(f); - m_faceTangents[f] = Basis::computeTangent(m_faceNormals[f]); - m_faceBitangents[f] = Basis::computeBitangent(m_faceNormals[f], m_faceTangents[f]); + m_faceNormals[f] = m_mesh->computeFaceNormal(f); } -#if XA_GROW_CHARTS_COPLANAR // Precompute regions of coplanar incident faces. m_nextPlanarRegionFace.resize(faceCount); - for (uint32_t f = 0; f < faceCount; f++) + m_facePlanarRegionId.resize(faceCount); + for (uint32_t f = 0; f < faceCount; f++) { m_nextPlanarRegionFace[f] = f; + m_facePlanarRegionId[f] = UINT32_MAX; + } Array<uint32_t> faceStack; faceStack.reserve(min(faceCount, 16u)); + uint32_t planarRegionCount = 0; for (uint32_t f = 0; f < faceCount; f++) { if (m_nextPlanarRegionFace[f] != f) continue; // Already assigned. - if (m_ignoreFaces[f]) - continue; faceStack.clear(); faceStack.push_back(f); for (;;) { if (faceStack.isEmpty()) break; const uint32_t face = faceStack.back(); + m_facePlanarRegionId[face] = planarRegionCount; faceStack.pop_back(); for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { const uint32_t oface = it.oppositeFace(); - if (it.isBoundary() || m_ignoreFaces[oface]) + if (it.isBoundary()) continue; if (m_nextPlanarRegionFace[oface] != oface) continue; // Already assigned. @@ -4377,29 +4968,33 @@ struct Atlas const uint32_t next = m_nextPlanarRegionFace[face]; m_nextPlanarRegionFace[face] = oface; m_nextPlanarRegionFace[oface] = next; + m_facePlanarRegionId[oface] = planarRegionCount; faceStack.push_back(oface); } } + planarRegionCount++; + } +#if XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_planar_regions.obj", meshId, chartGroupId); + FILE *file; + XA_FOPEN(file, filename, "w"); + if (file) { + m_mesh->writeObjVertices(file); + fprintf(file, "s off\n"); + for (uint32_t i = 0; i < planarRegionCount; i++) { + fprintf(file, "o region%u\n", i); + for (uint32_t j = 0; j < faceCount; j++) { + if (m_facePlanarRegionId[j] == i) + m_mesh->writeObjFace(file, j); + } + } + fclose(file); } #endif XA_PROFILE_END(buildAtlasInit) } - ~Atlas() - { - const uint32_t chartCount = m_chartArray.size(); - for (uint32_t i = 0; i < chartCount; i++) { - m_chartArray[i]->~Chart(); - XA_FREE(m_chartArray[i]); - } - } - - uint32_t facesLeft() const { return m_facesLeft; } - uint32_t chartCount() const { return m_chartArray.size(); } - const Array<uint32_t> &chartFaces(uint32_t i) const { return m_chartArray[i]->faces; } - const Basis &chartBasis(uint32_t chartIndex) const { return m_chartArray[chartIndex]->basis; } - const Vector2 *faceTexcoords(uint32_t face) const { return &m_texcoords[face * 3]; } - void placeSeeds(float threshold) { XA_PROFILE_START(buildAtlasPlaceSeeds) @@ -4415,28 +5010,51 @@ struct Atlas } // Returns true if any of the charts can grow more. - bool growCharts(float threshold) + void growCharts(float threshold) { XA_PROFILE_START(buildAtlasGrowCharts) - // Build global candidate list. - m_faceCandidateCharts.zeroOutMemory(); - for (uint32_t i = 0; i < m_chartArray.size(); i++) - addChartCandidateToGlobalCandidates(m_chartArray[i]); - // Add one candidate face per chart (threshold permitting). - const uint32_t faceCount = m_mesh->faceCount(); - bool canAddAny = false; - for (uint32_t f = 0; f < faceCount; f++) { - Chart *chart = m_faceCandidateCharts[f]; - if (!chart || m_faceCandidateCosts[f] > threshold) - continue; - createFaceTexcoords(chart, f); - if (!canAddFaceToChart(chart, f)) - continue; - addFaceToChart(chart, f); - canAddAny = true; + for (;;) { + if (m_facesLeft == 0) + break; + // Get the single best candidate out of the chart best candidates. + uint32_t bestFace = UINT32_MAX, bestChart = UINT32_MAX; + float lowestCost = FLT_MAX; + for (uint32_t i = 0; i < m_charts.size(); i++) { + Chart *chart = m_charts[i]; + // Get the best candidate from the chart. + // Cleanup any best candidates that have been claimed by another chart. + uint32_t face = UINT32_MAX; + float cost = FLT_MAX; + for (;;) { + if (chart->candidates.count() == 0) + break; + cost = chart->candidates.peekCost(); + face = chart->candidates.peekFace(); + if (m_faceCharts[face] == -1) + break; + else { + // Face belongs to another chart. Pop from queue so the next best candidate can be retrieved. + chart->candidates.pop(); + face = UINT32_MAX; + } + } + if (face == UINT32_MAX) + continue; // No candidates for this chart. + // See if best candidate overall. + if (cost < lowestCost) { + lowestCost = cost; + bestFace = face; + bestChart = i; + } + } + if (bestFace == UINT32_MAX || lowestCost > threshold) + break; + Chart *chart = m_charts[bestChart]; + chart->candidates.pop(); // Pop the selected candidate from the queue. + if (!addFaceToChart(chart, bestFace)) + chart->failedPlanarRegions.push_back(m_facePlanarRegionId[bestFace]); } XA_PROFILE_END(buildAtlasGrowCharts) - return canAddAny && m_facesLeft != 0; // Can continue growing. } void resetCharts() @@ -4444,53 +5062,34 @@ struct Atlas XA_PROFILE_START(buildAtlasResetCharts) const uint32_t faceCount = m_mesh->faceCount(); for (uint32_t i = 0; i < faceCount; i++) - m_faceChartArray[i] = -1; - m_facesLeft = m_meshFaces ? m_meshFaces->size() : faceCount; - const uint32_t chartCount = m_chartArray.size(); + m_faceCharts[i] = -1; + m_facesLeft = faceCount; + const uint32_t chartCount = m_charts.size(); for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = m_chartArray[i]; + Chart *chart = m_charts[i]; const uint32_t seed = chart->seeds.back(); chart->area = 0.0f; chart->boundaryLength = 0.0f; - chart->normalSum = Vector3(0.0f); + chart->basis.normal = Vector3(0.0f); + chart->basis.tangent = Vector3(0.0f); + chart->basis.bitangent = Vector3(0.0f); chart->centroidSum = Vector3(0.0f); chart->centroid = Vector3(0.0f); chart->faces.clear(); chart->candidates.clear(); + chart->failedPlanarRegions.clear(); addFaceToChart(chart, seed); } -#if XA_GROW_CHARTS_COPLANAR - for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = m_chartArray[i]; - growChartCoplanar(chart); - } -#endif XA_PROFILE_END(buildAtlasResetCharts) } - void updateChartCandidates(Chart *chart, uint32_t f) - { - // Traverse neighboring faces, add the ones that do not belong to any chart yet. - for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { - if (!it.isBoundary() && !m_ignoreFaces[it.oppositeFace()] && m_faceChartArray[it.oppositeFace()] == -1) - chart->candidates.push(it.oppositeFace()); - } - // Re-evaluate all candidate priorities. - uint32_t candidateCount = chart->candidates.count(); - for (uint32_t i = 0; i < candidateCount; i++) { - PriorityQueue::Pair &pair = chart->candidates.pairs[i]; - pair.priority = evaluateCost(chart, pair.face); - } - chart->candidates.sort(); - } - bool relocateSeeds() { XA_PROFILE_START(buildAtlasRelocateSeeds) bool anySeedChanged = false; - const uint32_t chartCount = m_chartArray.size(); + const uint32_t chartCount = m_charts.size(); for (uint32_t i = 0; i < chartCount; i++) { - if (relocateSeed(m_chartArray[i])) { + if (relocateSeed(m_charts[i])) { anySeedChanged = true; } } @@ -4510,45 +5109,38 @@ struct Atlas void mergeCharts() { XA_PROFILE_START(buildAtlasMergeCharts) - Array<float> sharedBoundaryLengths; - Array<float> sharedBoundaryLengthsNoSeams; - Array<uint32_t> sharedBoundaryEdgeCountNoSeams; - Array<Vector2> tempTexcoords; - const uint32_t chartCount = m_chartArray.size(); + const uint32_t chartCount = m_charts.size(); // Merge charts progressively until there's none left to merge. for (;;) { bool merged = false; for (int c = chartCount - 1; c >= 0; c--) { - Chart *chart = m_chartArray[c]; + Chart *chart = m_charts[c]; if (chart == nullptr) continue; float externalBoundaryLength = 0.0f; - sharedBoundaryLengths.clear(); - sharedBoundaryLengths.resize(chartCount); - sharedBoundaryLengths.zeroOutMemory(); - sharedBoundaryLengthsNoSeams.clear(); - sharedBoundaryLengthsNoSeams.resize(chartCount); - sharedBoundaryLengthsNoSeams.zeroOutMemory(); - sharedBoundaryEdgeCountNoSeams.clear(); - sharedBoundaryEdgeCountNoSeams.resize(chartCount); - sharedBoundaryEdgeCountNoSeams.zeroOutMemory(); + m_sharedBoundaryLengths.resize(chartCount); + m_sharedBoundaryLengths.zeroOutMemory(); + m_sharedBoundaryLengthsNoSeams.resize(chartCount); + m_sharedBoundaryLengthsNoSeams.zeroOutMemory(); + m_sharedBoundaryEdgeCountNoSeams.resize(chartCount); + m_sharedBoundaryEdgeCountNoSeams.zeroOutMemory(); const uint32_t faceCount = chart->faces.size(); for (uint32_t i = 0; i < faceCount; i++) { const uint32_t f = chart->faces[i]; for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { const float l = m_edgeLengths[it.edge()]; - if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) { + if (it.isBoundary()) { externalBoundaryLength += l; } else { - const int neighborChart = m_faceChartArray[it.oppositeFace()]; - if (m_chartArray[neighborChart] != chart) { + const int neighborChart = m_faceCharts[it.oppositeFace()]; + if (m_charts[neighborChart] != chart) { if ((it.isSeam() && (isNormalSeam(it.edge()) || it.isTextureSeam()))) { externalBoundaryLength += l; } else { - sharedBoundaryLengths[neighborChart] += l; + m_sharedBoundaryLengths[neighborChart] += l; } - sharedBoundaryLengthsNoSeams[neighborChart] += l; - sharedBoundaryEdgeCountNoSeams[neighborChart]++; + m_sharedBoundaryLengthsNoSeams[neighborChart] += l; + m_sharedBoundaryEdgeCountNoSeams[neighborChart]++; } } } @@ -4556,50 +5148,38 @@ struct Atlas for (int cc = chartCount - 1; cc >= 0; cc--) { if (cc == c) continue; - Chart *chart2 = m_chartArray[cc]; + Chart *chart2 = m_charts[cc]; if (chart2 == nullptr) continue; + // Must share a boundary. + if (m_sharedBoundaryLengths[cc] <= 0.0f) + continue; // Compare proxies. - if (dot(chart2->averageNormal, chart->averageNormal) < XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION) + if (dot(chart2->basis.normal, chart->basis.normal) < XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION) continue; // Obey max chart area and boundary length. if (m_options.maxChartArea > 0.0f && chart->area + chart2->area > m_options.maxChartArea) continue; - if (m_options.maxBoundaryLength > 0.0f && chart->boundaryLength + chart2->boundaryLength - sharedBoundaryLengthsNoSeams[cc] > m_options.maxBoundaryLength) + if (m_options.maxBoundaryLength > 0.0f && chart->boundaryLength + chart2->boundaryLength - m_sharedBoundaryLengthsNoSeams[cc] > m_options.maxBoundaryLength) continue; // Merge if chart2 has a single face. // chart1 must have more than 1 face. // chart2 area must be <= 10% of chart1 area. - if (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 && sharedBoundaryEdgeCountNoSeams[cc] >= 2) + if (chart2->faces.size() == 2 && m_sharedBoundaryEdgeCountNoSeams[cc] >= 2) goto merge; // Merge if chart2 is wholely inside chart1, ignoring seams. - if (sharedBoundaryLengthsNoSeams[cc] > 0.0f && equal(sharedBoundaryLengthsNoSeams[cc], chart2->boundaryLength, kEpsilon)) + if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && equal(m_sharedBoundaryLengthsNoSeams[cc], chart2->boundaryLength, kEpsilon)) goto merge; - if (sharedBoundaryLengths[cc] > 0.2f * max(0.0f, chart->boundaryLength - externalBoundaryLength) || - 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: - // Create texcoords for chart 2 using chart 1 basis. Backup chart 2 texcoords for restoration if charts cannot be merged. - tempTexcoords.resize(chart2->faces.size() * 3); - for (uint32_t i = 0; i < chart2->faces.size(); i++) { - const uint32_t face = chart2->faces[i]; - for (uint32_t j = 0; j < 3; j++) - tempTexcoords[i * 3 + j] = m_texcoords[face * 3 + j]; - createFaceTexcoords(chart, face); - } - if (!canMergeCharts(chart, chart2)) { - // Restore chart 2 texcoords. - for (uint32_t i = 0; i < chart2->faces.size(); i++) { - for (uint32_t j = 0; j < 3; j++) - m_texcoords[chart2->faces[i] * 3 + j] = tempTexcoords[i * 3 + j]; - } + if (!mergeChart(chart, chart2, m_sharedBoundaryLengthsNoSeams[cc])) continue; - } - mergeChart(chart, chart2, sharedBoundaryLengthsNoSeams[cc]); merged = true; break; } @@ -4610,20 +5190,20 @@ struct Atlas break; } // Remove deleted charts. - for (int c = 0; c < int32_t(m_chartArray.size()); /*do not increment if removed*/) { - if (m_chartArray[c] == nullptr) { - m_chartArray.removeAt(c); - // Update m_faceChartArray. - const uint32_t faceCount = m_faceChartArray.size(); + for (int c = 0; c < int32_t(m_charts.size()); /*do not increment if removed*/) { + if (m_charts[c] == nullptr) { + m_charts.removeAt(c); + // Update m_faceCharts. + const uint32_t faceCount = m_faceCharts.size(); for (uint32_t i = 0; i < faceCount; i++) { - XA_DEBUG_ASSERT(m_faceChartArray[i] != c); - XA_DEBUG_ASSERT(m_faceChartArray[i] <= int32_t(m_chartArray.size())); - if (m_faceChartArray[i] > c) { - m_faceChartArray[i]--; + XA_DEBUG_ASSERT(m_faceCharts[i] != c); + XA_DEBUG_ASSERT(m_faceCharts[i] <= int32_t(m_charts.size())); + if (m_faceCharts[i] > c) { + m_faceCharts[i]--; } } } else { - m_chartArray[c]->id = c; + m_charts[c]->id = c; c++; } } @@ -4635,264 +5215,204 @@ private: void createRandomChart(float threshold) { Chart *chart = XA_NEW(MemTag::Default, Chart); - chart->id = (int)m_chartArray.size(); - m_chartArray.push_back(chart); + chart->id = (int)m_charts.size(); + m_charts.push_back(chart); // Pick random face that is not used by any chart yet. uint32_t face = m_rand.getRange(m_mesh->faceCount() - 1); - while (m_ignoreFaces[face] || m_faceChartArray[face] != -1) { + while (m_faceCharts[face] != -1) { if (++face >= m_mesh->faceCount()) face = 0; } chart->seeds.push_back(face); addFaceToChart(chart, face); -#if XA_GROW_CHARTS_COPLANAR - growChartCoplanar(chart); -#endif // Grow the chart as much as possible within the given threshold. - for (uint32_t i = 0; i < m_facesLeft; ) { - if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold) + for (;;) { + if (chart->candidates.count() == 0 || chart->candidates.peekCost() > threshold) break; const uint32_t f = chart->candidates.pop(); - if (m_faceChartArray[f] != -1) + if (m_faceCharts[f] != -1) continue; - createFaceTexcoords(chart, f); - if (!canAddFaceToChart(chart, f)) + if (!addFaceToChart(chart, f)) { + chart->failedPlanarRegions.push_back(m_facePlanarRegionId[f]); continue; - addFaceToChart(chart, f); - i++; - } - } - - void addChartCandidateToGlobalCandidates(Chart *chart) - { - if (chart->candidates.count() == 0) - return; - const float cost = chart->candidates.firstPriority(); - const uint32_t face = chart->candidates.pop(); - if (m_faceChartArray[face] != -1) { - addChartCandidateToGlobalCandidates(chart); - } else if (!m_faceCandidateCharts[face]) { - // No candidate assigned to this face yet. - m_faceCandidateCharts[face] = chart; - m_faceCandidateCosts[face] = cost; - } else { - if (cost < m_faceCandidateCosts[face]) { - // This is a better candidate for this face (lower cost). The other chart can choose another candidate. - Chart *otherChart = m_faceCandidateCharts[face]; - m_faceCandidateCharts[face] = chart; - m_faceCandidateCosts[face] = cost; - addChartCandidateToGlobalCandidates(otherChart); - } else { - // Existing candidate is better. This chart can choose another candidate. - addChartCandidateToGlobalCandidates(chart); } } } - void createFaceTexcoords(Chart *chart, uint32_t face) - { - for (uint32_t i = 0; i < 3; i++) { - const Vector3 &pos = m_mesh->position(m_mesh->vertexAt(face * 3 + i)); - m_texcoords[face * 3 + i] = Vector2(dot(chart->basis.tangent, pos), dot(chart->basis.bitangent, pos)); - } - } - bool isChartBoundaryEdge(const Chart *chart, uint32_t edge) const { const uint32_t oppositeEdge = m_mesh->oppositeEdge(edge); const uint32_t oppositeFace = meshEdgeFace(oppositeEdge); - return oppositeEdge == UINT32_MAX || m_ignoreFaces[oppositeFace] || m_faceChartArray[oppositeFace] != chart->id; + return oppositeEdge == UINT32_MAX || m_faceCharts[oppositeFace] != chart->id; } - bool edgeArraysIntersect(const uint32_t *edges1, uint32_t edges1Count, const uint32_t *edges2, uint32_t edges2Count) + bool computeChartBasis(Chart *chart, Basis *basis) { - for (uint32_t i = 0; i < edges1Count; i++) { - const uint32_t edge1 = edges1[i]; - for (uint32_t j = 0; j < edges2Count; j++) { - const uint32_t edge2 = edges2[j]; - const Vector2 &a1 = m_texcoords[meshEdgeIndex0(edge1)]; - const Vector2 &a2 = m_texcoords[meshEdgeIndex1(edge1)]; - const Vector2 &b1 = m_texcoords[meshEdgeIndex0(edge2)]; - const Vector2 &b2 = m_texcoords[meshEdgeIndex1(edge2)]; - if (linesIntersect(a1, a2, b1, b2, m_mesh->epsilon())) - return true; - } + const uint32_t faceCount = chart->faces.size(); + m_tempPoints.resize(chart->faces.size() * 3); + for (uint32_t i = 0; i < faceCount; i++) { + const uint32_t f = chart->faces[i]; + for (uint32_t j = 0; j < 3; j++) + m_tempPoints[i * 3 + j] = m_mesh->position(m_mesh->vertexAt(f * 3 + j)); } - return false; + return Fit::computeBasis(m_tempPoints.data(), m_tempPoints.size(), basis); } bool isFaceFlipped(uint32_t face) const { - const float t1 = m_texcoords[face * 3 + 0].x; - const float s1 = m_texcoords[face * 3 + 0].y; - const float t2 = m_texcoords[face * 3 + 1].x; - const float s2 = m_texcoords[face * 3 + 1].y; - const float t3 = m_texcoords[face * 3 + 2].x; - const float s3 = m_texcoords[face * 3 + 2].y; - const float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) / 2; + const Vector2 &v1 = m_texcoords[face * 3 + 0]; + const Vector2 &v2 = m_texcoords[face * 3 + 1]; + const Vector2 &v3 = m_texcoords[face * 3 + 2]; + const float parametricArea = ((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f; return parametricArea < 0.0f; } - void computeChartBoundaryEdges(const Chart *chart, Array<uint32_t> *dest) const + void parameterizeChart(const Chart *chart) { - dest->clear(); - for (uint32_t f = 0; f < chart->faces.size(); f++) { - const uint32_t face = chart->faces[f]; - for (uint32_t i = 0; i < 3; i++) { - const uint32_t edge = face * 3 + i; - if (isChartBoundaryEdge(chart, edge)) - dest->push_back(edge); + const uint32_t faceCount = chart->faces.size(); + for (uint32_t i = 0; i < faceCount; i++) { + const uint32_t face = chart->faces[i]; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t offset = face * 3 + j; + const Vector3 &pos = m_mesh->position(m_mesh->vertexAt(offset)); + m_texcoords[offset] = Vector2(dot(chart->basis.tangent, pos), dot(chart->basis.bitangent, pos)); } } } - bool canAddFaceToChart(Chart *chart, uint32_t face) + // m_faceCharts for the chart faces must be set to the chart ID. Needed to compute boundary edges. + bool isChartParameterizationValid(const Chart *chart) { - // Check for flipped triangles. - if (isFaceFlipped(face)) + const uint32_t faceCount = chart->faces.size(); + // Check for flipped faces in the parameterization. OK if all are flipped. + uint32_t flippedFaceCount = 0; + for (uint32_t i = 0; i < faceCount; i++) { + if (isFaceFlipped(chart->faces[i])) + flippedFaceCount++; + } + if (flippedFaceCount != 0 && flippedFaceCount != faceCount) return false; - // Find face edges that don't border this chart. - m_tempEdges1.clear(); - for (uint32_t i = 0; i < 3; i++) { - const uint32_t edge = face * 3 + i; - if (isChartBoundaryEdge(chart, edge)) - m_tempEdges1.push_back(edge); - } - if (m_tempEdges1.isEmpty()) - return true; // This can happen if the face is surrounded by the chart. - // Get chart boundary edges, except those that border the face. - m_tempEdges2.clear(); - for (uint32_t i = 0; i < chart->faces.size(); i++) { - const uint32_t chartFace = chart->faces[i]; + // Check for boundary intersection in the parameterization. + m_boundaryGrid.reset(m_texcoords.data()); + for (uint32_t i = 0; i < faceCount; i++) { + const uint32_t f = chart->faces[i]; for (uint32_t j = 0; j < 3; j++) { - const uint32_t chartEdge = chartFace * 3 + j; - if (!isChartBoundaryEdge(chart, chartEdge)) - continue; - // Don't check chart boundary edges that border the face. - const uint32_t oppositeChartEdge = m_mesh->oppositeEdge(chartEdge); - if (meshEdgeFace(oppositeChartEdge) == face) - continue; - m_tempEdges2.push_back(chartEdge); - } - } - const bool intersect = edgeArraysIntersect(m_tempEdges1.data(), m_tempEdges1.size(), m_tempEdges2.data(), m_tempEdges2.size()); -#if 0 - if (intersect) { - static std::atomic<uint32_t> count = 0; - char filename[256]; - XA_SPRINTF(filename, sizeof(filename), "intersect%04u.obj", count.fetch_add(1)); - FILE *file; - XA_FOPEN(file, filename, "w"); - if (file) { - for (uint32_t i = 0; i < m_texcoords.size(); i++) - fprintf(file, "v %g %g 0.0\n", m_texcoords[i].x, m_texcoords[i].y); - fprintf(file, "s off\n"); - fprintf(file, "o face\n"); - { - fprintf(file, "f "); - for (uint32_t j = 0; j < 3; j++) { - const uint32_t index = face * 3 + j + 1; // 1-indexed - fprintf(file, "%d/%d/%d%c", index, index, index, j == 2 ? '\n' : ' '); - } - } - fprintf(file, "s off\n"); - fprintf(file, "o chart\n"); - for (uint32_t i = 0; i < chart->faces.size(); i++) { - const uint32_t chartFace = chart->faces[i]; - fprintf(file, "f "); - for (uint32_t j = 0; j < 3; j++) { - const uint32_t index = chartFace * 3 + j + 1; // 1-indexed - fprintf(file, "%d/%d/%d%c", index, index, index, j == 2 ? '\n' : ' '); - } - } - fclose(file); + const uint32_t edge = f * 3 + j; + if (isChartBoundaryEdge(chart, edge)) + m_boundaryGrid.append(edge); } } -#endif - return !intersect; + if (m_boundaryGrid.intersectSelf(m_mesh->epsilon())) + return false; + return true; } - bool canMergeCharts(Chart *chart1, Chart *chart2) + bool addFaceToChart(Chart *chart, uint32_t face) { - for (uint32_t i = 0; i < chart2->faces.size(); i++) { - if (isFaceFlipped(chart2->faces[i])) - return false; + XA_DEBUG_ASSERT(m_faceCharts[face] == -1); + 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_nextPlanarRegionFace[face]; + while (coplanarFace != face) { + XA_DEBUG_ASSERT(m_faceCharts[coplanarFace] == -1); + chart->faces.push_back(coplanarFace); + coplanarFace = m_nextPlanarRegionFace[coplanarFace]; } - computeChartBoundaryEdges(chart1, &m_tempEdges1); - computeChartBoundaryEdges(chart2, &m_tempEdges2); - return !edgeArraysIntersect(m_tempEdges1.data(), m_tempEdges1.size(), m_tempEdges2.data(), m_tempEdges2.size()); - } - - void addFaceToChart(Chart *chart, uint32_t f) - { - const bool firstFace = chart->faces.isEmpty(); - // Use the first face normal as the chart basis. + const uint32_t faceCount = chart->faces.size(); + // Compute basis. + Basis basis; if (firstFace) { - chart->basis.normal = m_faceNormals[f]; - chart->basis.tangent = m_faceTangents[f]; - chart->basis.bitangent = m_faceBitangents[f]; - createFaceTexcoords(chart, f); - } - // Add face to chart. - chart->faces.push_back(f); - XA_DEBUG_ASSERT(m_faceChartArray[f] == -1); - m_faceChartArray[f] = chart->id; - m_facesLeft--; - // Update area and boundary length. - chart->area = chart->area + m_faceAreas[f]; - chart->boundaryLength = computeBoundaryLength(chart, f); - chart->normalSum += m_mesh->triangleNormalAreaScaled(f); - chart->averageNormal = normalizeSafe(chart->normalSum, Vector3(0), 0.0f); - chart->centroidSum += m_mesh->triangleCenter(f); + // Use the first face normal. + // Use any edge as the tangent vector. + basis.normal = m_faceNormals[face]; + basis.tangent = normalize(m_mesh->position(m_mesh->vertexAt(face * 3 + 0)) - m_mesh->position(m_mesh->vertexAt(face * 3 + 1)), kEpsilon); + basis.bitangent = cross(basis.normal, basis.tangent); + } else { + // Use best fit normal. + if (!computeChartBasis(chart, &basis)) { + chart->faces.resize(oldFaceCount); + return false; + } + if (dot(basis.normal, m_faceNormals[face]) < 0.0f) // Flip normal if oriented in the wrong direction. + basis.normal = -basis.normal; + } + if (!firstFace) { + // Compute orthogonal parameterization and check that it is valid. + parameterizeChart(chart); + for (uint32_t i = oldFaceCount; i < faceCount; i++) + m_faceCharts[chart->faces[i]] = chart->id; + if (!isChartParameterizationValid(chart)) { + for (uint32_t i = oldFaceCount; i < faceCount; i++) + m_faceCharts[chart->faces[i]] = -1; + chart->faces.resize(oldFaceCount); + return false; + } + } + // Add face(s) to chart. + chart->basis = basis; + chart->area = computeArea(chart, face); + chart->boundaryLength = computeBoundaryLength(chart, face); + for (uint32_t i = oldFaceCount; i < faceCount; i++) { + const uint32_t f = chart->faces[i]; + m_faceCharts[f] = chart->id; + m_facesLeft--; + chart->centroidSum += m_mesh->computeFaceCenter(f); + } chart->centroid = chart->centroidSum / float(chart->faces.size()); - // Update candidates. - updateChartCandidates(chart, f); - } - -#if XA_GROW_CHARTS_COPLANAR - void growChartCoplanar(Chart *chart) - { - XA_DEBUG_ASSERT(!chart->faces.isEmpty()); - for (uint32_t i = 0; i < chart->faces.size(); i++) { - const uint32_t chartFace = chart->faces[i]; - uint32_t face = m_nextPlanarRegionFace[chartFace]; - while (face != chartFace) { - // Not assigned to a chart? - if (m_faceChartArray[face] == -1) { - createFaceTexcoords(chart, face); - addFaceToChart(chart, face); - } - face = m_nextPlanarRegionFace[face]; + // Refresh candidates. + chart->candidates.clear(); + for (uint32_t i = 0; i < faceCount; i++) { + // Traverse neighboring faces, add the ones that do not belong to any chart yet. + const uint32_t f = chart->faces[i]; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t edge = f * 3 + j; + const uint32_t oedge = m_mesh->oppositeEdge(edge); + if (oedge == UINT32_MAX) + continue; // Boundary edge. + const uint32_t oface = meshEdgeFace(oedge); + if (m_faceCharts[oface] != -1) + continue; // Face belongs to another chart. + if (chart->failedPlanarRegions.contains(m_facePlanarRegionId[oface])) + continue; // Failed to add this faces planar region to the chart before. + const float cost = evaluateCost(chart, oface); + if (cost < FLT_MAX) + chart->candidates.push(cost, oface); } } + return true; } -#endif + // Returns true if the seed has changed. bool relocateSeed(Chart *chart) { // Find the first N triangles that fit the proxy best. const uint32_t faceCount = chart->faces.size(); m_bestTriangles.clear(); for (uint32_t i = 0; i < faceCount; i++) { - float priority = evaluateProxyFitMetric(chart, chart->faces[i]); - m_bestTriangles.push(priority, chart->faces[i]); + const float cost = evaluateProxyFitMetric(chart, chart->faces[i]); + m_bestTriangles.push(cost, chart->faces[i]); } // Of those, choose the least central triangle. uint32_t leastCentral = 0; float maxDistance = -1; - const uint32_t bestCount = m_bestTriangles.count(); - for (uint32_t i = 0; i < bestCount; i++) { - Vector3 faceCentroid = m_mesh->triangleCenter(m_bestTriangles.pairs[i].face); - float distance = length(chart->centroid - faceCentroid); + for (;;) { + if (m_bestTriangles.count() == 0) + break; + const uint32_t face = m_bestTriangles.pop(); + Vector3 faceCentroid = m_mesh->computeFaceCenter(face); + const float distance = length(chart->centroid - faceCentroid); if (distance > maxDistance) { maxDistance = distance; - leastCentral = m_bestTriangles.pairs[i].face; + leastCentral = face; } } XA_DEBUG_ASSERT(maxDistance >= 0); // In order to prevent k-means cyles we record all the previously chosen seeds. for (uint32_t i = 0; i < chart->seeds.size(); i++) { - if (chart->seeds[i] == leastCentral) { + // Treat seeds belong to the same planar region as equal. + if (chart->seeds[i] == leastCentral || m_facePlanarRegionId[chart->seeds[i]] == m_facePlanarRegionId[leastCentral]) { // Move new seed to the end of the seed array. uint32_t last = chart->seeds.size() - 1; swap(chart->seeds[i], chart->seeds[last]); @@ -4907,26 +5427,32 @@ private: // Evaluate combined metric. float evaluateCost(Chart *chart, uint32_t face) const { + if (dot(m_faceNormals[face], chart->basis.normal) <= 0.26f) // ~75 degrees + return FLT_MAX; // Estimate boundary length and area: - const float newChartArea = chart->area + m_faceAreas[face]; - const float newBoundaryLength = computeBoundaryLength(chart, face); + float newChartArea = 0.0f, newBoundaryLength = 0.0f; + if (m_options.maxChartArea > 0.0f || m_options.roundnessMetricWeight > 0.0f) + newChartArea = computeArea(chart, face); + if (m_options.maxBoundaryLength > 0.0f || m_options.roundnessMetricWeight > 0.0f) + newBoundaryLength = computeBoundaryLength(chart, face); // Enforce limits strictly: if (m_options.maxChartArea > 0.0f && newChartArea > m_options.maxChartArea) return FLT_MAX; if (m_options.maxBoundaryLength > 0.0f && newBoundaryLength > m_options.maxBoundaryLength) return FLT_MAX; - if (dot(m_faceNormals[face], chart->averageNormal) < 0.5f) - return FLT_MAX; - // Penalize faces that cross seams, reward faces that close seams or reach boundaries. - // Make sure normal seams are fully respected: - const float N = evaluateNormalSeamMetric(chart, face); - if (m_options.normalSeamMetricWeight >= 1000.0f && N > 0.0f) - return FLT_MAX; - float cost = m_options.normalSeamMetricWeight * N; + float cost = 0.0f; + if (m_options.normalSeamMetricWeight > 0.0f) { + // Penalize faces that cross seams, reward faces that close seams or reach boundaries. + // Make sure normal seams are fully respected: + const float N = evaluateNormalSeamMetric(chart, face); + if (m_options.normalSeamMetricWeight >= 1000.0f && N > 0.0f) + return FLT_MAX; + cost += m_options.normalSeamMetricWeight * N; + } if (m_options.proxyFitMetricWeight > 0.0f) cost += m_options.proxyFitMetricWeight * evaluateProxyFitMetric(chart, face); if (m_options.roundnessMetricWeight > 0.0f) - cost += m_options.roundnessMetricWeight * evaluateRoundnessMetric(chart, face, newBoundaryLength, newChartArea); + cost += m_options.roundnessMetricWeight * evaluateRoundnessMetric(chart, newBoundaryLength, newChartArea); if (m_options.straightnessMetricWeight > 0.0f) cost += m_options.straightnessMetricWeight * evaluateStraightnessMetric(chart, face); if (m_options.textureSeamMetricWeight > 0.0f) @@ -4941,40 +5467,45 @@ private: } // Returns a value in [0-1]. - float evaluateProxyFitMetric(Chart *chart, uint32_t f) const + float evaluateProxyFitMetric(Chart *chart, uint32_t face) const { - const Vector3 faceNormal = m_faceNormals[f]; + // All faces in coplanar regions have the same normal, can use any face. + const Vector3 faceNormal = m_faceNormals[face]; // Use plane fitting metric for now: - return 1 - dot(faceNormal, chart->averageNormal); // @@ normal deviations should be weighted by face area + return 1 - dot(faceNormal, chart->basis.normal); // @@ normal deviations should be weighted by face area } - float evaluateRoundnessMetric(Chart *chart, uint32_t /*face*/, float newBoundaryLength, float newChartArea) const + float evaluateRoundnessMetric(Chart *chart, float newBoundaryLength, float newChartArea) const { - float roundness = square(chart->boundaryLength) / chart->area; - float newRoundness = square(newBoundaryLength) / newChartArea; - if (newRoundness > roundness) { - return square(newBoundaryLength) / (newChartArea * 4.0f * kPi); - } else { - // Offer no impedance to faces that improve roundness. - return 0; - } + const float roundness = square(chart->boundaryLength) / chart->area; + const float newBoundaryLengthSq = square(newBoundaryLength); + const float newRoundness = newBoundaryLengthSq / newChartArea; + if (newRoundness > roundness) + return newBoundaryLengthSq / (newChartArea * kPi4); + // Offer no impedance to faces that improve roundness. + return 0; } - float evaluateStraightnessMetric(Chart *chart, uint32_t f) const + float evaluateStraightnessMetric(Chart *chart, uint32_t firstFace) const { - float l_out = 0.0f; - float l_in = 0.0f; - for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { - float l = m_edgeLengths[it.edge()]; - if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) { - l_out += l; - } else { - if (m_faceChartArray[it.oppositeFace()] != chart->id) { + float l_out = 0.0f, l_in = 0.0f; + const uint32_t planarRegionId = m_facePlanarRegionId[firstFace]; + uint32_t face = firstFace; + for (;;) { + for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { + const float l = m_edgeLengths[it.edge()]; + if (it.isBoundary()) { l_out += l; - } else { - l_in += l; + } else if (m_facePlanarRegionId[it.oppositeFace()] != planarRegionId) { + if (m_faceCharts[it.oppositeFace()] != chart->id) + l_out += l; + else + l_in += l; } } + face = m_nextPlanarRegionFace[face]; + if (face == firstFace) + break; } 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); @@ -4991,128 +5522,184 @@ private: const uint32_t v1 = m_mesh->vertexAt(meshEdgeIndex1(edge)); const uint32_t ov0 = m_mesh->vertexAt(meshEdgeIndex0(oppositeEdge)); const uint32_t ov1 = m_mesh->vertexAt(meshEdgeIndex1(oppositeEdge)); - return m_mesh->normal(v0) != m_mesh->normal(ov1) || m_mesh->normal(v1) != m_mesh->normal(ov0); + if (v0 == ov1 && v1 == ov0) + return false; + return !equal(m_mesh->normal(v0), m_mesh->normal(ov1), kNormalEpsilon) || !equal(m_mesh->normal(v1), m_mesh->normal(ov0), kNormalEpsilon); } - return m_faceNormals[meshEdgeFace(edge)] != m_faceNormals[meshEdgeFace(oppositeEdge)]; + const uint32_t f0 = meshEdgeFace(edge); + const uint32_t f1 = meshEdgeFace(oppositeEdge); + if (m_facePlanarRegionId[f0] == m_facePlanarRegionId[f1]) + return false; + return !equal(m_faceNormals[f0], m_faceNormals[f1], kNormalEpsilon); } - float evaluateNormalSeamMetric(Chart *chart, uint32_t f) const + float evaluateNormalSeamMetric(Chart *chart, uint32_t firstFace) const { - float seamFactor = 0.0f; - float totalLength = 0.0f; - for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { - if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) - continue; - if (m_faceChartArray[it.oppositeFace()] != chart->id) - continue; - float l = m_edgeLengths[it.edge()]; - totalLength += l; - if (!it.isSeam()) - continue; - // Make sure it's a normal seam. - if (isNormalSeam(it.edge())) { - float d; - if (m_mesh->flags() & MeshFlags::HasNormals) { - const Vector3 &n0 = m_mesh->normal(it.vertex0()); - const Vector3 &n1 = m_mesh->normal(it.vertex1()); - const Vector3 &on0 = m_mesh->normal(m_mesh->vertexAt(meshEdgeIndex0(it.oppositeEdge()))); - const Vector3 &on1 = m_mesh->normal(m_mesh->vertexAt(meshEdgeIndex1(it.oppositeEdge()))); - const float d0 = clamp(dot(n0, on1), 0.0f, 1.0f); - const float d1 = clamp(dot(n1, on0), 0.0f, 1.0f); - d = (d0 + d1) * 0.5f; - } else { - d = clamp(dot(m_faceNormals[f], m_faceNormals[meshEdgeFace(it.oppositeEdge())]), 0.0f, 1.0f); + float seamFactor = 0.0f, totalLength = 0.0f; + uint32_t face = firstFace; + for (;;) { + for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { + if (it.isBoundary()) + continue; + if (m_faceCharts[it.oppositeFace()] != chart->id) + continue; + float l = m_edgeLengths[it.edge()]; + totalLength += l; + if (!it.isSeam()) + continue; + // Make sure it's a normal seam. + if (isNormalSeam(it.edge())) { + float d; + if (m_mesh->flags() & MeshFlags::HasNormals) { + const Vector3 &n0 = m_mesh->normal(it.vertex0()); + const Vector3 &n1 = m_mesh->normal(it.vertex1()); + const Vector3 &on0 = m_mesh->normal(m_mesh->vertexAt(meshEdgeIndex0(it.oppositeEdge()))); + const Vector3 &on1 = m_mesh->normal(m_mesh->vertexAt(meshEdgeIndex1(it.oppositeEdge()))); + const float d0 = clamp(dot(n0, on1), 0.0f, 1.0f); + const float d1 = clamp(dot(n1, on0), 0.0f, 1.0f); + d = (d0 + d1) * 0.5f; + } else { + d = clamp(dot(m_faceNormals[face], m_faceNormals[meshEdgeFace(it.oppositeEdge())]), 0.0f, 1.0f); + } + l *= 1 - d; + seamFactor += l; } - l *= 1 - d; - seamFactor += l; } + face = m_nextPlanarRegionFace[face]; + if (face == firstFace) + break; } if (seamFactor <= 0.0f) return 0.0f; return seamFactor / totalLength; } - float evaluateTextureSeamMetric(Chart *chart, uint32_t f) const + float evaluateTextureSeamMetric(Chart *chart, uint32_t firstFace) const { - float seamLength = 0.0f; - float totalLength = 0.0f; - for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { - if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) - continue; - if (m_faceChartArray[it.oppositeFace()] != chart->id) - continue; - float l = m_edgeLengths[it.edge()]; - totalLength += l; - if (!it.isSeam()) - continue; - // Make sure it's a texture seam. - if (it.isTextureSeam()) - seamLength += l; + float seamLength = 0.0f, totalLength = 0.0f; + uint32_t face = firstFace; + for (;;) { + for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { + if (it.isBoundary()) + continue; + if (m_faceCharts[it.oppositeFace()] != chart->id) + continue; + float l = m_edgeLengths[it.edge()]; + totalLength += l; + if (!it.isSeam()) + continue; + // Make sure it's a texture seam. + if (it.isTextureSeam()) + seamLength += l; + } + face = m_nextPlanarRegionFace[face]; + if (face == firstFace) + break; } - if (seamLength == 0.0f) + if (seamLength <= 0.0f) return 0.0f; // Avoid division by zero. return seamLength / totalLength; } - float computeBoundaryLength(Chart *chart, uint32_t f) const + float computeArea(Chart *chart, uint32_t firstFace) const + { + float area = chart->area; + uint32_t face = firstFace; + for (;;) { + area += m_faceAreas[face]; + face = m_nextPlanarRegionFace[face]; + if (face == firstFace) + break; + } + return area; + } + + float computeBoundaryLength(Chart *chart, uint32_t firstFace) const { float boundaryLength = chart->boundaryLength; // Add new edges, subtract edges shared with the chart. - for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { - const float edgeLength = m_edgeLengths[it.edge()]; - if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) { - boundaryLength += edgeLength; - } else { - if (m_faceChartArray[it.oppositeFace()] != chart->id) + const uint32_t planarRegionId = m_facePlanarRegionId[firstFace]; + uint32_t face = firstFace; + for (;;) { + for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { + const float edgeLength = m_edgeLengths[it.edge()]; + if (it.isBoundary()) { boundaryLength += edgeLength; - else - boundaryLength -= edgeLength; + } else if (m_facePlanarRegionId[it.oppositeFace()] != planarRegionId) { + if (m_faceCharts[it.oppositeFace()] != chart->id) + boundaryLength += edgeLength; + else + boundaryLength -= edgeLength; + } } + face = m_nextPlanarRegionFace[face]; + if (face == firstFace) + break; } return max(0.0f, boundaryLength); // @@ Hack! } - void mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength) + bool mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength) { - const uint32_t faceCount = chart->faces.size(); - for (uint32_t i = 0; i < faceCount; i++) { - uint32_t f = chart->faces[i]; - XA_DEBUG_ASSERT(m_faceChartArray[f] == chart->id); - m_faceChartArray[f] = owner->id; - owner->faces.push_back(f); + const uint32_t oldOwnerFaceCount = owner->faces.size(); + const uint32_t chartFaceCount = chart->faces.size(); + owner->faces.push_back(chart->faces); + for (uint32_t i = 0; i < chartFaceCount; i++) { + XA_DEBUG_ASSERT(m_faceCharts[chart->faces[i]] == chart->id); + m_faceCharts[chart->faces[i]] = owner->id; + } + // Compute basis using best fit normal. + Basis basis; + if (!computeChartBasis(owner, &basis)) { + owner->faces.resize(oldOwnerFaceCount); + for (uint32_t i = 0; i < chartFaceCount; i++) + m_faceCharts[chart->faces[i]] = chart->id; + return false; + } + if (dot(basis.normal, m_faceNormals[owner->faces[0]]) < 0.0f) // Flip normal if oriented in the wrong direction. + basis.normal = -basis.normal; + // Compute orthogonal parameterization and check that it is valid. + parameterizeChart(owner); + if (!isChartParameterizationValid(owner)) { + owner->faces.resize(oldOwnerFaceCount); + for (uint32_t i = 0; i < chartFaceCount; i++) + m_faceCharts[chart->faces[i]] = chart->id; + return false; } + // Merge chart. + owner->basis = basis; + owner->failedPlanarRegions.push_back(chart->failedPlanarRegions); // Update adjacencies? owner->area += chart->area; owner->boundaryLength += chart->boundaryLength - sharedBoundaryLength; - owner->normalSum += chart->normalSum; - owner->averageNormal = normalizeSafe(owner->normalSum, Vector3(0), 0.0f); // Delete chart. - m_chartArray[chart->id] = nullptr; + m_charts[chart->id] = nullptr; chart->~Chart(); XA_FREE(chart); + return true; } const Mesh *m_mesh; - const Array<uint32_t> *m_meshFaces; - Array<bool> m_ignoreFaces; Array<float> m_edgeLengths; Array<float> m_faceAreas; Array<Vector3> m_faceNormals; - Array<Vector3> m_faceTangents; - Array<Vector3> m_faceBitangents; Array<Vector2> m_texcoords; uint32_t m_facesLeft; - Array<int> m_faceChartArray; - Array<Chart *> m_chartArray; - PriorityQueue m_bestTriangles; + Array<int> m_faceCharts; + Array<Chart *> m_charts; + CostQueue m_bestTriangles; KISSRng m_rand; ChartOptions m_options; - Array<Chart *> m_faceCandidateCharts; - Array<float> m_faceCandidateCosts; -#if XA_GROW_CHARTS_COPLANAR Array<uint32_t> m_nextPlanarRegionFace; + Array<uint32_t> m_facePlanarRegionId; + Array<Vector3> m_tempPoints; + UniformGrid2 m_boundaryGrid; +#if XA_MERGE_CHARTS + // mergeCharts + Array<float> m_sharedBoundaryLengths; + Array<float> m_sharedBoundaryLengthsNoSeams; + Array<uint32_t> m_sharedBoundaryEdgeCountNoSeams; #endif - Array<uint32_t> m_tempEdges1, m_tempEdges2; }; } // namespace segment @@ -5301,7 +5888,11 @@ private: // q = A·p sparse::mult(A, p, q); // alpha = delta_new / p·q - alpha = delta_new / sparse::dot(p, q); + const float pdotq = sparse::dot(p, q); + if (!isFinite(pdotq) || isNan(pdotq)) + alpha = 0.0f; + else + alpha = delta_new / pdotq; // x = alfa·p + x sparse::saxpy(alpha, p, x); if ((i & 31) == 0) { // recompute r after 32 steps @@ -5514,186 +6105,518 @@ static bool computeLeastSquaresConformalMap(Mesh *mesh) // Solve Solver::LeastSquaresSolver(A, b, x, lockedParameters, 4, 0.000001f); // Map x back to texcoords: - for (uint32_t v = 0; v < vertexCount; v++) + for (uint32_t v = 0; v < vertexCount; v++) { mesh->texcoord(v) = Vector2(x[2 * v + 0], x[2 * v + 1]); + XA_DEBUG_ASSERT(!isNan(mesh->texcoord(v).x)); + XA_DEBUG_ASSERT(!isNan(mesh->texcoord(v).y)); + } return true; } -static bool computeOrthogonalProjectionMap(Mesh *mesh) +#if XA_RECOMPUTE_CHARTS +struct PiecewiseParam { - uint32_t vertexCount = mesh->vertexCount(); - // Avoid redundant computations. - float matrix[6]; - Fit::computeCovariance(vertexCount, &mesh->position(0), matrix); - if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) - return false; - float eigenValues[3]; - Vector3 eigenVectors[3]; - if (!Fit::eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) - return false; - Vector3 axis[2]; - axis[0] = normalize(eigenVectors[0], kEpsilon); - axis[1] = normalize(eigenVectors[1], kEpsilon); - // Project vertices to plane. - for (uint32_t i = 0; i < vertexCount; i++) - mesh->texcoord(i) = Vector2(dot(axis[0], mesh->position(i)), dot(axis[1], mesh->position(i))); - return true; -} + void reset(const Mesh *mesh, uint32_t faceCount) + { + m_mesh = mesh; + m_faceCount = faceCount; + const uint32_t vertexCount = m_mesh->vertexCount(); + m_texcoords.resize(vertexCount); + m_patch.reserve(m_faceCount); + m_faceAssigned.resize(m_faceCount); + m_faceAssigned.zeroOutMemory(); + m_faceInvalid.resize(m_faceCount); + m_faceInPatch.resize(m_faceCount); + m_vertexInPatch.resize(vertexCount); + m_faceInCandidates.resize(m_faceCount); + } + + ConstArrayView<uint32_t> chartFaces() const { return m_patch; } + const Vector2 *texcoords() const { return m_texcoords.data(); } + + bool computeChart() + { + m_patch.clear(); + m_faceInvalid.zeroOutMemory(); + m_faceInPatch.zeroOutMemory(); + m_vertexInPatch.zeroOutMemory(); + // Add the seed face (first unassigned face) to the patch. + uint32_t seed = UINT32_MAX; + for (uint32_t f = 0; f < m_faceCount; f++) { + if (m_faceAssigned.get(f)) + continue; + seed = f; + m_patch.push_back(seed); + m_faceInPatch.set(seed); + m_faceAssigned.set(seed); + Vector2 texcoords[3]; + orthoProjectFace(seed, texcoords); + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = m_mesh->vertexAt(seed * 3 + i); + m_vertexInPatch.set(vertex); + m_texcoords[vertex] = texcoords[i]; + } + break; + } + if (seed == UINT32_MAX) + return false; + for (;;) { + findCandidates(); + if (m_candidates.isEmpty()) + break; + for (;;) { + // Find the candidate with the lowest cost. + float lowestCost = FLT_MAX; + uint32_t bestCandidate = UINT32_MAX; + for (uint32_t i = 0; i < m_candidates.size(); i++) { + const Candidate &candidate = m_candidates[i]; + if (m_faceInvalid.get(candidate.face)) // A candidate face may be invalidated after is was added. + continue; + if (candidate.maxCost < lowestCost) { + lowestCost = candidate.maxCost; + bestCandidate = i; + } + } + if (bestCandidate == UINT32_MAX) + break; + // Compute the position by averaging linked candidates (candidates that share the same free vertex). + Vector2 position(0.0f); + uint32_t n = 0; + for (CandidateIterator it(m_candidates, bestCandidate); !it.isDone(); it.advance()) { + position += it.current().position; + n++; + } + position *= 1.0f / (float)n; + const uint32_t freeVertex = m_candidates[bestCandidate].vertex; + XA_DEBUG_ASSERT(!isNan(position.x)); + XA_DEBUG_ASSERT(!isNan(position.y)); + m_texcoords[freeVertex] = position; + // Check for flipped faces. This is also done when candidates are first added, but the averaged position of the free vertex is different now, so check again. + bool invalid = false; + for (CandidateIterator it(m_candidates, bestCandidate); !it.isDone(); it.advance()) { + const uint32_t vertex0 = m_mesh->vertexAt(meshEdgeIndex0(it.current().patchEdge)); + const uint32_t vertex1 = m_mesh->vertexAt(meshEdgeIndex1(it.current().patchEdge)); + const float freeVertexOrient = orientToEdge(m_texcoords[vertex0], m_texcoords[vertex1], position); + if ((it.current().patchVertexOrient < 0.0f && freeVertexOrient < 0.0f) || (it.current().patchVertexOrient > 0.0f && freeVertexOrient > 0.0f)) { + invalid = true; + break; + } + } + // Check for boundary intersection. + if (!invalid) { + m_boundaryGrid.reset(m_texcoords.data(), m_mesh->indices()); + // Add edges on the patch boundary to the grid. + // Temporarily adding candidate faces to the patch makes it simpler to detect which edges are on the boundary. + const uint32_t oldPatchSize = m_patch.size(); + for (CandidateIterator it(m_candidates, bestCandidate); !it.isDone(); it.advance()) + m_patch.push_back(it.current().face); + 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)) + m_boundaryGrid.append(it.edge()); + } + } + invalid = m_boundaryGrid.intersectSelf(m_mesh->epsilon()); + m_patch.resize(oldPatchSize); + } + if (invalid) { + // Mark all faces of linked candidates as invalid. + for (CandidateIterator it(m_candidates, bestCandidate); !it.isDone(); it.advance()) + m_faceInvalid.set(it.current().face); + continue; + } + // Add faces to the patch. + for (CandidateIterator it(m_candidates, bestCandidate); !it.isDone(); it.advance()) { + m_patch.push_back(it.current().face); + m_faceInPatch.set(it.current().face); + m_faceAssigned.set(it.current().face); + } + // Add vertex to the patch. + m_vertexInPatch.set(freeVertex); + // Successfully added candidate face(s) to patch. + break; + } + } + return true; + } + +private: + struct Candidate + { + uint32_t face, vertex; + uint32_t next; // The next candidate with the same vertex. + Vector2 position; + float cost; + float maxCost; // Of all linked candidates. + uint32_t patchEdge; + float patchVertexOrient; + }; + + struct CandidateIterator + { + CandidateIterator(Array<Candidate> &candidates, uint32_t first) : m_candidates(candidates), m_current(first) {} + void advance() { if (m_current != UINT32_MAX) m_current = m_candidates[m_current].next; } + bool isDone() const { return m_current == UINT32_MAX; } + Candidate ¤t() { return m_candidates[m_current]; } + + private: + Array<Candidate> &m_candidates; + uint32_t m_current; + }; + + const Mesh *m_mesh; + uint32_t m_faceCount; + Array<Vector2> m_texcoords; + Array<Candidate> m_candidates; + BitArray m_faceInCandidates; + Array<uint32_t> m_patch; + BitArray m_faceAssigned; // Face is assigned to a previous chart or the current patch. + BitArray m_faceInPatch, m_vertexInPatch; + BitArray m_faceInvalid; // Face cannot be added to the patch - flipped, cost too high or causes boundary intersection. + UniformGrid2 m_boundaryGrid; + + // Find candidate faces on the patch front. + void findCandidates() + { + m_candidates.clear(); + m_faceInCandidates.zeroOutMemory(); + 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_faceAssigned.get(oface) || m_faceInCandidates.get(oface)) + continue; + // Found an active edge on the patch front. + // Find the free vertex (the vertex that isn't on the active edge). + // Compute the orientation of the other patch face vertex to the active edge. + uint32_t freeVertex = UINT32_MAX; + float orient = 0.0f; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t vertex = m_mesh->vertexAt(oface * 3 + j); + if (vertex != it.vertex0() && vertex != it.vertex1()) { + freeVertex = vertex; + orient = orientToEdge(m_texcoords[it.vertex0()], m_texcoords[it.vertex1()], m_texcoords[m_mesh->vertexAt(m_patch[i] * 3 + j)]); + break; + } + } + 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)) { + freeVertex = UINT32_MAX; + m_patch.push_back(oface); + m_faceAssigned.set(oface); + continue; + } + // Check this here rather than above so faces enclosed by the patch are always added. + if (m_faceInvalid.get(oface)) + continue; + addCandidateFace(it.edge(), orient, oface, it.oppositeEdge(), freeVertex); + } + } + // Link candidates that share the same vertex. + for (uint32_t i = 0; i < m_candidates.size(); i++) { + if (m_candidates[i].next != UINT32_MAX) + continue; + uint32_t current = i; + for (uint32_t j = i + 1; j < m_candidates.size(); j++) { + if (m_candidates[j].vertex == m_candidates[current].vertex) { + m_candidates[current].next = j; + current = j; + } + } + } + // Set max cost for linked candidates. + for (uint32_t i = 0; i < m_candidates.size(); i++) { + float maxCost = 0.0f; + for (CandidateIterator it(m_candidates, i); !it.isDone(); it.advance()) + maxCost = max(maxCost, it.current().cost); + for (CandidateIterator it(m_candidates, i); !it.isDone(); it.advance()) + it.current().maxCost = maxCost; + } + } + + void addCandidateFace(uint32_t patchEdge, float patchVertexOrient, uint32_t face, uint32_t edge, uint32_t freeVertex) + { + Vector2 texcoords[3]; + orthoProjectFace(face, texcoords); + // Find corresponding vertices between the patch edge and candidate edge. + const uint32_t vertex0 = m_mesh->vertexAt(meshEdgeIndex0(patchEdge)); + const uint32_t vertex1 = m_mesh->vertexAt(meshEdgeIndex1(patchEdge)); + uint32_t localVertex0 = UINT32_MAX, localVertex1 = UINT32_MAX, localFreeVertex = UINT32_MAX; + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = m_mesh->vertexAt(face * 3 + i); + if (vertex == m_mesh->vertexAt(meshEdgeIndex1(edge))) + localVertex0 = i; + else if (vertex == m_mesh->vertexAt(meshEdgeIndex0(edge))) + localVertex1 = i; + else + localFreeVertex = i; + } + // Scale orthogonal projection to match the patch edge. + const Vector2 patchEdgeVec = m_texcoords[vertex1] - m_texcoords[vertex0]; + const Vector2 localEdgeVec = texcoords[localVertex1] - texcoords[localVertex0]; + const float len1 = length(patchEdgeVec); + const float len2 = length(localEdgeVec); + const float scale = len1 / len2; + XA_ASSERT(scale > 0.0f); + for (uint32_t i = 0; i < 3; i++) + texcoords[i] *= scale; + // Translate to the first vertex on the patch edge. + const Vector2 translate = m_texcoords[vertex0] - texcoords[localVertex0]; + for (uint32_t i = 0; i < 3; i++) + texcoords[i] += translate; + // Compute the angle between the patch edge and the corresponding local edge. + const float angle = atan2f(patchEdgeVec.y, patchEdgeVec.x) - atan2f(localEdgeVec.y, localEdgeVec.x); + // Rotate so the patch edge and the corresponding local edge occupy the same space. + for (uint32_t i = 0; i < 3; i++) { + if (i == localVertex0) + continue; + Vector2 &uv = texcoords[i]; + uv -= texcoords[localVertex0]; // Rotate around the first vertex. + const float c = cosf(angle); + const float s = sinf(angle); + const float x = uv.x * c - uv.y * s; + const float y = uv.y * c + uv.x * s; + uv.x = x + texcoords[localVertex0].x; + uv.y = y + texcoords[localVertex0].y; + } + // 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]); + if ((patchVertexOrient < 0.0f && freeVertexOrient < 0.0f) || (patchVertexOrient > 0.0f && freeVertexOrient > 0.0f)) { + m_faceInvalid.set(face); + return; + } + const float stretch = computeStretch(m_mesh->position(vertex0), m_mesh->position(vertex1), m_mesh->position(freeVertex), texcoords[0], texcoords[1], texcoords[2]); + if (stretch >= FLT_MAX) { + m_faceInvalid.set(face); + return; + } + const float cost = fabsf(stretch - 1.0f); +#if 0 + if (cost > 0.25f) { + m_faceInvalid.set(face); + return; + } +#endif + // Add the candidate. + Candidate candidate; + candidate.face = face; + candidate.vertex = freeVertex; + candidate.position = texcoords[localFreeVertex]; + candidate.next = UINT32_MAX; + candidate.cost = cost; + candidate.patchEdge = patchEdge; + candidate.patchVertexOrient = patchVertexOrient; + m_candidates.push_back(candidate); + m_faceInCandidates.set(face); + } + + 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); + 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)); + texcoords[i] = Vector2(dot(tangent, pos), dot(bitangent, pos)); + } + } + + 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 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; + if (parametricArea < 0.0f) + parametricArea = fabsf(parametricArea); + const float geometricArea = length(cross(p2 - p1, p3 - p1)) * 0.5f; + if (parametricArea <= geometricArea) + return parametricArea / geometricArea; + else + return geometricArea / parametricArea; + } + + // 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 + { + return (edgeVertex0.x - point.x) * (edgeVertex1.y - point.y) - (edgeVertex0.y - point.y) * (edgeVertex1.x - point.x); + } +}; +#endif // Estimate quality of existing parameterization. -struct ParameterizationQuality +struct Quality { + // computeBoundaryIntersection + bool boundaryIntersection = false; + + // computeFlippedFaces uint32_t totalTriangleCount = 0; uint32_t flippedTriangleCount = 0; uint32_t zeroAreaTriangleCount = 0; - float parametricArea = 0.0f; - float geometricArea = 0.0f; + + // computeMetrics + float totalParametricArea = 0.0f; + float totalGeometricArea = 0.0f; float stretchMetric = 0.0f; float maxStretchMetric = 0.0f; float conformalMetric = 0.0f; float authalicMetric = 0.0f; - bool boundaryIntersection = false; -}; -static ParameterizationQuality calculateParameterizationQuality(const Mesh *mesh, uint32_t faceCount, Array<uint32_t> *flippedFaces) -{ - XA_DEBUG_ASSERT(mesh != nullptr); - ParameterizationQuality quality; - uint32_t firstBoundaryEdge = UINT32_MAX; - for (uint32_t e = 0; e < mesh->edgeCount(); e++) { - if (mesh->isBoundaryEdge(e)) { - firstBoundaryEdge = e; - break; - } + 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); + for (uint32_t i = 0; i < boundaryEdgeCount; i++) + boundaryGrid.append(boundaryEdges[i]); + boundaryIntersection = boundaryGrid.intersectSelf(mesh->epsilon()); +#if XA_DEBUG_EXPORT_BOUNDARY_GRID + static int exportIndex = 0; + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_boundary_grid_%03d.tga", exportIndex); + boundaryGrid.debugExport(filename); + exportIndex++; +#endif } - XA_DEBUG_ASSERT(firstBoundaryEdge != UINT32_MAX); - for (Mesh::BoundaryEdgeIterator it1(mesh, firstBoundaryEdge); !it1.isDone(); it1.advance()) { - const uint32_t edge1 = it1.edge(); - for (Mesh::BoundaryEdgeIterator it2(mesh, firstBoundaryEdge); !it2.isDone(); it2.advance()) { - const uint32_t edge2 = it2.edge(); - // Skip self and edges directly connected to edge1. - if (edge1 == edge2 || it1.nextEdge() == edge2 || it2.nextEdge() == edge1) + + void computeFlippedFaces(const Mesh *mesh, uint32_t faceCount, Array<uint32_t> *flippedFaces) + { + totalTriangleCount = flippedTriangleCount = zeroAreaTriangleCount = 0; + if (flippedFaces) + flippedFaces->clear(); + for (uint32_t f = 0; f < faceCount; f++) { + Vector2 texcoord[3]; + for (int i = 0; i < 3; i++) { + const uint32_t v = mesh->vertexAt(f * 3 + i); + texcoord[i] = mesh->texcoord(v); + } + totalTriangleCount++; + const float t1 = texcoord[0].x; + const float s1 = texcoord[0].y; + const float t2 = texcoord[1].x; + const float s2 = texcoord[1].y; + const float t3 = texcoord[2].x; + const float s3 = texcoord[2].y; + const float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) * 0.5f; + if (isZero(parametricArea, kAreaEpsilon)) { + zeroAreaTriangleCount++; continue; - const Vector2 &a1 = mesh->texcoord(mesh->vertexAt(meshEdgeIndex0(edge1))); - const Vector2 &a2 = mesh->texcoord(mesh->vertexAt(meshEdgeIndex1(edge1))); - const Vector2 &b1 = mesh->texcoord(mesh->vertexAt(meshEdgeIndex0(edge2))); - const Vector2 &b2 = mesh->texcoord(mesh->vertexAt(meshEdgeIndex1(edge2))); - if (linesIntersect(a1, a2, b1, b2, mesh->epsilon())) { - quality.boundaryIntersection = true; - break; + } + if (parametricArea < 0.0f) { + // Count flipped triangles. + flippedTriangleCount++; + if (flippedFaces) + flippedFaces->push_back(f); } } - if (quality.boundaryIntersection) - break; - } - if (flippedFaces) - flippedFaces->clear(); - for (uint32_t f = 0; f < faceCount; f++) { - Vector3 pos[3]; - Vector2 texcoord[3]; - for (int i = 0; i < 3; i++) { - const uint32_t v = mesh->vertexAt(f * 3 + i); - pos[i] = mesh->position(v); - texcoord[i] = mesh->texcoord(v); - } - quality.totalTriangleCount++; - // Evaluate texture stretch metric. See: - // - "Texture Mapping Progressive Meshes", Sander, Snyder, Gortler & Hoppe - // - "Mesh Parameterization: Theory and Practice", Siggraph'07 Course Notes, Hormann, Levy & Sheffer. - const float t1 = texcoord[0].x; - const float s1 = texcoord[0].y; - const float t2 = texcoord[1].x; - const float s2 = texcoord[1].y; - const float t3 = texcoord[2].x; - const float s3 = texcoord[2].y; - float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) / 2; - if (isZero(parametricArea, kAreaEpsilon)) { - quality.zeroAreaTriangleCount++; - continue; - } - if (parametricArea < 0.0f) { - // Count flipped triangles. - quality.flippedTriangleCount++; + if (flippedTriangleCount + zeroAreaTriangleCount == totalTriangleCount) { + // If all triangles are flipped, then none are. if (flippedFaces) - flippedFaces->push_back(f); - parametricArea = fabsf(parametricArea); + flippedFaces->clear(); + flippedTriangleCount = 0; } - const float geometricArea = length(cross(pos[1] - pos[0], pos[2] - pos[0])) / 2; - const Vector3 Ss = (pos[0] * (t2 - t3) + pos[1] * (t3 - t1) + pos[2] * (t1 - t2)) / (2 * parametricArea); - const Vector3 St = (pos[0] * (s3 - s2) + pos[1] * (s1 - s3) + pos[2] * (s2 - s1)) / (2 * parametricArea); - 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: - 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)); - // isometric: sigma1 = sigma2 = 1 - // conformal: sigma1 / sigma2 = 1 - // authalic: sigma1 * sigma2 = 1 - const float rmsStretch = sqrtf((a + c) * 0.5f); - const float rmsStretch2 = sqrtf((square(sigma1) + square(sigma2)) * 0.5f); - XA_DEBUG_ASSERT(equal(rmsStretch, rmsStretch2, 0.01f)); - XA_UNUSED(rmsStretch2); - quality.stretchMetric += square(rmsStretch) * geometricArea; - quality.maxStretchMetric = max(quality.maxStretchMetric, sigma2); - if (!isZero(sigma1, 0.000001f)) { - // sigma1 is zero when geometricArea is zero. - quality.conformalMetric += (sigma2 / sigma1) * geometricArea; - } - quality.authalicMetric += (sigma1 * sigma2) * geometricArea; - // Accumulate total areas. - quality.geometricArea += geometricArea; - quality.parametricArea += parametricArea; - //triangleConformalEnergy(q, p); - } - if (quality.flippedTriangleCount + quality.zeroAreaTriangleCount == quality.totalTriangleCount) { - // If all triangles are flipped, then none are. - if (flippedFaces) - flippedFaces->clear(); - quality.flippedTriangleCount = 0; - } - if (quality.flippedTriangleCount > quality.totalTriangleCount / 2) - { - // If more than half the triangles are flipped, reverse the flipped / not flipped classification. - quality.flippedTriangleCount = quality.totalTriangleCount - quality.flippedTriangleCount; - if (flippedFaces) { - Array<uint32_t> temp; - flippedFaces->copyTo(temp); - flippedFaces->clear(); - for (uint32_t f = 0; f < faceCount; f++) { - bool match = false; - for (uint32_t ff = 0; ff < temp.size(); ff++) { - if (temp[ff] == f) { - match = true; - break; + if (flippedTriangleCount > totalTriangleCount / 2) + { + // If more than half the triangles are flipped, reverse the flipped / not flipped classification. + flippedTriangleCount = totalTriangleCount - flippedTriangleCount; + if (flippedFaces) { + Array<uint32_t> temp; + flippedFaces->copyTo(temp); + flippedFaces->clear(); + for (uint32_t f = 0; f < faceCount; f++) { + bool match = false; + for (uint32_t ff = 0; ff < temp.size(); ff++) { + if (temp[ff] == f) { + match = true; + break; + } } + if (!match) + flippedFaces->push_back(f); } - if (!match) - flippedFaces->push_back(f); } } } - XA_DEBUG_ASSERT(isFinite(quality.parametricArea) && quality.parametricArea >= 0); - XA_DEBUG_ASSERT(isFinite(quality.geometricArea) && quality.geometricArea >= 0); - XA_DEBUG_ASSERT(isFinite(quality.stretchMetric)); - XA_DEBUG_ASSERT(isFinite(quality.maxStretchMetric)); - XA_DEBUG_ASSERT(isFinite(quality.conformalMetric)); - XA_DEBUG_ASSERT(isFinite(quality.authalicMetric)); - if (quality.geometricArea <= 0.0f) { - quality.stretchMetric = 0.0f; - quality.maxStretchMetric = 0.0f; - quality.conformalMetric = 0.0f; - quality.authalicMetric = 0.0f; - } else { - const float normFactor = sqrtf(quality.parametricArea / quality.geometricArea); - quality.stretchMetric = sqrtf(quality.stretchMetric / quality.geometricArea) * normFactor; - quality.maxStretchMetric *= normFactor; - quality.conformalMetric = sqrtf(quality.conformalMetric / quality.geometricArea); - quality.authalicMetric = sqrtf(quality.authalicMetric / quality.geometricArea); + + void computeMetrics(const Mesh *mesh, uint32_t faceCount) + { + totalGeometricArea = totalParametricArea = 0.0f; + stretchMetric = maxStretchMetric = conformalMetric = authalicMetric = 0.0f; + for (uint32_t f = 0; f < faceCount; f++) { + Vector3 pos[3]; + Vector2 texcoord[3]; + for (int i = 0; i < 3; i++) { + const uint32_t v = mesh->vertexAt(f * 3 + i); + pos[i] = mesh->position(v); + texcoord[i] = mesh->texcoord(v); + } + // Evaluate texture stretch metric. See: + // - "Texture Mapping Progressive Meshes", Sander, Snyder, Gortler & Hoppe + // - "Mesh Parameterization: Theory and Practice", Siggraph'07 Course Notes, Hormann, Levy & Sheffer. + const float t1 = texcoord[0].x; + const float s1 = texcoord[0].y; + const float t2 = texcoord[1].x; + const float s2 = texcoord[1].y; + const float t3 = texcoord[2].x; + const float s3 = texcoord[2].y; + float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) * 0.5f; + if (isZero(parametricArea, kAreaEpsilon)) + continue; + if (parametricArea < 0.0f) + parametricArea = fabsf(parametricArea); + const float geometricArea = length(cross(pos[1] - pos[0], pos[2] - pos[0])) / 2; + const Vector3 Ss = (pos[0] * (t2 - t3) + pos[1] * (t3 - t1) + pos[2] * (t1 - t2)) / (2 * parametricArea); + const Vector3 St = (pos[0] * (s3 - s2) + pos[1] * (s1 - s3) + pos[2] * (s2 - s1)) / (2 * parametricArea); + 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: + 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)); + // isometric: sigma1 = sigma2 = 1 + // conformal: sigma1 / sigma2 = 1 + // authalic: sigma1 * sigma2 = 1 + const float rmsStretch = sqrtf((a + c) * 0.5f); + const float rmsStretch2 = sqrtf((square(sigma1) + square(sigma2)) * 0.5f); + XA_DEBUG_ASSERT(equal(rmsStretch, rmsStretch2, 0.01f)); + XA_UNUSED(rmsStretch2); + stretchMetric += square(rmsStretch) * geometricArea; + maxStretchMetric = max(maxStretchMetric, sigma2); + if (!isZero(sigma1, 0.000001f)) { + // sigma1 is zero when geometricArea is zero. + conformalMetric += (sigma2 / sigma1) * geometricArea; + } + authalicMetric += (sigma1 * sigma2) * geometricArea; + // Accumulate total areas. + totalGeometricArea += geometricArea; + totalParametricArea += parametricArea; + } + XA_DEBUG_ASSERT(isFinite(totalParametricArea) && totalParametricArea >= 0); + XA_DEBUG_ASSERT(isFinite(totalGeometricArea) && totalGeometricArea >= 0); + XA_DEBUG_ASSERT(isFinite(stretchMetric)); + XA_DEBUG_ASSERT(isFinite(maxStretchMetric)); + XA_DEBUG_ASSERT(isFinite(conformalMetric)); + XA_DEBUG_ASSERT(isFinite(authalicMetric)); + if (totalGeometricArea > 0.0f) { + const float normFactor = sqrtf(totalParametricArea / totalGeometricArea); + stretchMetric = sqrtf(stretchMetric / totalGeometricArea) * normFactor; + maxStretchMetric *= normFactor; + conformalMetric = sqrtf(conformalMetric / totalGeometricArea); + authalicMetric = sqrtf(authalicMetric / totalGeometricArea); + } } - return quality; -} +}; struct ChartWarningFlags { @@ -5706,24 +6629,30 @@ struct ChartWarningFlags }; }; +struct ChartCtorBuffers +{ + Array<uint32_t> chartMeshIndices; + Array<uint32_t> unifiedMeshIndices; + Array<uint32_t> boundaryLoops; +}; + /// A chart is a connected set of faces with a certain topology (usually a disk). class Chart { public: - Chart(const segment::Atlas *atlas, const Mesh *originalMesh, uint32_t chartIndex, uint32_t meshId, uint32_t chartGroupId, uint32_t chartId) : m_mesh(nullptr), m_unifiedMesh(nullptr), m_isDisk(false), m_isOrtho(false), m_isPlanar(false), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0) + Chart(ChartCtorBuffers &buffers, const Basis &basis, ConstArrayView<uint32_t> faces, const Mesh *originalMesh, uint32_t meshId, 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) { XA_UNUSED(meshId); XA_UNUSED(chartGroupId); XA_UNUSED(chartId); - m_basis = atlas->chartBasis(chartIndex); - atlas->chartFaces(chartIndex).copyTo(m_faceArray); + m_faceArray.copyFrom(faces.data, faces.length); // Copy face indices. m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, originalMesh->epsilon(), m_faceArray.size() * 3, m_faceArray.size()); m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, originalMesh->epsilon(), m_faceArray.size() * 3, m_faceArray.size()); - Array<uint32_t> chartMeshIndices; + Array<uint32_t> &chartMeshIndices = buffers.chartMeshIndices; chartMeshIndices.resize(originalMesh->vertexCount()); chartMeshIndices.setAll(UINT32_MAX); - Array<uint32_t> unifiedMeshIndices; + Array<uint32_t> &unifiedMeshIndices = buffers.unifiedMeshIndices; unifiedMeshIndices.resize(originalMesh->vertexCount()); unifiedMeshIndices.setAll(UINT32_MAX); // Add vertices. @@ -5735,11 +6664,7 @@ public: if (unifiedMeshIndices[unifiedVertex] == (uint32_t)~0) { unifiedMeshIndices[unifiedVertex] = m_unifiedMesh->vertexCount(); XA_DEBUG_ASSERT(equal(originalMesh->position(vertex), originalMesh->position(unifiedVertex), originalMesh->epsilon())); -#if XA_SKIP_PARAMETERIZATION - m_unifiedMesh->addVertex(originalMesh->position(vertex), Vector3(0.0f), atlas->faceTexcoords(m_faceArray[f])[i]); -#else m_unifiedMesh->addVertex(originalMesh->position(vertex)); -#endif } if (chartMeshIndices[vertex] == (uint32_t)~0) { chartMeshIndices[vertex] = m_mesh->vertexCount(); @@ -5774,11 +6699,10 @@ public: } m_mesh->createBoundaries(); // For AtlasPacker::computeBoundingBox m_unifiedMesh->createBoundaries(); - m_unifiedMesh->linkBoundaries(); - m_isPlanar = meshIsPlanar(*m_unifiedMesh); - if (m_isPlanar) { - m_isDisk = true; - } else { + if (meshIsPlanar(*m_unifiedMesh)) + m_type = ChartType::Planar; + else { + m_unifiedMesh->linkBoundaries(); #if XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION m_unifiedMesh->writeObjFile("debug_before_fix_tjunction.obj"); #endif @@ -5791,15 +6715,14 @@ public: m_warningFlags |= ChartWarningFlags::FixTJunctionsDuplicatedEdge; if (failed) m_warningFlags |= ChartWarningFlags::FixTJunctionsFailed; - m_unifiedMesh->~Mesh(); - XA_FREE(m_unifiedMesh); + m_unmodifiedUnifiedMesh = m_unifiedMesh; m_unifiedMesh = fixedUnifiedMesh; m_unifiedMesh->createBoundaries(); m_unifiedMesh->linkBoundaries(); m_initialFaceCount = m_unifiedMesh->faceCount(); // Fixing t-junctions rewrites faces. } // See if there are any holes that need closing. - Array<uint32_t> boundaryLoops; + Array<uint32_t> &boundaryLoops = buffers.boundaryLoops; meshGetBoundaryLoops(*m_unifiedMesh, boundaryLoops); if (boundaryLoops.size() > 1) { #if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR @@ -5810,16 +6733,21 @@ public: // - Find cuts that reduce genus. // - Find cuts to connect holes. // - Use minimal spanning trees or seamster. - Array<uint32_t> holeFaceCounts; XA_PROFILE_START(closeChartMeshHoles) - failed = !meshCloseHoles(m_unifiedMesh, boundaryLoops, m_basis.normal, holeFaceCounts); + 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 = holeFaceCounts.size(); + m_closedHolesCount = holeCount; #if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR if (m_warningFlags & ChartWarningFlags::CloseHolesFailed) { char filename[256]; @@ -5848,18 +6776,75 @@ public: } #endif } - // Note: MeshTopology needs linked boundaries. - MeshTopology topology(m_unifiedMesh); - m_isDisk = topology.isDisk(); -#if XA_DEBUG_EXPORT_OBJ_NOT_DISK - if (!m_isDisk) { - char filename[256]; - XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_chart_%03u_not_disk.obj", meshId, chartGroupId, chartId); - m_unifiedMesh->writeObjFile(filename); + } + } + +#if XA_RECOMPUTE_CHARTS + Chart(ChartCtorBuffers &buffers, const Chart *parent, const Mesh *parentMesh, ConstArrayView<uint32_t> faces, const Vector2 *texcoords, const Mesh *originalMesh, uint32_t meshId, uint32_t chartGroupId, uint32_t chartId) : m_mesh(nullptr), m_unifiedMesh(nullptr), m_unmodifiedUnifiedMesh(nullptr), m_type(ChartType::Piecewise), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0) + { + XA_UNUSED(meshId); + XA_UNUSED(chartGroupId); + XA_UNUSED(chartId); + const uint32_t faceCount = m_initialFaceCount = faces.length; + m_faceArray.resize(faceCount); + for (uint32_t i = 0; i < faceCount; i++) + m_faceArray[i] = parent->m_faceArray[faces[i]]; // Map faces to parent chart original mesh. + // Copy face indices. + m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, originalMesh->epsilon(), m_faceArray.size() * 3, m_faceArray.size()); + m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, originalMesh->epsilon(), m_faceArray.size() * 3, m_faceArray.size()); + Array<uint32_t> &chartMeshIndices = buffers.chartMeshIndices; + chartMeshIndices.resize(originalMesh->vertexCount()); + chartMeshIndices.setAll(UINT32_MAX); + Array<uint32_t> &unifiedMeshIndices = buffers.unifiedMeshIndices; + unifiedMeshIndices.resize(originalMesh->vertexCount()); + unifiedMeshIndices.setAll(UINT32_MAX); + // Add vertices. + for (uint32_t f = 0; f < faceCount; f++) { + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = originalMesh->vertexAt(m_faceArray[f] * 3 + i); + const uint32_t unifiedVertex = originalMesh->firstColocal(vertex); + const uint32_t parentVertex = parentMesh->vertexAt(faces[f] * 3 + i); + if (unifiedMeshIndices[unifiedVertex] == (uint32_t)~0) { + unifiedMeshIndices[unifiedVertex] = m_unifiedMesh->vertexCount(); + XA_DEBUG_ASSERT(equal(originalMesh->position(vertex), originalMesh->position(unifiedVertex), originalMesh->epsilon())); + m_unifiedMesh->addVertex(originalMesh->position(vertex), Vector3(0.0f), texcoords[parentVertex]); + } + if (chartMeshIndices[vertex] == (uint32_t)~0) { + chartMeshIndices[vertex] = m_mesh->vertexCount(); + m_chartToOriginalMap.push_back(vertex); + m_chartToUnifiedMap.push_back(unifiedMeshIndices[unifiedVertex]); + m_mesh->addVertex(originalMesh->position(vertex), Vector3(0.0f), texcoords[parentVertex]); + } + } + } + // 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 vertex = originalMesh->vertexAt(m_faceArray[f] * 3 + i); + indices[i] = chartMeshIndices[vertex]; + unifiedIndices[i] = unifiedMeshIndices[originalMesh->firstColocal(vertex)]; + } + 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_mesh->createBoundaries(); // For AtlasPacker::computeBoundingBox + m_unifiedMesh->createBoundaries(); + m_unifiedMesh->linkBoundaries(); } +#endif ~Chart() { @@ -5871,16 +6856,19 @@ public: m_unifiedMesh->~Mesh(); XA_FREE(m_unifiedMesh); } + if (m_unmodifiedUnifiedMesh) { + m_unmodifiedUnifiedMesh->~Mesh(); + XA_FREE(m_unmodifiedUnifiedMesh); + } } const Basis &basis() const { return m_basis; } - bool isDisk() const { return m_isDisk; } - bool isOrtho() const { return m_isOrtho; } - bool isPlanar() const { return m_isPlanar; } + 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; } - const ParameterizationQuality ¶mQuality() const { return m_paramQuality; } + 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 @@ -5889,26 +6877,31 @@ public: Mesh *mesh() { return m_mesh; } const Mesh *unifiedMesh() const { return m_unifiedMesh; } Mesh *unifiedMesh() { return m_unifiedMesh; } + const Mesh *unmodifiedUnifiedMesh() const { return m_unmodifiedUnifiedMesh; } uint32_t mapChartVertexToOriginalVertex(uint32_t i) const { return m_chartToOriginalMap[i]; } - void evaluateOrthoParameterizationQuality() + void evaluateOrthoQuality(UniformGrid2 &boundaryGrid) { XA_PROFILE_START(parameterizeChartsEvaluateQuality) - m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, m_initialFaceCount, nullptr); + 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_paramQuality.boundaryIntersection && m_paramQuality.geometricArea > 0.0f && m_paramQuality.stretchMetric <= 1.1f && m_paramQuality.maxStretchMetric <= 1.25f) - m_isOrtho = true; + if (!m_quality.boundaryIntersection && m_quality.totalGeometricArea > 0.0f && m_quality.stretchMetric <= 1.1f && m_quality.maxStretchMetric <= 1.25f) + m_type = ChartType::Ortho; } - void evaluateParameterizationQuality() + void evaluateQuality(UniformGrid2 &boundaryGrid) { XA_PROFILE_START(parameterizeChartsEvaluateQuality) + m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); #if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION - m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, m_initialFaceCount, &m_paramFlippedFaces); + m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, &m_paramFlippedFaces); #else - m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, m_initialFaceCount, nullptr); + m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, 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. XA_PROFILE_END(parameterizeChartsEvaluateQuality) } @@ -5920,16 +6913,6 @@ public: m_mesh->texcoord(v) = m_unifiedMesh->texcoord(m_chartToUnifiedMap[v]); } - float computeSurfaceArea() const - { - return m_mesh->computeSurfaceArea(); - } - - float computeParametricArea() const - { - return m_mesh->computeParametricArea(); - } - Vector2 computeParametricBounds() const { Vector2 minCorner(FLT_MAX, FLT_MAX); @@ -5946,7 +6929,8 @@ private: Basis m_basis; Mesh *m_mesh; Mesh *m_unifiedMesh; - bool m_isDisk, m_isOrtho, m_isPlanar; + 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; @@ -5959,7 +6943,7 @@ private: Array<uint32_t> m_chartToUnifiedMap; - ParameterizationQuality m_paramQuality; + Quality m_quality; #if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION Array<uint32_t> m_paramFlippedFaces; #endif @@ -5967,12 +6951,13 @@ private: struct CreateChartTaskArgs { - const segment::Atlas *atlas; const Mesh *mesh; - uint32_t chartIndex; // In the atlas. + const Basis *basis; + ConstArrayView<uint32_t> faces; uint32_t meshId; uint32_t chartGroupId; uint32_t chartId; + ThreadLocal<ChartCtorBuffers> *chartBuffers; Chart **chart; }; @@ -5980,7 +6965,7 @@ static void runCreateChartTask(void *userData) { XA_PROFILE_START(createChartMeshesThread) auto args = (CreateChartTaskArgs *)userData; - *(args->chart) = XA_NEW_ARGS(MemTag::Default, Chart, args->atlas, args->mesh, args->chartIndex, args->meshId, args->chartGroupId, args->chartId); + *(args->chart) = XA_NEW_ARGS(MemTag::Default, Chart, args->chartBuffers->get(), *(args->basis), args->faces, args->mesh, args->meshId, args->chartGroupId, args->chartId); XA_PROFILE_END(createChartMeshesThread) } @@ -5988,6 +6973,7 @@ struct ParameterizeChartTaskArgs { Chart *chart; ParameterizeFunc func; + ThreadLocal<UniformGrid2> *boundaryGrid; }; static void runParameterizeChartTask(void *userData) @@ -5995,24 +6981,26 @@ static void runParameterizeChartTask(void *userData) auto args = (ParameterizeChartTaskArgs *)userData; Mesh *mesh = args->chart->unifiedMesh(); XA_PROFILE_START(parameterizeChartsOrthogonal) -#if 1 - computeOrthogonalProjectionMap(mesh); -#else - for (uint32_t i = 0; i < vertexCount; i++) - mesh->texcoord(i) = Vector2(dot(args->chart->basis().tangent, mesh->position(i)), dot(args->chart->basis().bitangent, mesh->position(i))); -#endif + { + // Project vertices to plane. + const uint32_t vertexCount = mesh->vertexCount(); + const Basis &basis = args->chart->basis(); + for (uint32_t i = 0; i < vertexCount; i++) + mesh->texcoord(i) = Vector2(dot(basis.tangent, mesh->position(i)), dot(basis.bitangent, mesh->position(i))); + } XA_PROFILE_END(parameterizeChartsOrthogonal) - args->chart->evaluateOrthoParameterizationQuality(); - if (!args->chart->isOrtho() && !args->chart->isPlanar()) { + // Computing charts checks for flipped triangles and boundary intersection. Don't need to do that again here if chart is planar. + if (args->chart->type() != ChartType::Planar) + args->chart->evaluateOrthoQuality(args->boundaryGrid->get()); + if (args->chart->type() == ChartType::LSCM) { XA_PROFILE_START(parameterizeChartsLSCM) if (args->func) args->func(&mesh->position(0).x, &mesh->texcoord(0).x, mesh->vertexCount(), mesh->indices(), mesh->indexCount()); - else if (args->chart->isDisk()) + else computeLeastSquaresConformalMap(mesh); XA_PROFILE_END(parameterizeChartsLSCM) - args->chart->evaluateParameterizationQuality(); + args->chart->evaluateQuality(args->boundaryGrid->get()); } - // @@ Check that parameterization quality is above a certain threshold. // Transfer parameterization from unified mesh to chart mesh. args->chart->transferParameterization(); } @@ -6021,27 +7009,33 @@ static void runParameterizeChartTask(void *userData) class ChartGroup { public: - ChartGroup(uint32_t id, const Mesh *sourceMesh, uint32_t faceGroup) : m_sourceId(sourceMesh->id()), m_id(id), m_isVertexMap(faceGroup == UINT32_MAX), m_paramAddedChartsCount(0), m_paramDeletedChartsCount(0) + ChartGroup(uint32_t id, const Mesh *sourceMesh, uint16_t faceGroup) : m_sourceId(sourceMesh->id()), m_id(id), m_isVertexMap(faceGroup == Mesh::kInvalidFaceGroup), m_paramAddedChartsCount(0), m_paramDeletedChartsCount(0) { // Create new mesh from the source mesh, using faces that belong to this group. const uint32_t sourceFaceCount = sourceMesh->faceCount(); - for (uint32_t f = 0; f < sourceFaceCount; f++) { - if (sourceMesh->faceGroupAt(f) == faceGroup) - m_faceToSourceFaceMap.push_back(f); + if (!m_isVertexMap) { + m_faceToSourceFaceMap.reserve(sourceMesh->faceGroupFaceCount(faceGroup)); + for (Mesh::GroupFaceIterator it(sourceMesh, faceGroup); !it.isDone(); it.advance()) + m_faceToSourceFaceMap.push_back(it.face()); + } else { + for (uint32_t f = 0; f < sourceFaceCount; f++) { + if (sourceMesh->faceGroupAt(f) == faceGroup) + m_faceToSourceFaceMap.push_back(f); + } } // Only initial meshes have face groups and ignored faces. The only flag we care about is HasNormals. const uint32_t faceCount = m_faceToSourceFaceMap.size(); - m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), faceCount * 3, faceCount, sourceMesh->flags() & MeshFlags::HasNormals); XA_DEBUG_ASSERT(faceCount > 0); - Array<uint32_t> meshIndices; - meshIndices.resize(sourceMesh->vertexCount()); - meshIndices.setAll((uint32_t)~0); + const uint32_t approxVertexCount = faceCount * 3; + m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), approxVertexCount, faceCount, sourceMesh->flags() & MeshFlags::HasNormals); + m_vertexToSourceVertexMap.reserve(approxVertexCount); + HashMap<uint32_t> sourceVertexToVertexMap(MemTag::Mesh, approxVertexCount); for (uint32_t f = 0; f < faceCount; f++) { const uint32_t face = m_faceToSourceFaceMap[f]; for (uint32_t i = 0; i < 3; i++) { const uint32_t vertex = sourceMesh->vertexAt(face * 3 + i); - if (meshIndices[vertex] == (uint32_t)~0) { - meshIndices[vertex] = m_mesh->vertexCount(); + if (sourceVertexToVertexMap.get(vertex) == UINT32_MAX) { + sourceVertexToVertexMap.add(vertex); m_vertexToSourceVertexMap.push_back(vertex); Vector3 normal(0.0f); if (sourceMesh->flags() & MeshFlags::HasNormals) @@ -6056,8 +7050,8 @@ public: uint32_t indices[3]; for (uint32_t i = 0; i < 3; i++) { const uint32_t vertex = sourceMesh->vertexAt(face * 3 + i); - XA_DEBUG_ASSERT(meshIndices[vertex] != (uint32_t)~0); - indices[i] = meshIndices[vertex]; + indices[i] = sourceVertexToVertexMap.get(vertex); + XA_DEBUG_ASSERT(indices[i] != UINT32_MAX); } // Don't copy flags, it doesn't matter if a face is ignored after this point. All ignored faces get their own vertex map (m_isVertexMap) ChartGroup. // Don't hash edges if m_isVertexMap, they may be degenerate. @@ -6068,7 +7062,6 @@ public: if (!m_isVertexMap) { m_mesh->createColocals(); m_mesh->createBoundaries(); - m_mesh->linkBoundaries(); } #if XA_DEBUG_EXPORT_OBJ_CHART_GROUPS char filename[256]; @@ -6083,14 +7076,14 @@ public: { m_mesh->~Mesh(); XA_FREE(m_mesh); - for (uint32_t i = 0; i < m_chartArray.size(); i++) { - m_chartArray[i]->~Chart(); - XA_FREE(m_chartArray[i]); + for (uint32_t i = 0; i < m_charts.size(); i++) { + m_charts[i]->~Chart(); + XA_FREE(m_charts[i]); } } - uint32_t chartCount() const { return m_chartArray.size(); } - Chart *chartAt(uint32_t i) const { return m_chartArray[i]; } + uint32_t chartCount() const { return m_charts.size(); } + Chart *chartAt(uint32_t i) const { return m_charts[i]; } uint32_t paramAddedChartsCount() const { return m_paramAddedChartsCount; } uint32_t paramDeletedChartsCount() const { return m_paramDeletedChartsCount; } bool isVertexMap() const { return m_isVertexMap; } @@ -6158,40 +7151,41 @@ public: - emphasize roundness metrics to prevent those cases. - If interior self-overlaps: preserve boundary parameterization and use mean-value map. */ - void computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options) + void computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, segment::Atlas &atlas, ThreadLocal<ChartCtorBuffers> *chartBuffers) { m_chartOptions = options; // This function may be called multiple times, so destroy existing charts. - for (uint32_t i = 0; i < m_chartArray.size(); i++) { - m_chartArray[i]->~Chart(); - XA_FREE(m_chartArray[i]); + for (uint32_t i = 0; i < m_charts.size(); i++) { + m_charts[i]->~Chart(); + XA_FREE(m_charts[i]); } - m_chartArray.clear(); + m_charts.clear(); #if XA_DEBUG_SINGLE_CHART Array<uint32_t> chartFaces; chartFaces.resize(m_mesh->faceCount()); for (uint32_t i = 0; i < chartFaces.size(); i++) chartFaces[i] = i; Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, m_mesh, chartFaces, m_sourceId, m_id, 0); - m_chartArray.push_back(chart); + m_charts.push_back(chart); #else XA_PROFILE_START(buildAtlas) - segment::Atlas atlas(m_mesh, nullptr, options); + atlas.reset(m_sourceId, m_id, m_mesh, options); buildAtlas(atlas, options); XA_PROFILE_END(buildAtlas) const uint32_t chartCount = atlas.chartCount(); - m_chartArray.resize(chartCount); + m_charts.resize(chartCount); Array<CreateChartTaskArgs> taskArgs; taskArgs.resize(chartCount); for (uint32_t i = 0; i < chartCount; i++) { CreateChartTaskArgs &args = taskArgs[i]; - args.atlas = &atlas; + args.basis = &atlas.chartBasis(i); + args.faces = atlas.chartFaces(i); args.mesh = m_mesh; - args.chartIndex = i; args.meshId = m_sourceId; args.chartGroupId = m_id; args.chartId = i; - args.chart = &m_chartArray[i]; + args.chartBuffers = chartBuffers; + args.chart = &m_charts[i]; } XA_PROFILE_START(createChartMeshesReal) TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount); @@ -6225,26 +7219,22 @@ public: #endif } - void parameterizeCharts(TaskScheduler *taskScheduler, ParameterizeFunc func) - { - const uint32_t chartCount = m_chartArray.size(); -#if XA_SKIP_PARAMETERIZATION - XA_UNUSED(taskScheduler); - XA_UNUSED(func); - for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = m_chartArray[i]; - chart->evaluateOrthoParameterizationQuality(); - chart->evaluateParameterizationQuality(); - chart->transferParameterization(); - } +#if XA_RECOMPUTE_CHARTS + void parameterizeCharts(TaskScheduler *taskScheduler, ParameterizeFunc func, ThreadLocal<UniformGrid2> *boundaryGrid, ThreadLocal<ChartCtorBuffers> *chartBuffers, ThreadLocal<PiecewiseParam> *piecewiseParam) #else + void parameterizeCharts(TaskScheduler* taskScheduler, ParameterizeFunc func, ThreadLocal<UniformGrid2>* boundaryGrid, ThreadLocal<ChartCtorBuffers>* /*chartBuffers*/) +#endif + { + m_paramAddedChartsCount = 0; + const uint32_t chartCount = m_charts.size(); Array<ParameterizeChartTaskArgs> taskArgs; taskArgs.resize(chartCount); TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount); for (uint32_t i = 0; i < chartCount; i++) { ParameterizeChartTaskArgs &args = taskArgs[i]; - args.chart = m_chartArray[i]; + args.chart = m_charts[i]; args.func = func; + args.boundaryGrid = boundaryGrid; Task task; task.userData = &args; task.func = runParameterizeChartTask; @@ -6255,67 +7245,65 @@ public: // Find charts with invalid parameterizations. Array<Chart *> invalidCharts; for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = m_chartArray[i]; - const ParameterizationQuality &quality = chart->paramQuality(); + Chart *chart = m_charts[i]; + const Quality &quality = chart->quality(); if (quality.boundaryIntersection || quality.flippedTriangleCount > 0) invalidCharts.push_back(chart); } if (invalidCharts.isEmpty()) return; // Recompute charts with invalid parameterizations. - Array<uint32_t> meshFaces; + PiecewiseParam &pp = piecewiseParam->get(); for (uint32_t i = 0; i < invalidCharts.size(); i++) { Chart *invalidChart = invalidCharts[i]; - const Mesh *invalidMesh = invalidChart->mesh(); - const uint32_t faceCount = invalidMesh->faceCount(); - meshFaces.resize(faceCount); - float invalidChartArea = 0.0f; - for (uint32_t j = 0; j < faceCount; j++) { - meshFaces[j] = invalidChart->mapFaceToSourceFace(j); - invalidChartArea += invalidMesh->faceArea(j); - } - ChartOptions options = m_chartOptions; - options.maxChartArea = invalidChartArea * 0.2f; - options.maxThreshold = 0.25f; - options.maxIterations = 3; - segment::Atlas atlas(m_mesh, &meshFaces, options); - buildAtlas(atlas, options); - for (uint32_t j = 0; j < atlas.chartCount(); j++) { - Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, &atlas, m_mesh, j, m_sourceId, m_id, m_chartArray.size()); - m_chartArray.push_back(chart); - m_paramAddedChartsCount++; + // 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. } + pp.reset(invalidMesh, faceCount); #if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS char filename[256]; - XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_recomputed_chart_%u.obj", m_sourceId, m_id, i); + XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_recomputed_chart_%03u.obj", m_sourceId, m_id, m_paramAddedChartsCount); FILE *file; XA_FOPEN(file, filename, "w"); - if (file) { - m_mesh->writeObjVertices(file); - for (uint32_t j = 0; j < builder.chartCount(); j++) { - fprintf(file, "o chart_%04d\n", j); + uint32_t subChartIndex = 0; +#endif + for (;;) { + if (!pp.computeChart()) + break; + Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, chartBuffers->get(), invalidChart, invalidMesh, pp.chartFaces(), pp.texcoords(), m_mesh, m_sourceId, m_id, m_charts.size()); + m_charts.push_back(chart); +#if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS + if (file) { + for (uint32_t j = 0; j < invalidMesh->vertexCount(); j++) { + fprintf(file, "v %g %g %g\n", invalidMesh->position(j).x, invalidMesh->position(j).y, invalidMesh->position(j).z); + fprintf(file, "vt %g %g\n", pp.texcoords()[j].x, pp.texcoords()[j].y); + } + fprintf(file, "o chart%03u\n", subChartIndex); fprintf(file, "s off\n"); - const Array<uint32_t> &faces = builder.chartFaces(j); - for (uint32_t f = 0; f < faces.size(); f++) - m_mesh->writeObjFace(file, faces[f]); + for (uint32_t f = 0; f < pp.chartFaces().length; f++) { + fprintf(file, "f "); + const uint32_t face = pp.chartFaces()[f]; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t index = invalidMesh->vertexCount() * subChartIndex + invalidMesh->vertexAt(face * 3 + j) + 1; // 1-indexed + fprintf(file, "%d/%d/%c", index, index, j == 2 ? '\n' : ' '); + } + } } - fclose(file); + subChartIndex++; +#endif + m_paramAddedChartsCount++; } +#if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS + if (file) + fclose(file); #endif } - // Parameterize the new charts. - taskGroup = taskScheduler->createTaskGroup(m_chartArray.size() - chartCount); - taskArgs.resize(m_chartArray.size() - chartCount); - for (uint32_t i = chartCount; i < m_chartArray.size(); i++) { - ParameterizeChartTaskArgs &args = taskArgs[i - chartCount]; - args.chart = m_chartArray[i]; - args.func = func; - Task task; - task.userData = &args; - task.func = runParameterizeChartTask; - taskScheduler->run(taskGroup, task); - } - taskScheduler->wait(&taskGroup); // Remove and delete the invalid charts. for (uint32_t i = 0; i < invalidCharts.size(); i++) { Chart *chart = invalidCharts[i]; @@ -6325,7 +7313,6 @@ public: m_paramDeletedChartsCount++; } #endif // XA_RECOMPUTE_CHARTS -#endif // XA_SKIP_PARAMETERIZATION } private: @@ -6343,19 +7330,18 @@ private: atlas.resetCharts(); // Restart process growing charts in parallel. uint32_t iteration = 0; - while (true) { - if (!atlas.growCharts(options.maxThreshold)) { - // If charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration. - atlas.fillHoles(options.maxThreshold * 0.5f); + for (;;) { + atlas.growCharts(options.maxThreshold); + // When charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration. + atlas.fillHoles(options.maxThreshold * 0.5f); #if XA_MERGE_CHARTS - atlas.mergeCharts(); + atlas.mergeCharts(); #endif - if (++iteration == options.maxIterations) - break; - if (!atlas.relocateSeeds()) - break; - atlas.resetCharts(); - } + if (++iteration == options.maxIterations) + break; + if (!atlas.relocateSeeds()) + break; + atlas.resetCharts(); } // Make sure no holes are left! XA_DEBUG_ASSERT(atlas.facesLeft() == 0); @@ -6363,9 +7349,9 @@ private: void removeChart(const Chart *chart) { - for (uint32_t i = 0; i < m_chartArray.size(); i++) { - if (m_chartArray[i] == chart) { - m_chartArray.removeAt(i); + for (uint32_t i = 0; i < m_charts.size(); i++) { + if (m_charts[i] == chart) { + m_charts.removeAt(i); return; } } @@ -6376,7 +7362,7 @@ private: Mesh *m_mesh; Array<uint32_t> m_faceToSourceFaceMap; // List of faces of the source mesh that belong to this chart group. Array<uint32_t> m_vertexToSourceVertexMap; // Map vertices of the mesh to vertices of the source mesh. - Array<Chart *> m_chartArray; + Array<Chart *> m_charts; ChartOptions m_chartOptions; 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. @@ -6384,7 +7370,7 @@ private: struct CreateChartGroupTaskArgs { - uint32_t faceGroup; + uint16_t faceGroup; uint32_t groupId; const Mesh *mesh; ChartGroup **chartGroup; @@ -6402,6 +7388,8 @@ struct ComputeChartsTaskArgs { TaskScheduler *taskScheduler; ChartGroup *chartGroup; + ThreadLocal<segment::Atlas> *atlas; + ThreadLocal<ChartCtorBuffers> *chartBuffers; const ChartOptions *options; Progress *progress; }; @@ -6412,7 +7400,7 @@ static void runComputeChartsJob(void *userData) if (args->progress->cancel) return; XA_PROFILE_START(computeChartsThread) - args->chartGroup->computeCharts(args->taskScheduler, *args->options); + args->chartGroup->computeCharts(args->taskScheduler, *args->options, args->atlas->get(), args->chartBuffers); XA_PROFILE_END(computeChartsThread) args->progress->value++; args->progress->update(); @@ -6423,6 +7411,11 @@ struct ParameterizeChartsTaskArgs TaskScheduler *taskScheduler; ChartGroup *chartGroup; ParameterizeFunc func; + ThreadLocal<UniformGrid2> *boundaryGrid; + ThreadLocal<ChartCtorBuffers> *chartBuffers; +#if XA_RECOMPUTE_CHARTS + ThreadLocal<PiecewiseParam> *piecewiseParam; +#endif Progress *progress; }; @@ -6432,7 +7425,11 @@ static void runParameterizeChartsJob(void *userData) if (args->progress->cancel) return; XA_PROFILE_START(parameterizeChartsThread) - args->chartGroup->parameterizeCharts(args->taskScheduler, args->func); +#if XA_RECOMPUTE_CHARTS + args->chartGroup->parameterizeCharts(args->taskScheduler, args->func, args->boundaryGrid, args->chartBuffers, args->piecewiseParam); +#else + args->chartGroup->parameterizeCharts(args->taskScheduler, args->func, args->boundaryGrid, args->chartBuffers); +#endif XA_PROFILE_END(parameterizeChartsThread) args->progress->value++; args->progress->update(); @@ -6482,31 +7479,17 @@ public: // This function is thread safe. void addMesh(TaskScheduler *taskScheduler, const Mesh *mesh) { - // Get list of face groups. - const uint32_t faceCount = mesh->faceCount(); - Array<uint32_t> faceGroups; - for (uint32_t f = 0; f < faceCount; f++) { - const uint32_t group = mesh->faceGroupAt(f); - bool exists = false; - for (uint32_t g = 0; g < faceGroups.size(); g++) { - if (faceGroups[g] == group) { - exists = true; - break; - } - } - if (!exists) - faceGroups.push_back(group); - } // Create one chart group per face group. + // If there's any ignored faces in the mesh, create an extra face group for that (vertex map). // Chart group creation is slow since it copies a chunk of the source mesh, so use tasks. Array<ChartGroup *> chartGroups; - chartGroups.resize(faceGroups.size()); + chartGroups.resize(mesh->faceGroupCount() + (mesh->ignoredFaceCount() > 0 ? 1 : 0)); Array<CreateChartGroupTaskArgs> taskArgs; taskArgs.resize(chartGroups.size()); for (uint32_t g = 0; g < chartGroups.size(); g++) { CreateChartGroupTaskArgs &args = taskArgs[g]; args.chartGroup = &chartGroups[g]; - args.faceGroup = faceGroups[g]; + args.faceGroup = uint16_t(g < mesh->faceGroupCount() ? g : Mesh::kInvalidFaceGroup); args.groupId = g; args.mesh = mesh; } @@ -6561,6 +7544,8 @@ public: chartGroupCount++; } Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, chartGroupCount); + ThreadLocal<segment::Atlas> atlas; + ThreadLocal<ChartCtorBuffers> chartBuffers; Array<ComputeChartsTaskArgs> taskArgs; taskArgs.reserve(chartGroupCount); for (uint32_t i = 0; i < m_chartGroups.size(); i++) { @@ -6568,6 +7553,8 @@ public: ComputeChartsTaskArgs args; args.taskScheduler = taskScheduler; args.chartGroup = m_chartGroups[i]; + args.atlas = &atlas; + args.chartBuffers = &chartBuffers; args.options = &options; args.progress = &progress; taskArgs.push_back(args); @@ -6605,6 +7592,11 @@ public: chartGroupCount++; } Progress progress(ProgressCategory::ParameterizeCharts, progressFunc, progressUserData, chartGroupCount); + ThreadLocal<UniformGrid2> boundaryGrid; // For Quality boundary intersection. + ThreadLocal<ChartCtorBuffers> chartBuffers; +#if XA_RECOMPUTE_CHARTS + ThreadLocal<PiecewiseParam> piecewiseParam; +#endif Array<ParameterizeChartsTaskArgs> taskArgs; taskArgs.reserve(chartGroupCount); for (uint32_t i = 0; i < m_chartGroups.size(); i++) { @@ -6613,6 +7605,11 @@ public: args.taskScheduler = taskScheduler; args.chartGroup = m_chartGroups[i]; args.func = func; + args.boundaryGrid = &boundaryGrid; + args.chartBuffers = &chartBuffers; +#if XA_RECOMPUTE_CHARTS + args.piecewiseParam = &piecewiseParam; +#endif args.progress = &progress; taskArgs.push_back(args); } @@ -6646,55 +7643,6 @@ private: namespace pack { -#if XA_DEBUG_EXPORT_ATLAS_IMAGES -const uint8_t TGA_TYPE_RGB = 2; -const uint8_t TGA_ORIGIN_UPPER = 0x20; - -#pragma pack(push, 1) -struct TgaHeader -{ - uint8_t id_length; - uint8_t colormap_type; - uint8_t image_type; - uint16_t colormap_index; - uint16_t colormap_length; - uint8_t colormap_size; - uint16_t x_origin; - uint16_t y_origin; - uint16_t width; - uint16_t height; - uint8_t pixel_size; - uint8_t flags; - enum { Size = 18 }; -}; -#pragma pack(pop) - -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"); - if (!f) - return; - TgaHeader tga; - tga.id_length = 0; - tga.colormap_type = 0; - tga.image_type = TGA_TYPE_RGB; - tga.colormap_index = 0; - tga.colormap_length = 0; - tga.colormap_size = 0; - tga.x_origin = 0; - tga.y_origin = 0; - tga.width = (uint16_t)width; - tga.height = (uint16_t)height; - tga.pixel_size = 24; - tga.flags = TGA_ORIGIN_UPPER; - fwrite(&tga, sizeof(TgaHeader), 1, f); - fwrite(data, sizeof(uint8_t), width * height * 3, f); - fclose(f); -} -#endif - class AtlasImage { public: @@ -6728,13 +7676,13 @@ public: const int xx = x + offset_x; if (xx >= 0 && xx < atlas_w && yy < atlas_h) { const uint32_t dataOffset = xx + yy * m_width; - if (image->bitAt(x, y)) { + if (image->get(x, y)) { XA_DEBUG_ASSERT(m_data[dataOffset] == 0); m_data[dataOffset] = chartIndex | kImageHasChartIndexBit; - } else if (imageBilinear && imageBilinear->bitAt(x, y)) { + } else if (imageBilinear && imageBilinear->get(x, y)) { XA_DEBUG_ASSERT(m_data[dataOffset] == 0); m_data[dataOffset] = chartIndex | kImageHasChartIndexBit | kImageIsBilinearBit; - } else if (imagePadding && imagePadding->bitAt(x, y)) { + } else if (imagePadding && imagePadding->get(x, y)) { XA_DEBUG_ASSERT(m_data[dataOffset] == 0); m_data[dataOffset] = chartIndex | kImageHasChartIndexBit | kImageIsPaddingBit; } @@ -6807,6 +7755,8 @@ struct Chart bool allowRotate; // bounding box Vector2 majorAxis, minorAxis, minCorner, maxCorner; + // Mesh only + const Array<uint32_t> *boundaryEdges; // UvMeshChart only Array<uint32_t> faces; @@ -6816,6 +7766,7 @@ struct Chart struct AddChartTaskArgs { + ThreadLocal<BoundingBox2D> *boundingBox; param::Chart *paramChart; Chart *chart; // out }; @@ -6834,102 +7785,32 @@ static void runAddChartTask(void *userData) chart->material = 0; chart->indexCount = mesh->indexCount(); chart->indices = mesh->indices(); - chart->parametricArea = paramChart->computeParametricArea(); + chart->parametricArea = mesh->computeParametricArea(); if (chart->parametricArea < kAreaEpsilon) { // When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers. const Vector2 bounds = paramChart->computeParametricBounds(); chart->parametricArea = bounds.x * bounds.y; } - chart->surfaceArea = paramChart->computeSurfaceArea(); + chart->surfaceArea = mesh->computeSurfaceArea(); chart->vertices = mesh->texcoords(); chart->vertexCount = mesh->vertexCount(); chart->allowRotate = true; - // Compute list of boundary vertices. - Array<Vector2> boundary; - boundary.reserve(16); + chart->boundaryEdges = &mesh->boundaryEdges(); + // Compute bounding box of chart. + BoundingBox2D &bb = args->boundingBox->get(); + bb.clear(); for (uint32_t v = 0; v < chart->vertexCount; v++) { if (mesh->isBoundaryVertex(v)) - boundary.push_back(mesh->texcoord(v)); + bb.appendBoundaryVertex(mesh->texcoord(v)); } - XA_DEBUG_ASSERT(boundary.size() > 0); - // Compute bounding box of chart. - static thread_local BoundingBox2D boundingBox; - boundingBox.compute(boundary.data(), boundary.size(), mesh->texcoords(), mesh->vertexCount()); - chart->majorAxis = boundingBox.majorAxis(); - chart->minorAxis = boundingBox.minorAxis(); - chart->minCorner = boundingBox.minCorner(); - chart->maxCorner = boundingBox.maxCorner(); + bb.compute(mesh->texcoords(), mesh->vertexCount()); + chart->majorAxis = bb.majorAxis; + chart->minorAxis = bb.minorAxis; + chart->minCorner = bb.minCorner; + chart->maxCorner = bb.maxCorner; XA_PROFILE_END(packChartsAddChartsThread) } -struct FindChartLocationBruteForceTaskArgs -{ - std::atomic<bool> *finished; // One of the tasks found a location that doesn't expand the atlas. - Vector2i startPosition; - const BitImage *atlasBitImage; - const BitImage *chartBitImage; - const BitImage *chartBitImageRotated; - int w, h; - bool blockAligned, allowRotate; - uint32_t maxResolution; - // out - bool best_insideAtlas; - int best_metric, best_x, best_y, best_w, best_h, best_r; -}; - -static void runFindChartLocationBruteForceTask(void *userData) -{ - XA_PROFILE_START(packChartsFindLocationThread) - auto args = (FindChartLocationBruteForceTaskArgs *)userData; - args->best_metric = INT_MAX; - if (args->finished->load()) - return; - // Try two different orientations. - for (int r = 0; r < 2; r++) { - if (args->finished->load()) - break; - int cw = args->chartBitImage->width(); - int ch = args->chartBitImage->height(); - if (r == 1) { - if (args->allowRotate) - swap(cw, ch); - else - break; - } - const int y = args->startPosition.y; - const int stepSize = args->blockAligned ? 4 : 1; - for (int x = args->startPosition.x; x <= args->w + stepSize; x += stepSize) { - if (args->maxResolution > 0 && (x > (int)args->maxResolution - cw || y > (int)args->maxResolution - ch)) - continue; - if (args->finished->load()) - break; - // Early out if metric not better. - const int area = max(args->w, x + cw) * max(args->h, y + ch); - const int extents = max(max(args->w, x + cw), max(args->h, y + ch)); - const int metric = extents * extents + area; - if (metric > args->best_metric) - continue; - // If metric is the same, pick the one closest to the origin. - if (metric == args->best_metric && max(x, y) >= max(args->best_x, args->best_y)) - continue; - if (!args->atlasBitImage->canBlit(r == 1 ? *(args->chartBitImageRotated) : *(args->chartBitImage), x, y)) - continue; - args->best_metric = metric; - args->best_insideAtlas = area == args->w * args->h; - args->best_x = x; - args->best_y = y; - args->best_w = cw; - args->best_h = ch; - args->best_r = r; - if (args->best_insideAtlas) { - args->finished->store(true); - break; - } - } - } - XA_PROFILE_END(packChartsFindLocationThread) -} - struct Atlas { ~Atlas() @@ -6975,6 +7856,7 @@ struct Atlas taskArgs.resize(chartCount); TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount); uint32_t chartIndex = 0; + ThreadLocal<BoundingBox2D> boundingBox; for (uint32_t i = 0; i < chartGroupsCount; i++) { const param::ChartGroup *chartGroup = paramAtlas->chartGroupAt(i); if (chartGroup->isVertexMap()) @@ -6982,6 +7864,7 @@ struct Atlas const uint32_t count = chartGroup->chartCount(); for (uint32_t j = 0; j < count; j++) { AddChartTaskArgs &args = taskArgs[chartIndex]; + args.boundingBox = &boundingBox; args.paramChart = chartGroup->chartAt(j); Task task; task.userData = &taskArgs[chartIndex]; @@ -7000,8 +7883,6 @@ struct Atlas void addUvMeshCharts(UvMeshInstance *mesh) { BitArray vertexUsed(mesh->texcoords.size()); - Array<Vector2> boundary; - boundary.reserve(16); BoundingBox2D boundingBox; for (uint32_t c = 0; c < mesh->mesh->charts.size(); c++) { UvMeshChart *uvChart = mesh->mesh->charts[c]; @@ -7013,14 +7894,15 @@ struct Atlas chart->vertices = mesh->texcoords.data(); chart->vertexCount = mesh->texcoords.size(); chart->allowRotate = mesh->rotateCharts; + 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.clearAll(); + vertexUsed.zeroOutMemory(); for (uint32_t i = 0; i < chart->indexCount; i++) { const uint32_t vertex = chart->indices[i]; - if (!vertexUsed.bitAt(vertex)) { - vertexUsed.setBitAt(vertex); + if (!vertexUsed.get(vertex)) { + vertexUsed.set(vertex); chart->uniqueVertices.push_back(vertex); } } @@ -7045,24 +7927,22 @@ struct Atlas const Vector2 bounds = (maxCorner - minCorner) * 0.5f; chart->parametricArea = bounds.x * bounds.y; } - // Compute list of boundary vertices. + // Compute bounding box of chart. // Using all unique vertices for simplicity, can compute real boundaries if this is too slow. - boundary.clear(); + boundingBox.clear(); for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) - boundary.push_back(chart->uniqueVertexAt(v)); - XA_DEBUG_ASSERT(boundary.size() > 0); - // Compute bounding box of chart. - boundingBox.compute(boundary.data(), boundary.size(), boundary.data(), boundary.size()); - chart->majorAxis = boundingBox.majorAxis(); - chart->minorAxis = boundingBox.minorAxis(); - chart->minCorner = boundingBox.minCorner(); - chart->maxCorner = boundingBox.maxCorner(); + boundingBox.appendBoundaryVertex(chart->uniqueVertexAt(v)); + boundingBox.compute(); + chart->majorAxis = boundingBox.majorAxis; + chart->minorAxis = boundingBox.minorAxis; + chart->minCorner = boundingBox.minCorner; + chart->maxCorner = boundingBox.maxCorner; m_charts.push_back(chart); } } // Pack charts in the smallest possible rectangle. - bool packCharts(TaskScheduler *taskScheduler, const PackOptions &options, ProgressFunc progressFunc, void *progressUserData) + bool packCharts(const PackOptions &options, ProgressFunc progressFunc, void *progressUserData) { if (progressFunc) { if (!progressFunc(ProgressCategory::PackCharts, 0, progressUserData)) @@ -7107,10 +7987,11 @@ struct Atlas for (uint32_t c = 0; c < chartCount; c++) { Chart *chart = m_charts[c]; // Compute chart scale - float scale = (chart->surfaceArea / chart->parametricArea) * m_texelsPerUnit; - if (chart->parametricArea == 0.0f) - scale = 0; - XA_ASSERT(isFinite(scale)); + float scale = 1.0f; + if (chart->parametricArea != 0.0f) { + scale = (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) { @@ -7181,6 +8062,8 @@ struct Atlas texcoord.y += 0.5f + options.padding; extents = max(extents, texcoord); } + if (extents.x > resolution || extents.y > resolution) + XA_PRINT(" Chart %u extents are large (%gx%g)\n", c, extents.x, extents.y); chartExtents[c] = extents; chartOrderArray[c] = extents.x + extents.y; // Use perimeter for chart sort key. minChartPerimeter = min(minChartPerimeter, chartOrderArray[c]); @@ -7207,6 +8090,7 @@ struct Atlas // Rotated versions swap x and y. BitImage chartImage, chartImageBilinear, chartImagePadding; BitImage chartImageRotated, chartImageBilinearRotated, chartImagePaddingRotated; + UniformGrid2 boundaryEdgeGrid; Array<Vector2i> atlasSizes; atlasSizes.push_back(Vector2i(0, 0)); int progress = 0; @@ -7249,7 +8133,7 @@ struct Atlas } // Expand chart by pixels sampled by bilinear interpolation. if (options.bilinear) - bilinearExpand(chart, &chartImage, &chartImageBilinear, chart->allowRotate ? &chartImageBilinearRotated : nullptr); + bilinearExpand(chart, &chartImage, &chartImageBilinear, chart->allowRotate ? &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). @@ -7310,7 +8194,7 @@ struct Atlas chartStartPositions.push_back(Vector2i(0, 0)); } XA_PROFILE_START(packChartsFindLocation) - const bool foundLocation = findChartLocation(taskScheduler, 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(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); 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) { @@ -7359,6 +8243,13 @@ struct Atlas } else { m_atlasImages[currentAtlas]->addChart(c, &chartImageRotated, options.bilinear ? &chartImageBilinearRotated : nullptr, options.padding > 0 ? &chartImagePaddingRotated : nullptr, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, best_x, best_y); } +#if XA_DEBUG_EXPORT_ATLAS_IMAGES && XA_DEBUG_EXPORT_ATLAS_IMAGES_PER_CHART + for (uint32_t j = 0; j < m_atlasImages.size(); j++) { + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_atlas_image%02u_chart%04u.tga", j, i); + m_atlasImages[j]->writeTga(filename, (uint32_t)atlasSizes[j].x, (uint32_t)atlasSizes[j].y); + } +#endif } chart->atlasIndex = (int32_t)currentAtlas; // Modify texture coordinates: @@ -7415,7 +8306,7 @@ struct Atlas uint32_t count = 0; for (uint32_t y = 0; y < m_height; y++) { for (uint32_t x = 0; x < m_width; x++) - count += m_bitImages[i]->bitAt(x, y); + count += m_bitImages[i]->get(x, y); } m_utilization[i] = float(count) / (m_width * m_height); } @@ -7445,70 +8336,56 @@ private: // 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(TaskScheduler *taskScheduler, 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 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) { const int attempts = 4096; if (bruteForce || attempts >= w * h) - return findChartLocation_bruteForce(taskScheduler, startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned, maxResolution, allowRotate); + 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); } - bool findChartLocation_bruteForce(TaskScheduler *taskScheduler, 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) + 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; - const int chartMinHeight = min(chartBitImage->height(), chartBitImageRotated->height()); - uint32_t taskCount = 0; - for (int y = startPosition.y; y <= h + stepSize; y += stepSize) { - if (maxResolution > 0 && y > (int)maxResolution - chartMinHeight) - break; - taskCount++; - } - m_bruteForceTaskArgs.clear(); - m_bruteForceTaskArgs.resize(taskCount); - TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(taskCount); - std::atomic<bool> finished(false); // One of the tasks found a location that doesn't expand the atlas. - uint32_t i = 0; - for (int y = startPosition.y; y <= h + stepSize; y += stepSize) { - if (maxResolution > 0 && y > (int)maxResolution - chartMinHeight) - break; - FindChartLocationBruteForceTaskArgs &args = m_bruteForceTaskArgs[i]; - args.finished = &finished; - args.startPosition = Vector2i(y == startPosition.y ? startPosition.x : 0, y); - args.atlasBitImage = atlasBitImage; - args.chartBitImage = chartBitImage; - args.chartBitImageRotated = chartBitImageRotated; - args.w = w; - args.h = h; - args.blockAligned = blockAligned; - args.allowRotate = allowRotate; - args.maxResolution = maxResolution; - Task task; - task.userData = &m_bruteForceTaskArgs[i]; - task.func = runFindChartLocationBruteForceTask; - taskScheduler->run(taskGroup, task); - i++; - } - taskScheduler->wait(&taskGroup); - // Find the task result with the best metric. int best_metric = INT_MAX; - bool best_insideAtlas = false; - for (i = 0; i < taskCount; i++) { - FindChartLocationBruteForceTaskArgs &args = m_bruteForceTaskArgs[i]; - if (args.best_metric > best_metric) - continue; - // A location that doesn't expand the atlas is always preferred. - if (!args.best_insideAtlas && best_insideAtlas) - continue; - // If metric is the same, pick the one closest to the origin. - if (args.best_insideAtlas == best_insideAtlas && args.best_metric == best_metric && max(args.best_x, args.best_y) >= max(*best_x, *best_y)) - continue; - best_metric = args.best_metric; - best_insideAtlas = args.best_insideAtlas; - *best_x = args.best_x; - *best_y = args.best_y; - *best_w = args.best_w; - *best_h = args.best_h; - *best_r = args.best_r; + // Try two different orientations. + for (int r = 0; r < 2; r++) { + int cw = chartBitImage->width(); + int ch = chartBitImage->height(); + if (r == 1) { + if (allowRotate) + swap(cw, ch); + else + break; + } + for (int y = startPosition.y; y <= h + stepSize; y += stepSize) { + if (maxResolution > 0 && y > (int)maxResolution - ch) + break; + for (int x = (y == startPosition.y ? startPosition.x : 0); x <= w + stepSize; x += stepSize) { + if (maxResolution > 0 && x > (int)maxResolution - cw) + break; + // Early out if metric is not better. + const int extentX = max(w, x + cw), extentY = max(h, y + ch); + const int area = extentX * extentY; + const int extents = max(extentX, extentY); + const int metric = extents * extents + area; + if (metric > best_metric) + continue; + // If metric is the same, pick the one closest to the origin. + if (metric == best_metric && max(x, y) >= max(*best_x, *best_y)) + continue; + if (!atlasBitImage->canBlit(r == 1 ? *chartBitImageRotated : *chartBitImage, x, y)) + continue; + best_metric = metric; + *best_x = x; + *best_y = y; + *best_w = cw; + *best_h = ch; + *best_r = r; + if (area == w * h) + return true; // Chart is completely inside, do not look at any other location. + } + } } return best_metric != INT_MAX; } @@ -7581,10 +8458,10 @@ private: for (int x = 0; x < w; x++) { int xx = x + offset_x; if (xx >= 0) { - if (image->bitAt(x, y)) { + if (image->get(x, y)) { if (xx < atlas_w && yy < atlas_h) { - XA_DEBUG_ASSERT(atlasBitImage->bitAt(xx, yy) == false); - atlasBitImage->setBitAt(xx, yy); + XA_DEBUG_ASSERT(atlasBitImage->get(xx, yy) == false); + atlasBitImage->set(xx, yy); } } } @@ -7593,14 +8470,23 @@ private: } } - void bilinearExpand(const Chart *chart, BitImage *source, BitImage *dest, BitImage *destRotated) 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++) + boundaryEdgeGrid.append(i); + } const int xOffsets[] = { -1, 0, 1, -1, 1, -1, 0, 1 }; const int yOffsets[] = { -1, -1, -1, 0, 0, 1, 1, 1 }; for (uint32_t y = 0; y < source->height(); y++) { for (uint32_t x = 0; x < source->width(); x++) { // Copy pixels from source. - if (source->bitAt(x, y)) + if (source->get(x, y)) goto setPixel; // Empty pixel. If none of of the surrounding pixels are set, this pixel can't be sampled by bilinear interpolation. { @@ -7610,44 +8496,32 @@ private: const int sy = (int)y + yOffsets[s]; if (sx < 0 || sy < 0 || sx >= (int)source->width() || sy >= (int)source->height()) continue; - if (source->bitAt((uint32_t)sx, (uint32_t)sy)) + if (source->get((uint32_t)sx, (uint32_t)sy)) break; } if (s == 8) continue; } - // If a 2x2 square centered on the pixels centroid intersects the triangle, this pixel will be sampled by bilinear interpolation. - // See "Precomputed Global Illumination in Frostbite (GDC 2018)" page 95 - for (uint32_t f = 0; f < chart->indexCount / 3; f++) { + { + // If a 2x2 square centered on the pixels centroid intersects the triangle, this pixel will be sampled by bilinear interpolation. + // See "Precomputed Global Illumination in Frostbite (GDC 2018)" page 95 const Vector2 centroid((float)x + 0.5f, (float)y + 0.5f); - Vector2 vertices[3]; - for (uint32_t i = 0; i < 3; i++) - vertices[i] = chart->vertices[chart->indices[f * 3 + i]]; - // Test for triangle vertex in square bounds. - for (uint32_t i = 0; i < 3; i++) { - const Vector2 &v = vertices[i]; - if (v.x > centroid.x - 1.0f && v.x < centroid.x + 1.0f && v.y > centroid.y - 1.0f && v.y < centroid.y + 1.0f) - goto setPixel; - } - // Test for triangle edge intersection with square edge. const Vector2 squareVertices[4] = { Vector2(centroid.x - 1.0f, centroid.y - 1.0f), Vector2(centroid.x + 1.0f, centroid.y - 1.0f), Vector2(centroid.x + 1.0f, centroid.y + 1.0f), Vector2(centroid.x - 1.0f, centroid.y + 1.0f) }; - for (uint32_t i = 0; i < 3; i++) { - for (uint32_t j = 0; j < 4; j++) { - if (linesIntersect(vertices[i], vertices[(i + 1) % 3], squareVertices[j], squareVertices[(j + 1) % 4], 0.0f)) - goto setPixel; - } + for (uint32_t j = 0; j < 4; j++) { + if (boundaryEdgeGrid.intersect(squareVertices[j], squareVertices[(j + 1) % 4], 0.0f)) + goto setPixel; } } continue; setPixel: - dest->setBitAt(x, y); + dest->set(x, y); if (destRotated) - destRotated->setBitAt(y, x); + destRotated->set(y, x); } } } @@ -7660,9 +8534,9 @@ private: static bool drawTriangleCallback(void *param, int x, int y) { auto args = (DrawTriangleCallbackArgs *)param; - args->chartBitImage->setBitAt(x, y); + args->chartBitImage->set(x, y); if (args->chartBitImageRotated) - args->chartBitImageRotated->setBitAt(y, x); + args->chartBitImageRotated->set(y, x); return true; } @@ -7670,7 +8544,6 @@ private: Array<float> m_utilization; Array<BitImage *> m_bitImages; Array<Chart *> m_charts; - Array<FindChartLocationBruteForceTaskArgs> m_bruteForceTaskArgs; RadixSort m_radix; uint32_t m_width = 0; uint32_t m_height = 0; @@ -7789,13 +8662,6 @@ static void runAddMeshTask(void *userData) } if (progress->cancel) goto cleanup; - { - XA_PROFILE_START(addMeshCreateBoundaries) - mesh->createBoundaries(); - XA_PROFILE_END(addMeshCreateBoundaries) - } - if (progress->cancel) - goto cleanup; #if XA_DEBUG_EXPORT_OBJ_SOURCE_MESHES char filename[256]; XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u.obj", mesh->id()); @@ -7805,22 +8671,22 @@ static void runAddMeshTask(void *userData) mesh->writeObjVertices(file); // groups uint32_t numGroups = 0; - for (uint32_t i = 0; i < mesh->faceGroupCount(); i++) { - if (mesh->faceGroupAt(i) != UINT32_MAX) + for (uint32_t i = 0; i < mesh->faceCount(); i++) { + if (mesh->faceGroupAt(i) != Mesh::kInvalidFaceGroup) numGroups = internal::max(numGroups, mesh->faceGroupAt(i) + 1); } for (uint32_t i = 0; i < numGroups; i++) { fprintf(file, "o group_%04d\n", i); fprintf(file, "s off\n"); - for (uint32_t f = 0; f < mesh->faceGroupCount(); f++) { + for (uint32_t f = 0; f < mesh->faceCount(); f++) { if (mesh->faceGroupAt(f) == i) mesh->writeObjFace(file, f); } } fprintf(file, "o group_ignored\n"); fprintf(file, "s off\n"); - for (uint32_t f = 0; f < mesh->faceGroupCount(); f++) { - if (mesh->faceGroupAt(f) == UINT32_MAX) + for (uint32_t f = 0; f < mesh->faceCount(); f++) { + if (mesh->faceGroupAt(f) == Mesh::kInvalidFaceGroup) mesh->writeObjFace(file, f); } mesh->writeObjBoundaryEges(file); @@ -8033,7 +8899,6 @@ void AddMeshJoin(Atlas *atlas) XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", addMeshThread) XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", addMeshCreateColocals) XA_PROFILE_PRINT_AND_RESET(" Create face groups: ", addMeshCreateFaceGroups) - XA_PROFILE_PRINT_AND_RESET(" Create boundaries: ", addMeshCreateBoundaries) XA_PROFILE_PRINT_AND_RESET(" Create chart groups (real): ", addMeshCreateChartGroupsReal) XA_PROFILE_PRINT_AND_RESET(" Create chart groups (thread): ", addMeshCreateChartGroupsThread) XA_PRINT_MEM_USAGE @@ -8044,16 +8909,7 @@ struct EdgeKey EdgeKey() {} EdgeKey(const EdgeKey &k) : v0(k.v0), v1(k.v1) {} EdgeKey(uint32_t v0, uint32_t v1) : v0(v0), v1(v1) {} - - void operator=(const EdgeKey &k) - { - v0 = k.v0; - v1 = k.v1; - } - bool operator==(const EdgeKey &k) const - { - return v0 == k.v0 && v1 == k.v1; - } + bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; } uint32_t v0; uint32_t v1; @@ -8119,15 +8975,15 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl) for (uint32_t i = 0; i < indexCount; i++) vertexToFaceMap.add(meshInstance->texcoords[mesh->indices[i]]); internal::BitArray faceAssigned(faceCount); - faceAssigned.clearAll(); + faceAssigned.zeroOutMemory(); for (uint32_t f = 0; f < faceCount; f++) { - if (faceAssigned.bitAt(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.setBitAt(f); + faceAssigned.set(f); chart->faces.push_back(f); for (;;) { bool newFaceAssigned = false; @@ -8140,8 +8996,8 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl) while (mapIndex != UINT32_MAX) { const uint32_t face2 = mapIndex / 3; // 3 vertices added per face. // Materials must match. - if (!faceAssigned.bitAt(face2) && (!decl.faceMaterialData || decl.faceMaterialData[face] == decl.faceMaterialData[face2])) { - faceAssigned.setBitAt(face2); + if (!faceAssigned.get(face2) && (!decl.faceMaterialData || decl.faceMaterialData[face] == decl.faceMaterialData[face2])) { + faceAssigned.set(face2); chart->faces.push_back(face2); newFaceAssigned = true; } @@ -8202,6 +9058,7 @@ void ComputeCharts(Atlas *atlas, ChartOptions chartOptions) continue; 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) @@ -8210,8 +9067,7 @@ void ComputeCharts(Atlas *atlas, ChartOptions chartOptions) 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); - if (!chart->isDisk()) - XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): doesn't have disk topology\n", chartCount, i, j, k); +#endif holesCount += chart->closedHolesCount(); if (chart->closedHolesCount() > 0) chartsWithHolesCount++; @@ -8279,7 +9135,7 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) return; } XA_PROFILE_END(parameterizeChartsReal) - uint32_t chartCount = 0, orthoChartsCount = 0, planarChartsCount = 0, chartsAddedCount = 0, chartsDeletedCount = 0; + uint32_t chartCount = 0, orthoChartsCount = 0, planarChartsCount = 0, lscmChartsCount = 0, piecewiseChartsCount = 0, chartsAddedCount = 0, chartsDeletedCount = 0; for (uint32_t i = 0; i < ctx->meshCount; i++) { for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j); @@ -8287,19 +9143,23 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) continue; for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { const internal::param::Chart *chart = chartGroup->chartAt(k); - if (chart->isPlanar()) + if (chart->type() == ChartType::Planar) planarChartsCount++; - else if (chart->isOrtho()) + 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(); } } - XA_PRINT(" %u planar charts, %u ortho charts, %u other\n", planarChartsCount, orthoChartsCount, chartCount - (planarChartsCount + orthoChartsCount)); + 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 deleted due to invalid parameterizations, %u new charts added\n", chartsDeletedCount, chartsAddedCount); + XA_PRINT(" %u charts with invalid parameterizations replaced with %u new charts\n", chartsDeletedCount, chartsAddedCount); XA_PRINT(" %u charts\n", chartCount); } uint32_t chartIndex = 0, invalidParamCount = 0; @@ -8310,7 +9170,7 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) continue; for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { const internal::param::Chart *chart = chartGroup->chartAt(k); - const internal::param::ParameterizationQuality &quality = chart->paramQuality(); + const internal::param::Quality &quality = chart->quality(); #if XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION { char filename[256]; @@ -8319,13 +9179,20 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) } #endif bool invalid = false; + 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 (quality.boundaryIntersection) { invalid = true; - XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, self-intersecting boundary.\n", chartIndex, i, j, k, chart->isPlanar() ? "planar" : chart->isOrtho() ? "ortho" : "other"); + 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) { invalid = true; - XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, %u / %u flipped triangles.\n", chartIndex, i, j, k, chart->isPlanar() ? "planar" : chart->isOrtho() ? "ortho" : "other", quality.flippedTriangleCount, quality.totalTriangleCount); + 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 (invalid) invalidParamCount++; @@ -8415,7 +9282,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) packAtlas.addCharts(ctx->taskScheduler, &ctx->paramAtlas); XA_PROFILE_END(packChartsAddCharts) XA_PROFILE_START(packCharts) - if (!packAtlas.packCharts(ctx->taskScheduler, packOptions, ctx->progressFunc, ctx->progressUserData)) + if (!packAtlas.packCharts(packOptions, ctx->progressFunc, ctx->progressUserData)) return; XA_PROFILE_END(packCharts) // Populate atlas object with pack results. @@ -8440,8 +9307,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) XA_PROFILE_PRINT_AND_RESET(" Restore texcoords: ", packChartsAddChartsRestoreTexcoords) XA_PROFILE_PRINT_AND_RESET(" Rasterize: ", packChartsRasterize) XA_PROFILE_PRINT_AND_RESET(" Dilate (padding): ", packChartsDilate) - XA_PROFILE_PRINT_AND_RESET(" Find location (real): ", packChartsFindLocation) - XA_PROFILE_PRINT_AND_RESET(" Find location (thread): ", packChartsFindLocationThread) + XA_PROFILE_PRINT_AND_RESET(" Find location: ", packChartsFindLocation) XA_PROFILE_PRINT_AND_RESET(" Blit: ", packChartsBlit) XA_PRINT_MEM_USAGE XA_PRINT("Building output meshes\n"); @@ -8527,9 +9393,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) const int32_t atlasIndex = packAtlas.getChart(chartIndex)->atlasIndex; XA_DEBUG_ASSERT(atlasIndex >= 0); outputChart->atlasIndex = (uint32_t)atlasIndex; - outputChart->flags = 0; - if (chart->paramQuality().boundaryIntersection || chart->paramQuality().flippedTriangleCount > 0) - outputChart->flags |= ChartFlags::Invalid; + outputChart->type = 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++) diff --git a/thirdparty/xatlas/xatlas.h b/thirdparty/xatlas/xatlas.h index 7be165e7e5..e59f493287 100644 --- a/thirdparty/xatlas/xatlas.h +++ b/thirdparty/xatlas/xatlas.h @@ -35,11 +35,14 @@ Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com> namespace xatlas { -struct ChartFlags +struct ChartType { - enum + enum Enum { - Invalid = 1 << 0 + Planar, + Ortho, + LSCM, + Piecewise }; }; @@ -47,10 +50,10 @@ struct ChartFlags struct Chart { uint32_t atlasIndex; // Sub-atlas index. - uint32_t flags; uint32_t *faceArray; uint32_t faceCount; uint32_t material; + ChartType::Enum type; }; // Output vertex. diff --git a/version.py b/version.py index 9184facb73..930981f7af 100644 --- a/version.py +++ b/version.py @@ -2,6 +2,7 @@ short_name = "godot" name = "Godot Engine" major = 3 minor = 2 +patch = 0 status = "beta" module_config = "" year = 2020 |