Преглед изворни кода

Merge pull request #49237 from akien-mga/3.x-cherrypicks

Rémi Verschelde пре 4 година
родитељ
комит
e0fb05ad30

+ 5 - 6
core/bind/core_bind.cpp

@@ -760,7 +760,7 @@ int64_t _OS::get_unix_time_from_datetime(Dictionary datetime) const {
 	unsigned int hour = ((datetime.has(HOUR_KEY)) ? static_cast<unsigned int>(datetime[HOUR_KEY]) : 0);
 	unsigned int day = ((datetime.has(DAY_KEY)) ? static_cast<unsigned int>(datetime[DAY_KEY]) : 1);
 	unsigned int month = ((datetime.has(MONTH_KEY)) ? static_cast<unsigned int>(datetime[MONTH_KEY]) : 1);
-	unsigned int year = ((datetime.has(YEAR_KEY)) ? static_cast<unsigned int>(datetime[YEAR_KEY]) : 0);
+	unsigned int year = ((datetime.has(YEAR_KEY)) ? static_cast<unsigned int>(datetime[YEAR_KEY]) : 1970);
 
 	/// How many days come before each month (0-12)
 	static const unsigned short int DAYS_PAST_THIS_YEAR_TABLE[2][13] = {
@@ -771,15 +771,14 @@ int64_t _OS::get_unix_time_from_datetime(Dictionary datetime) const {
 	};
 
 	ERR_FAIL_COND_V_MSG(second > 59, 0, "Invalid second value of: " + itos(second) + ".");
-
 	ERR_FAIL_COND_V_MSG(minute > 59, 0, "Invalid minute value of: " + itos(minute) + ".");
-
 	ERR_FAIL_COND_V_MSG(hour > 23, 0, "Invalid hour value of: " + itos(hour) + ".");
-
+	ERR_FAIL_COND_V_MSG(year == 0, 0, "Years before 1 AD are not supported. Value passed: " + itos(year) + ".");
 	ERR_FAIL_COND_V_MSG(month > 12 || month == 0, 0, "Invalid month value of: " + itos(month) + ".");
-
 	// Do this check after month is tested as valid
-	ERR_FAIL_COND_V_MSG(day > MONTH_DAYS_TABLE[LEAPYEAR(year)][month - 1] || day == 0, 0, "Invalid day value of '" + itos(day) + "' which is larger than '" + itos(MONTH_DAYS_TABLE[LEAPYEAR(year)][month - 1]) + "' or 0.");
+	unsigned int days_in_month = MONTH_DAYS_TABLE[LEAPYEAR(year)][month - 1];
+	ERR_FAIL_COND_V_MSG(day == 0 || day > days_in_month, 0, "Invalid day value of: " + itos(day) + ". It should be comprised between 1 and " + itos(days_in_month) + " for month " + itos(month) + ".");
+
 	// Calculate all the seconds from months past in this year
 	uint64_t SECONDS_FROM_MONTHS_PAST_THIS_YEAR = DAYS_PAST_THIS_YEAR_TABLE[LEAPYEAR(year)][month - 1] * SECONDS_PER_DAY;
 

+ 1 - 1
doc/classes/OS.xml

@@ -550,7 +550,7 @@
 			<description>
 				Gets an epoch time value from a dictionary of time values.
 				[code]datetime[/code] must be populated with the following keys: [code]year[/code], [code]month[/code], [code]day[/code], [code]hour[/code], [code]minute[/code], [code]second[/code].
-				If the dictionary is empty [code]0[/code] is returned.
+				If the dictionary is empty [code]0[/code] is returned. If some keys are omitted, they default to the equivalent values for the UNIX epoch timestamp 0 (1970-01-01 at 00:00:00 UTC).
 				You can pass the output from [method get_datetime_from_unix_time] directly into this function. Daylight Savings Time ([code]dst[/code]), if present, is ignored.
 			</description>
 		</method>

+ 1 - 0
doc/classes/Resource.xml

@@ -27,6 +27,7 @@
 			<description>
 				Duplicates the resource, returning a new resource. By default, sub-resources are shared between resource copies for efficiency. This can be changed by passing [code]true[/code] to the [code]subresources[/code] argument which will copy the subresources.
 				[b]Note:[/b] If [code]subresources[/code] is [code]true[/code], this method will only perform a shallow copy. Nested resources within subresources will not be duplicated and will still be shared.
+				[b]Note:[/b] When duplicating a resource, only [code]export[/code]ed properties are copied. Other properties will be set to their default value in the new resource.
 			</description>
 		</method>
 		<method name="emit_changed">

+ 6 - 0
doc/classes/TextureButton.xml

@@ -17,6 +17,12 @@
 		<member name="expand" type="bool" setter="set_expand" getter="get_expand" default="false">
 			If [code]true[/code], the texture stretches to the edges of the node's bounding rectangle using the [member stretch_mode]. If [code]false[/code], the texture will not scale with the node.
 		</member>
+		<member name="flip_h" type="bool" setter="set_flip_h" getter="is_flipped_h" default="false">
+			If [code]true[/code], texture is flipped horizontally.
+		</member>
+		<member name="flip_v" type="bool" setter="set_flip_v" getter="is_flipped_v" default="false">
+			If [code]true[/code], texture is flipped vertically.
+		</member>
 		<member name="stretch_mode" type="int" setter="set_stretch_mode" getter="get_stretch_mode" enum="TextureButton.StretchMode" default="0">
 			Controls the texture's behavior when you resize the node's bounding rectangle, [b]only if[/b] [member expand] is [code]true[/code]. Set it to one of the [enum StretchMode] constants. See the constants to learn more.
 		</member>

+ 1 - 1
drivers/unix/dir_access_unix.cpp

@@ -432,7 +432,7 @@ uint64_t DirAccessUnix::get_space_left() {
 		return 0;
 	};
 
-	return vfs.f_bfree * vfs.f_bsize;
+	return (uint64_t)vfs.f_bavail * (uint64_t)vfs.f_frsize;
 #else
 	// FIXME: Implement this.
 	return 0;

+ 27 - 20
editor/editor_audio_buses.cpp

@@ -173,6 +173,9 @@ void EditorAudioBus::_notification(int p_what) {
 			bypass->set_icon(get_icon("AudioBusBypass", "EditorIcons"));
 
 			bus_options->set_icon(get_icon("GuiTabMenuHl", "EditorIcons"));
+
+			audio_value_preview_box->add_color_override("font_color", get_color("font_color", "TooltipLabel"));
+			audio_value_preview_box->add_style_override("panel", get_stylebox("panel", "TooltipPanel"));
 		} break;
 		case NOTIFICATION_MOUSE_EXIT:
 		case NOTIFICATION_DRAG_END: {
@@ -379,15 +382,24 @@ void EditorAudioBus::_show_value(float slider_value) {
 		db = _normalized_volume_to_scaled_db(slider_value);
 	}
 
-	String text = vformat("%10.1f dB", db);
+	String text;
+	if (Math::is_zero_approx(Math::stepify(db, 0.1))) {
+		// Prevent displaying `-0.0 dB` and show ` 0.0 dB` instead.
+		// The leading space makes the text visually line up with its positive/negative counterparts.
+		text = " 0.0 dB";
+	} else {
+		// Show an explicit `+` sign if positive.
+		text = vformat("%+.1f dB", db);
+	}
 
+	// Also set the preview text as a standard Control tooltip.
+	// This way, it can be seen when the slider is merely hovered (instead of dragged).
 	slider->set_tooltip(text);
 	audio_value_preview_label->set_text(text);
-	Vector2 slider_size = slider->get_size();
-	Vector2 slider_position = slider->get_global_position();
-	float left_padding = 5.0f;
-	float vert_padding = 10.0f;
-	Vector2 box_position = Vector2(slider_size.x + left_padding, (slider_size.y - vert_padding) * (1.0f - slider->get_value()) - vert_padding);
+	const Vector2 slider_size = slider->get_size();
+	const Vector2 slider_position = slider->get_global_position();
+	const float vert_padding = 10.0f;
+	const Vector2 box_position = Vector2(slider_size.x, (slider_size.y - vert_padding) * (1.0f - slider->get_value()) - vert_padding);
 	audio_value_preview_box->set_position(slider_position + box_position);
 	audio_value_preview_box->set_size(audio_value_preview_label->get_size());
 	if (slider->has_focus() && !audio_value_preview_box->is_visible()) {
@@ -818,7 +830,7 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) {
 	bus_options = memnew(MenuButton);
 	bus_options->set_h_size_flags(SIZE_SHRINK_END);
 	bus_options->set_anchor(MARGIN_RIGHT, 0.0);
-	bus_options->set_tooltip(TTR("Bus options"));
+	bus_options->set_tooltip(TTR("Bus Options"));
 	hbc->add_child(bus_options);
 
 	Ref<StyleBoxEmpty> sbempty = memnew(StyleBoxEmpty);
@@ -853,14 +865,13 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) {
 	audio_value_preview_label->set_v_size_flags(SIZE_EXPAND_FILL);
 	audio_value_preview_label->set_h_size_flags(SIZE_EXPAND_FILL);
 	audio_value_preview_label->set_mouse_filter(MOUSE_FILTER_PASS);
+	audio_value_preview_box->add_color_override("font_color", get_color("font_color", "TooltipLabel"));
 
 	audioprev_hbc->add_child(audio_value_preview_label);
 
 	slider->add_child(audio_value_preview_box);
 	audio_value_preview_box->set_as_toplevel(true);
-	Ref<StyleBoxFlat> panel_style = memnew(StyleBoxFlat);
-	panel_style->set_bg_color(Color(0.0f, 0.0f, 0.0f, 0.8f));
-	audio_value_preview_box->add_style_override("panel", panel_style);
+	audio_value_preview_box->add_style_override("panel", get_stylebox("panel", "TooltipPanel"));
 	audio_value_preview_box->set_mouse_filter(MOUSE_FILTER_PASS);
 	audio_value_preview_box->hide();
 
@@ -1427,7 +1438,7 @@ void EditorAudioMeterNotches::_bind_methods() {
 void EditorAudioMeterNotches::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_THEME_CHANGED: {
-			notch_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(0, 0, 0);
+			notch_color = get_color("font_color", "Editor");
 		} break;
 		case NOTIFICATION_DRAW: {
 			_draw_audio_notches();
@@ -1442,13 +1453,13 @@ void EditorAudioMeterNotches::_draw_audio_notches() {
 	for (int i = 0; i < notches.size(); i++) {
 		AudioNotch n = notches[i];
 		draw_line(Vector2(0, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding),
-				Vector2(line_length, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding),
+				Vector2(line_length * EDSCALE, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding),
 				notch_color,
-				1);
+				Math::round(EDSCALE));
 
 		if (n.render_db_value) {
 			draw_string(font,
-					Vector2(line_length + label_space,
+					Vector2((line_length + label_space) * EDSCALE,
 							(1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + (font_height / 4) + top_padding),
 					String::num(Math::abs(n.db_value)) + "dB",
 					notch_color);
@@ -1456,10 +1467,6 @@ void EditorAudioMeterNotches::_draw_audio_notches() {
 	}
 }
 
-EditorAudioMeterNotches::EditorAudioMeterNotches() :
-		line_length(5.0f),
-		label_space(2.0f),
-		btm_padding(9.0f),
-		top_padding(5.0f) {
-	notch_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(0, 0, 0);
+EditorAudioMeterNotches::EditorAudioMeterNotches() {
+	notch_color = get_color("font_color", "Editor");
 }

+ 4 - 4
editor/editor_audio_buses.h

@@ -244,10 +244,10 @@ private:
 	List<AudioNotch> notches;
 
 public:
-	float line_length;
-	float label_space;
-	float btm_padding;
-	float top_padding;
+	const float line_length = 5.0f;
+	const float label_space = 2.0f;
+	const float btm_padding = 9.0f;
+	const float top_padding = 5.0f;
 	Color notch_color;
 
 	void add_notch(float p_normalized_offset, float p_db_value, bool p_render_value = false);

+ 37 - 0
editor/editor_feature_profile.cpp

@@ -116,6 +116,18 @@ bool EditorFeatureProfile::has_class_properties_disabled(const StringName &p_cla
 	return disabled_properties.has(p_class);
 }
 
+void EditorFeatureProfile::set_item_collapsed(const StringName &p_class, bool p_collapsed) {
+	if (p_collapsed) {
+		collapsed_classes.insert(p_class);
+	} else {
+		collapsed_classes.erase(p_class);
+	}
+}
+
+bool EditorFeatureProfile::is_item_collapsed(const StringName &p_class) const {
+	return collapsed_classes.has(p_class);
+}
+
 void EditorFeatureProfile::set_disable_feature(Feature p_feature, bool p_disable) {
 	ERR_FAIL_INDEX(p_feature, FEATURE_MAX);
 	features_disabled[p_feature] = p_disable;
@@ -478,6 +490,9 @@ void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const S
 	class_item->set_selectable(0, true);
 	class_item->set_metadata(0, p_class);
 
+	bool collapsed = edited->is_item_collapsed(p_class);
+	class_item->set_collapsed(collapsed);
+
 	if (p_class == p_selected) {
 		class_item->select(0);
 	}
@@ -590,6 +605,26 @@ void EditorFeatureProfileManager::_class_list_item_edited() {
 	}
 }
 
+void EditorFeatureProfileManager::_class_list_item_collapsed(Object *p_item) {
+	if (updating_features) {
+		return;
+	}
+
+	TreeItem *item = Object::cast_to<TreeItem>(p_item);
+	if (!item) {
+		return;
+	}
+
+	Variant md = item->get_metadata(0);
+	if (md.get_type() != Variant::STRING) {
+		return;
+	}
+
+	String class_name = md;
+	bool collapsed = item->is_collapsed();
+	edited->set_item_collapsed(class_name, collapsed);
+}
+
 void EditorFeatureProfileManager::_property_item_edited() {
 	if (updating_features) {
 		return;
@@ -781,6 +816,7 @@ void EditorFeatureProfileManager::_bind_methods() {
 	ClassDB::bind_method("_export_profile", &EditorFeatureProfileManager::_export_profile);
 	ClassDB::bind_method("_class_list_item_selected", &EditorFeatureProfileManager::_class_list_item_selected);
 	ClassDB::bind_method("_class_list_item_edited", &EditorFeatureProfileManager::_class_list_item_edited);
+	ClassDB::bind_method("_class_list_item_collapsed", &EditorFeatureProfileManager::_class_list_item_collapsed);
 	ClassDB::bind_method("_property_item_edited", &EditorFeatureProfileManager::_property_item_edited);
 	ClassDB::bind_method("_emit_current_profile_changed", &EditorFeatureProfileManager::_emit_current_profile_changed);
 
@@ -852,6 +888,7 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() {
 	class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
 	class_list->connect("cell_selected", this, "_class_list_item_selected");
 	class_list->connect("item_edited", this, "_class_list_item_edited", varray(), CONNECT_DEFERRED);
+	class_list->connect("item_collapsed", this, "_class_list_item_collapsed");
 
 	VBoxContainer *property_list_vbc = memnew(VBoxContainer);
 	h_split->add_child(property_list_vbc);

+ 6 - 0
editor/editor_feature_profile.h

@@ -60,6 +60,8 @@ private:
 	Set<StringName> disabled_editors;
 	Map<StringName, Set<StringName>> disabled_properties;
 
+	Set<StringName> collapsed_classes;
+
 	bool features_disabled[FEATURE_MAX];
 	static const char *feature_names[FEATURE_MAX];
 	static const char *feature_identifiers[FEATURE_MAX];
@@ -80,6 +82,9 @@ public:
 	bool is_class_property_disabled(const StringName &p_class, const StringName &p_property) const;
 	bool has_class_properties_disabled(const StringName &p_class) const;
 
+	void set_item_collapsed(const StringName &p_class, bool p_collapsed);
+	bool is_item_collapsed(const StringName &p_class) const;
+
 	void set_disable_feature(Feature p_feature, bool p_disable);
 	bool is_feature_disabled(Feature p_feature) const;
 
@@ -148,6 +153,7 @@ class EditorFeatureProfileManager : public AcceptDialog {
 
 	void _class_list_item_selected();
 	void _class_list_item_edited();
+	void _class_list_item_collapsed(Object *p_item);
 	void _property_item_edited();
 	void _save_and_update();
 

+ 1 - 0
editor/plugin_config_dialog.cpp

@@ -209,6 +209,7 @@ PluginConfigDialog::PluginConfigDialog() {
 
 	desc_edit = memnew(TextEdit);
 	desc_edit->set_custom_minimum_size(Size2(400, 80) * EDSCALE);
+	desc_edit->set_wrap_enabled(true);
 	grid->add_child(desc_edit);
 
 	Label *author_lb = memnew(Label);

+ 2 - 0
editor/plugins/canvas_item_editor_plugin.cpp

@@ -5252,8 +5252,10 @@ void CanvasItemEditor::_bind_methods() {
 	ClassDB::bind_method("_popup_warning_depop", &CanvasItemEditor::_popup_warning_depop);
 	ClassDB::bind_method(D_METHOD("_selection_result_pressed"), &CanvasItemEditor::_selection_result_pressed);
 	ClassDB::bind_method(D_METHOD("_selection_menu_hide"), &CanvasItemEditor::_selection_menu_hide);
+	ClassDB::bind_method(D_METHOD("get_state"), &CanvasItemEditor::get_state);
 	ClassDB::bind_method(D_METHOD("set_state"), &CanvasItemEditor::set_state);
 	ClassDB::bind_method(D_METHOD("update_viewport"), &CanvasItemEditor::update_viewport);
+	ClassDB::bind_method(D_METHOD("_zoom_on_position"), &CanvasItemEditor::_zoom_on_position);
 
 	ADD_SIGNAL(MethodInfo("item_lock_status_changed"));
 	ADD_SIGNAL(MethodInfo("item_group_status_changed"));

+ 9 - 2
editor/plugins/script_text_editor.cpp

@@ -31,6 +31,7 @@
 #include "script_text_editor.h"
 
 #include "core/math/expression.h"
+#include "core/os/input.h"
 #include "core/os/keyboard.h"
 #include "editor/editor_node.h"
 #include "editor/editor_scale.h"
@@ -1506,11 +1507,17 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
 		Array files = d["files"];
 
 		String text_to_drop;
+		bool preload = Input::get_singleton()->is_key_pressed(KEY_CONTROL);
 		for (int i = 0; i < files.size(); i++) {
 			if (i > 0) {
-				text_to_drop += ",";
+				text_to_drop += ", ";
+			}
+
+			if (preload) {
+				text_to_drop += "preload(\"" + String(files[i]).c_escape() + "\")";
+			} else {
+				text_to_drop += "\"" + String(files[i]).c_escape() + "\"";
 			}
-			text_to_drop += "\"" + String(files[i]).c_escape() + "\"";
 		}
 
 		te->cursor_set_line(row);

+ 4 - 4
misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj

@@ -247,7 +247,7 @@
 				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
 				OTHER_LDFLAGS = "$linker_flags";
 				SDKROOT = iphoneos;
-				TARGETED_DEVICE_FAMILY = "1,2";
+				TARGETED_DEVICE_FAMILY = "$targeted_device_family";
 			};
 			name = Debug;
 		};
@@ -286,7 +286,7 @@
 				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
 				OTHER_LDFLAGS = "$linker_flags";
 				SDKROOT = iphoneos;
-				TARGETED_DEVICE_FAMILY = "1,2";
+				TARGETED_DEVICE_FAMILY = "$targeted_device_family";
 				VALIDATE_PRODUCT = YES;
 			};
 			name = Release;
@@ -315,7 +315,7 @@
 				PRODUCT_BUNDLE_IDENTIFIER = $identifier;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE = "$provisioning_profile_uuid_debug";
-				TARGETED_DEVICE_FAMILY = "1,2";
+				TARGETED_DEVICE_FAMILY = "$targeted_device_family";
 				VALID_ARCHS = "armv7 armv7s arm64 i386 x86_64";
 				WRAPPER_EXTENSION = app;
 			};
@@ -345,7 +345,7 @@
 				PRODUCT_BUNDLE_IDENTIFIER = $identifier;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE = "$provisioning_profile_uuid_release";
-				TARGETED_DEVICE_FAMILY = "1,2";
+				TARGETED_DEVICE_FAMILY = "$targeted_device_family";
 				VALID_ARCHS = "armv7 armv7s arm64 i386 x86_64";
 				WRAPPER_EXTENSION = app;
 			};

+ 5 - 5
misc/dist/ios_xcode/godot_ios.xcodeproj/xcshareddata/xcschemes/godot_ios.xcscheme

@@ -23,7 +23,7 @@
       </BuildActionEntries>
    </BuildAction>
    <TestAction
-      buildConfiguration = "Debug"
+      buildConfiguration = "$default_build_config"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
@@ -42,7 +42,7 @@
       </AdditionalOptions>
    </TestAction>
    <LaunchAction
-      buildConfiguration = "Debug"
+      buildConfiguration = "$default_build_config"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       launchStyle = "0"
@@ -67,7 +67,7 @@
       </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
-      buildConfiguration = "Debug"
+      buildConfiguration = "$default_build_config"
       shouldUseLaunchSchemeArgsEnv = "YES"
       savedToolIdentifier = ""
       useCustomWorkingDirectory = "NO"
@@ -84,10 +84,10 @@
       </BuildableProductRunnable>
    </ProfileAction>
    <AnalyzeAction
-      buildConfiguration = "Debug">
+      buildConfiguration = "$default_build_config">
    </AnalyzeAction>
    <ArchiveAction
-      buildConfiguration = "Debug"
+      buildConfiguration = "$default_build_config"
       revealArchiveInOrganizer = "YES">
    </ArchiveAction>
 </Scheme>

+ 2 - 0
modules/gdnative/pluginscript/pluginscript_script.cpp

@@ -203,6 +203,8 @@ ScriptInstance *PluginScript::instance_create(Object *p_this) {
 }
 
 bool PluginScript::instance_has(const Object *p_this) const {
+	ERR_FAIL_COND_V(!_language, false);
+
 	_language->lock();
 	bool hasit = _instances.has((Object *)p_this);
 	_language->unlock();

+ 3 - 0
modules/opensimplex/doc_classes/NoiseTexture.xml

@@ -31,6 +31,9 @@
 		<member name="noise" type="OpenSimplexNoise" setter="set_noise" getter="get_noise">
 			The [OpenSimplexNoise] instance used to generate the noise.
 		</member>
+		<member name="noise_offset" type="Vector2" setter="set_noise_offset" getter="get_noise_offset" default="Vector2( 0, 0 )">
+			An offset used to specify the noise space coordinate of the top left corner of the generated noise. This value is ignored if [member seamless] is enabled.
+		</member>
 		<member name="seamless" type="bool" setter="set_seamless" getter="get_seamless" default="false">
 			Whether the texture can be tiled without visible seams or not. Seamless textures take longer to generate.
 			[b]Note:[/b] Seamless noise has a lower contrast compared to non-seamless noise. This is due to the way noise uses higher dimensions for generating seamless noise.

+ 3 - 1
modules/opensimplex/doc_classes/OpenSimplexNoise.xml

@@ -31,8 +31,10 @@
 			</argument>
 			<argument index="1" name="height" type="int">
 			</argument>
+			<argument index="2" name="noise_offset" type="Vector2" default="Vector2( 0, 0 )">
+			</argument>
 			<description>
-				Generate a noise image in [constant Image.FORMAT_L8] format with the requested [code]width[/code] and [code]height[/code], based on the current noise parameters.
+				Generate a noise image in [constant Image.FORMAT_L8] format with the requested [code]width[/code] and [code]height[/code], based on the current noise parameters. If [code]noise_offset[/code] is specified, then the offset value is used as the coordinates of the top-left corner of the generated noise.
 			</description>
 		</method>
 		<method name="get_noise_1d" qualifiers="const">

+ 17 - 1
modules/opensimplex/noise_texture.cpp

@@ -62,6 +62,9 @@ void NoiseTexture::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_noise", "noise"), &NoiseTexture::set_noise);
 	ClassDB::bind_method(D_METHOD("get_noise"), &NoiseTexture::get_noise);
 
+	ClassDB::bind_method(D_METHOD("set_noise_offset", "noise_offset"), &NoiseTexture::set_noise_offset);
+	ClassDB::bind_method(D_METHOD("get_noise_offset"), &NoiseTexture::get_noise_offset);
+
 	ClassDB::bind_method(D_METHOD("set_seamless", "seamless"), &NoiseTexture::set_seamless);
 	ClassDB::bind_method(D_METHOD("get_seamless"), &NoiseTexture::get_seamless);
 
@@ -82,6 +85,7 @@ void NoiseTexture::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "as_normalmap"), "set_as_normalmap", "is_normalmap");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "bump_strength", PROPERTY_HINT_RANGE, "0,32,0.1,or_greater"), "set_bump_strength", "get_bump_strength");
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "OpenSimplexNoise"), "set_noise", "get_noise");
+	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "noise_offset"), "set_noise_offset", "get_noise_offset");
 }
 
 void NoiseTexture::_validate_property(PropertyInfo &property) const {
@@ -137,7 +141,7 @@ Ref<Image> NoiseTexture::_generate_texture() {
 	if (seamless) {
 		image = ref_noise->get_seamless_image(size.x);
 	} else {
-		image = ref_noise->get_image(size.x, size.y);
+		image = ref_noise->get_image(size.x, size.y, noise_offset);
 	}
 
 	if (as_normalmap) {
@@ -205,6 +209,14 @@ void NoiseTexture::set_height(int p_height) {
 	_queue_update();
 }
 
+void NoiseTexture::set_noise_offset(Vector2 p_noise_offset) {
+	if (noise_offset == p_noise_offset) {
+		return;
+	}
+	noise_offset = p_noise_offset;
+	_queue_update();
+}
+
 void NoiseTexture::set_seamless(bool p_seamless) {
 	if (p_seamless == seamless) {
 		return;
@@ -252,6 +264,10 @@ int NoiseTexture::get_height() const {
 	return size.y;
 }
 
+Vector2 NoiseTexture::get_noise_offset() const {
+	return noise_offset;
+}
+
 void NoiseTexture::set_flags(uint32_t p_flags) {
 	flags = p_flags;
 	VS::get_singleton()->texture_set_flags(texture, flags);

+ 4 - 0
modules/opensimplex/noise_texture.h

@@ -56,6 +56,7 @@ private:
 
 	Ref<OpenSimplexNoise> noise;
 	Vector2i size;
+	Vector2 noise_offset;
 	bool seamless;
 	bool as_normalmap;
 	float bump_strength;
@@ -79,6 +80,9 @@ public:
 	void set_width(int p_width);
 	void set_height(int p_height);
 
+	void set_noise_offset(Vector2 p_noise_offset);
+	Vector2 get_noise_offset() const;
+
 	void set_seamless(bool p_seamless);
 	bool get_seamless();
 

+ 3 - 3
modules/opensimplex/open_simplex_noise.cpp

@@ -102,7 +102,7 @@ void OpenSimplexNoise::set_lacunarity(float p_lacunarity) {
 	emit_changed();
 }
 
-Ref<Image> OpenSimplexNoise::get_image(int p_width, int p_height) const {
+Ref<Image> OpenSimplexNoise::get_image(int p_width, int p_height, const Vector2 &p_noise_offset) const {
 	PoolVector<uint8_t> data;
 	data.resize(p_width * p_height);
 
@@ -110,7 +110,7 @@ Ref<Image> OpenSimplexNoise::get_image(int p_width, int p_height) const {
 
 	for (int i = 0; i < p_height; i++) {
 		for (int j = 0; j < p_width; j++) {
-			float v = get_noise_2d(i, j);
+			float v = get_noise_2d(float(j) + p_noise_offset.x, float(i) + p_noise_offset.y);
 			v = v * 0.5 + 0.5; // Normalize [0..1]
 			wd8[(i * p_width + j)] = uint8_t(CLAMP(v * 255.0, 0, 255));
 		}
@@ -167,7 +167,7 @@ void OpenSimplexNoise::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_lacunarity", "lacunarity"), &OpenSimplexNoise::set_lacunarity);
 	ClassDB::bind_method(D_METHOD("get_lacunarity"), &OpenSimplexNoise::get_lacunarity);
 
-	ClassDB::bind_method(D_METHOD("get_image", "width", "height"), &OpenSimplexNoise::get_image);
+	ClassDB::bind_method(D_METHOD("get_image", "width", "height", "noise_offset"), &OpenSimplexNoise::get_image, DEFVAL(Vector2()));
 	ClassDB::bind_method(D_METHOD("get_seamless_image", "size"), &OpenSimplexNoise::get_seamless_image);
 
 	ClassDB::bind_method(D_METHOD("get_noise_1d", "x"), &OpenSimplexNoise::get_noise_1d);

+ 1 - 1
modules/opensimplex/open_simplex_noise.h

@@ -75,7 +75,7 @@ public:
 	void set_lacunarity(float p_lacunarity);
 	float get_lacunarity() const { return lacunarity; }
 
-	Ref<Image> get_image(int p_width, int p_height) const;
+	Ref<Image> get_image(int p_width, int p_height, const Vector2 &p_noise_offset = Vector2()) const;
 	Ref<Image> get_seamless_image(int p_size) const;
 
 	float get_noise_1d(float x) const;

+ 1 - 0
modules/visual_script/visual_script_builtin_funcs.cpp

@@ -134,6 +134,7 @@ bool VisualScriptBuiltinFunc::has_input_sequence_port() const {
 		case TEXT_PRINT:
 		case TEXT_PRINTERR:
 		case TEXT_PRINTRAW:
+		case MATH_SEED:
 			return true;
 		default:
 			return false;

+ 18 - 0
platform/iphone/export/export.cpp

@@ -352,6 +352,8 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), ""));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0));
 
+	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/targeted_device_family", PROPERTY_HINT_ENUM, "iPhone,iPad,iPhone & iPad"), 2));
+
 	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"));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
@@ -444,6 +446,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
 			strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n";
 		} else if (lines[i].find("$team_id") != -1) {
 			strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n";
+		} else if (lines[i].find("$default_build_config") != -1) {
+			strnew += lines[i].replace("$default_build_config", p_debug ? "Debug" : "Release") + "\n";
 		} else if (lines[i].find("$export_method") != -1) {
 			int export_method = p_preset->get(p_debug ? "application/export_method_debug" : "application/export_method_release");
 			strnew += lines[i].replace("$export_method", export_method_string[export_method]) + "\n";
@@ -464,6 +468,20 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
 			strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n";
 		} else if (lines[i].find("$linker_flags") != -1) {
 			strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n";
+		} else if (lines[i].find("$targeted_device_family") != -1) {
+			String xcode_value;
+			switch ((int)p_preset->get("application/targeted_device_family")) {
+				case 0: // iPhone
+					xcode_value = "1";
+					break;
+				case 1: // iPad
+					xcode_value = "2";
+					break;
+				case 2: // iPhone & iPad
+					xcode_value = "1,2";
+					break;
+			}
+			strnew += lines[i].replace("$targeted_device_family", xcode_value) + "\n";
 		} else if (lines[i].find("$cpp_code") != -1) {
 			strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n";
 		} else if (lines[i].find("$docs_in_place") != -1) {

+ 3 - 6
scene/2d/camera_2d.cpp

@@ -272,11 +272,10 @@ void Camera2D::_notification(int p_what) {
 			}
 
 			if (screen_drawing_enabled) {
-				Color area_axis_color(0.5, 0.42, 0.87, 0.63);
+				Color area_axis_color(1, 0.4, 1, 0.63);
 				float area_axis_width = 1;
 				if (is_current()) {
 					area_axis_width = 3;
-					area_axis_color.a = 0.83;
 				}
 
 				Transform2D inv_camera_transform = get_camera_transform().affine_inverse();
@@ -297,10 +296,9 @@ void Camera2D::_notification(int p_what) {
 			}
 
 			if (limit_drawing_enabled) {
-				Color limit_drawing_color(1, 1, 0, 0.63);
+				Color limit_drawing_color(1, 1, 0.25, 0.63);
 				float limit_drawing_width = 1;
 				if (is_current()) {
-					limit_drawing_color.a = 0.83;
 					limit_drawing_width = 3;
 				}
 
@@ -319,11 +317,10 @@ void Camera2D::_notification(int p_what) {
 			}
 
 			if (margin_drawing_enabled) {
-				Color margin_drawing_color(0, 1, 1, 0.63);
+				Color margin_drawing_color(0.25, 1, 1, 0.63);
 				float margin_drawing_width = 1;
 				if (is_current()) {
 					margin_drawing_width = 3;
-					margin_drawing_color.a = 0.83;
 				}
 
 				Transform2D inv_camera_transform = get_camera_transform().affine_inverse();

+ 1 - 1
scene/3d/skeleton.cpp

@@ -666,7 +666,7 @@ void Skeleton::_rebuild_physical_bones_cache() {
 	const int b_size = bones.size();
 	for (int i = 0; i < b_size; ++i) {
 		PhysicalBone *parent_pb = _get_physical_bone_parent(i);
-		if (parent_pb != bones[i].physical_bone) {
+		if (parent_pb != bones[i].cache_parent_physical_bone) {
 			bones.write[i].cache_parent_physical_bone = parent_pb;
 			if (bones[i].physical_bone) {
 				bones[i].physical_bone->_on_bone_parent_changed();

+ 114 - 18
scene/gui/graph_node.cpp

@@ -32,6 +32,12 @@
 
 #include "core/method_bind_ext.gen.inc"
 
+struct _MinSizeCache {
+	int min_size;
+	bool will_stretch;
+	int final_size;
+};
+
 bool GraphNode::_set(const StringName &p_name, const Variant &p_value) {
 	if (!p_name.operator String().begins_with("slot/")) {
 		return false;
@@ -119,15 +125,23 @@ void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const {
 }
 
 void GraphNode::_resort() {
-	int sep = get_constant("separation");
+	/** First pass, determine minimum size AND amount of stretchable elements */
+
+	Size2 new_size = get_size();
 	Ref<StyleBox> sb = get_stylebox("frame");
-	bool first = true;
 
-	Size2 minsize;
+	int sep = get_constant("separation");
+
+	bool first = true;
+	int children_count = 0;
+	int stretch_min = 0;
+	int stretch_avail = 0;
+	float stretch_ratio_total = 0;
+	Map<Control *, _MinSizeCache> min_size_cache;
 
 	for (int i = 0; i < get_child_count(); i++) {
 		Control *c = Object::cast_to<Control>(get_child(i));
-		if (!c) {
+		if (!c || !c->is_visible_in_tree()) {
 			continue;
 		}
 		if (c->is_set_as_toplevel()) {
@@ -135,38 +149,120 @@ void GraphNode::_resort() {
 		}
 
 		Size2i size = c->get_combined_minimum_size();
+		_MinSizeCache msc;
 
-		minsize.y += size.y;
-		minsize.x = MAX(minsize.x, size.x);
+		stretch_min += size.height;
+		msc.min_size = size.height;
+		msc.will_stretch = c->get_v_size_flags() & SIZE_EXPAND;
 
-		if (first) {
-			first = false;
-		} else {
-			minsize.y += sep;
+		if (msc.will_stretch) {
+			stretch_avail += msc.min_size;
+			stretch_ratio_total += c->get_stretch_ratio();
 		}
+		msc.final_size = msc.min_size;
+		min_size_cache[c] = msc;
+		children_count++;
 	}
 
-	int vofs = 0;
-	int w = get_size().x - sb->get_minimum_size().x;
+	if (children_count == 0) {
+		return;
+	}
+
+	int stretch_max = new_size.height - (children_count - 1) * sep;
+	int stretch_diff = stretch_max - stretch_min;
+	if (stretch_diff < 0) {
+		//avoid negative stretch space
+		stretch_diff = 0;
+	}
+
+	stretch_avail += stretch_diff - sb->get_margin(MARGIN_BOTTOM) - sb->get_margin(MARGIN_TOP); //available stretch space.
+	/** Second, pass sucessively to discard elements that can't be stretched, this will run while stretchable
+		elements exist */
+
+	while (stretch_ratio_total > 0) { // first of all, don't even be here if no stretchable objects exist
+		bool refit_successful = true; //assume refit-test will go well
+
+		for (int i = 0; i < get_child_count(); i++) {
+			Control *c = Object::cast_to<Control>(get_child(i));
+			if (!c || !c->is_visible_in_tree()) {
+				continue;
+			}
+			if (c->is_set_as_toplevel()) {
+				continue;
+			}
 
+			ERR_FAIL_COND(!min_size_cache.has(c));
+			_MinSizeCache &msc = min_size_cache[c];
+
+			if (msc.will_stretch) { //wants to stretch
+				//let's see if it can really stretch
+
+				int final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total;
+				if (final_pixel_size < msc.min_size) {
+					//if available stretching area is too small for widget,
+					//then remove it from stretching area
+					msc.will_stretch = false;
+					stretch_ratio_total -= c->get_stretch_ratio();
+					refit_successful = false;
+					stretch_avail -= msc.min_size;
+					msc.final_size = msc.min_size;
+					break;
+				} else {
+					msc.final_size = final_pixel_size;
+				}
+			}
+		}
+
+		if (refit_successful) { //uf refit went well, break
+			break;
+		}
+	}
+
+	/** Final pass, draw and stretch elements **/
+
+	int ofs = sb->get_margin(MARGIN_TOP);
+
+	first = true;
+	int idx = 0;
 	cache_y.clear();
+	int w = new_size.width - sb->get_minimum_size().x;
+
 	for (int i = 0; i < get_child_count(); i++) {
 		Control *c = Object::cast_to<Control>(get_child(i));
-		if (!c) {
+		if (!c || !c->is_visible_in_tree()) {
 			continue;
 		}
 		if (c->is_set_as_toplevel()) {
 			continue;
 		}
 
-		Size2i size = c->get_combined_minimum_size();
+		_MinSizeCache &msc = min_size_cache[c];
+
+		if (first) {
+			first = false;
+		} else {
+			ofs += sep;
+		}
 
-		Rect2 r(sb->get_margin(MARGIN_LEFT), sb->get_margin(MARGIN_TOP) + vofs, w, size.y);
+		int from = ofs;
+		int to = ofs + msc.final_size;
 
-		fit_child_in_rect(c, r);
-		cache_y.push_back(vofs + size.y * 0.5);
+		if (msc.will_stretch && idx == children_count - 1) {
+			//adjust so the last one always fits perfect
+			//compensating for numerical imprecision
 
-		vofs += size.y + sep;
+			to = new_size.height - sb->get_margin(MARGIN_BOTTOM);
+		}
+
+		int size = to - from;
+
+		Rect2 rect(sb->get_margin(MARGIN_LEFT), from, w, size);
+
+		fit_child_in_rect(c, rect);
+		cache_y.push_back(from - sb->get_margin(MARGIN_TOP) + size * 0.5);
+
+		ofs = to;
+		idx++;
 	}
 
 	update();

+ 37 - 5
scene/gui/texture_button.cpp

@@ -166,9 +166,11 @@ void TextureButton::_notification(int p_what) {
 				} break;
 			}
 
+			Point2 ofs;
+			Size2 size;
+
 			if (texdraw.is_valid()) {
-				Point2 ofs;
-				Size2 size = texdraw->get_size();
+				size = texdraw->get_size();
 				_texture_region = Rect2(Point2(), texdraw->get_size());
 				_tile = false;
 				if (expand) {
@@ -218,17 +220,21 @@ void TextureButton::_notification(int p_what) {
 				}
 
 				_position_rect = Rect2(ofs, size);
+
+				size.width *= hflip ? -1.0f : 1.0f;
+				size.height *= vflip ? -1.0f : 1.0f;
+
 				if (_tile) {
-					draw_texture_rect(texdraw, _position_rect, _tile);
+					draw_texture_rect(texdraw, Rect2(ofs, size), _tile);
 				} else {
-					draw_texture_rect_region(texdraw, _position_rect, _texture_region);
+					draw_texture_rect_region(texdraw, Rect2(ofs, size), _texture_region);
 				}
 			} else {
 				_position_rect = Rect2();
 			}
 
 			if (has_focus() && focused.is_valid()) {
-				draw_texture_rect(focused, _position_rect, false);
+				draw_texture_rect(focused, Rect2(ofs, size), false);
 			};
 		} break;
 	}
@@ -243,6 +249,10 @@ void TextureButton::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_click_mask", "mask"), &TextureButton::set_click_mask);
 	ClassDB::bind_method(D_METHOD("set_expand", "p_expand"), &TextureButton::set_expand);
 	ClassDB::bind_method(D_METHOD("set_stretch_mode", "p_mode"), &TextureButton::set_stretch_mode);
+	ClassDB::bind_method(D_METHOD("set_flip_h", "enable"), &TextureButton::set_flip_h);
+	ClassDB::bind_method(D_METHOD("is_flipped_h"), &TextureButton::is_flipped_h);
+	ClassDB::bind_method(D_METHOD("set_flip_v", "enable"), &TextureButton::set_flip_v);
+	ClassDB::bind_method(D_METHOD("is_flipped_v"), &TextureButton::is_flipped_v);
 
 	ClassDB::bind_method(D_METHOD("get_normal_texture"), &TextureButton::get_normal_texture);
 	ClassDB::bind_method(D_METHOD("get_pressed_texture"), &TextureButton::get_pressed_texture);
@@ -262,6 +272,8 @@ void TextureButton::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_click_mask", PROPERTY_HINT_RESOURCE_TYPE, "BitMap"), "set_click_mask", "get_click_mask");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_expand", "get_expand");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Scale,Tile,Keep,Keep Centered,Keep Aspect,Keep Aspect Centered,Keep Aspect Covered"), "set_stretch_mode", "get_stretch_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_flip_h", "is_flipped_h");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_flip_v", "is_flipped_v");
 
 	BIND_ENUM_CONSTANT(STRETCH_SCALE);
 	BIND_ENUM_CONSTANT(STRETCH_TILE);
@@ -338,9 +350,29 @@ TextureButton::StretchMode TextureButton::get_stretch_mode() const {
 	return stretch_mode;
 }
 
+void TextureButton::set_flip_h(bool p_flip) {
+	hflip = p_flip;
+	update();
+}
+
+bool TextureButton::is_flipped_h() const {
+	return hflip;
+}
+
+void TextureButton::set_flip_v(bool p_flip) {
+	vflip = p_flip;
+	update();
+}
+
+bool TextureButton::is_flipped_v() const {
+	return vflip;
+}
+
 TextureButton::TextureButton() {
 	expand = false;
 	stretch_mode = STRETCH_SCALE;
+	hflip = false;
+	vflip = false;
 
 	_texture_region = Rect2();
 	_position_rect = Rect2();

+ 9 - 0
scene/gui/texture_button.h

@@ -61,6 +61,9 @@ private:
 	Rect2 _position_rect;
 	bool _tile;
 
+	bool hflip;
+	bool vflip;
+
 protected:
 	virtual Size2 get_minimum_size() const;
 	virtual bool has_point(const Point2 &p_point) const;
@@ -88,6 +91,12 @@ public:
 	void set_stretch_mode(StretchMode p_stretch_mode);
 	StretchMode get_stretch_mode() const;
 
+	void set_flip_h(bool p_flip);
+	bool is_flipped_h() const;
+
+	void set_flip_v(bool p_flip);
+	bool is_flipped_v() const;
+
 	TextureButton();
 };
 

+ 11 - 9
scene/main/http_request.cpp

@@ -322,17 +322,19 @@ bool HTTPRequest::_update_connection() {
 			}
 
 			PoolByteArray chunk = client->read_response_body_chunk();
-			downloaded.add(chunk.size());
 
-			if (file) {
-				PoolByteArray::Read r = chunk.read();
-				file->store_buffer(r.ptr(), chunk.size());
-				if (file->get_error() != OK) {
-					call_deferred("_request_done", RESULT_DOWNLOAD_FILE_WRITE_ERROR, response_code, response_headers, PoolByteArray());
-					return true;
+			if (chunk.size()) {
+				downloaded.add(chunk.size());
+				if (file) {
+					PoolByteArray::Read r = chunk.read();
+					file->store_buffer(r.ptr(), chunk.size());
+					if (file->get_error() != OK) {
+						call_deferred("_request_done", RESULT_DOWNLOAD_FILE_WRITE_ERROR, response_code, response_headers, PoolByteArray());
+						return true;
+					}
+				} else {
+					body.append_array(chunk);
 				}
-			} else {
-				body.append_array(chunk);
 			}
 
 			if (body_size_limit >= 0 && downloaded.get() > body_size_limit) {

+ 14 - 9
servers/physics/body_sw.cpp

@@ -65,16 +65,18 @@ void BodySW::update_inertias() {
 			// We have to recompute the center of mass.
 			center_of_mass_local.zero();
 
-			for (int i = 0; i < get_shape_count(); i++) {
-				real_t area = get_shape_area(i);
+			if (total_area != 0.0) {
+				for (int i = 0; i < get_shape_count(); i++) {
+					real_t area = get_shape_area(i);
 
-				real_t mass = area * this->mass / total_area;
+					real_t mass = area * this->mass / total_area;
 
-				// NOTE: we assume that the shape origin is also its center of mass.
-				center_of_mass_local += mass * get_shape_transform(i).origin;
-			}
+					// NOTE: we assume that the shape origin is also its center of mass.
+					center_of_mass_local += mass * get_shape_transform(i).origin;
+				}
 
-			center_of_mass_local /= mass;
+				center_of_mass_local /= mass;
+			}
 
 			// Recompute the inertia tensor.
 			Basis inertia_tensor;
@@ -86,12 +88,15 @@ void BodySW::update_inertias() {
 					continue;
 				}
 
+				real_t area = get_shape_area(i);
+				if (area == 0.0) {
+					continue;
+				}
+
 				inertia_set = true;
 
 				const ShapeSW *shape = get_shape(i);
 
-				real_t area = get_shape_area(i);
-
 				real_t mass = area * this->mass / total_area;
 
 				Basis shape_inertia_tensor = shape->get_moment_of_inertia(mass).to_diagonal_matrix();