Merge pull request #69589 from timothyqiu/3.5-cherrypicks

Cherry-picks for the 3.5 branch (future 3.5.2) - 1st batch
This commit is contained in:
Rémi Verschelde 2022-12-05 07:44:41 +01:00 committed by GitHub
commit c76479727e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 787 additions and 556 deletions

View File

@ -3098,6 +3098,7 @@ Ref<Image> Image::rgbe_to_srgb() {
void Image::bumpmap_to_normalmap(float bump_scale) {
ERR_FAIL_COND(!_can_modify(format));
ERR_FAIL_COND_MSG(write_lock.ptr(), "Cannot modify image when it is locked.");
clear_mipmaps();
convert(Image::FORMAT_RF);
PoolVector<uint8_t> result_image; //rgba output

View File

@ -86,6 +86,8 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment) {
};
Error PCKPacker::add_file(const String &p_file, const String &p_src) {
ERR_FAIL_COND_V_MSG(!file, ERR_INVALID_PARAMETER, "File must be opened before use.");
FileAccess *f = FileAccess::open(p_src, FileAccess::READ);
if (!f) {
return ERR_FILE_CANT_OPEN;
@ -165,6 +167,9 @@ Error PCKPacker::flush(bool p_verbose) {
}
file->close();
memdelete(file);
file = nullptr;
memdelete_arr(buf);
return OK;

View File

@ -301,7 +301,7 @@ Error DirAccess::copy(String p_from, String p_to, int p_chmod_flags) {
const size_t copy_buffer_limit = 65536; // 64 KB
fsrc->seek_end(0);
int size = fsrc->get_position();
uint64_t size = fsrc->get_position();
fsrc->seek(0);
err = OK;
size_t buffer_size = MIN(size * sizeof(uint8_t), copy_buffer_limit);

View File

@ -1049,6 +1049,7 @@ ProjectSettings::ProjectSettings() {
// Initialization of engine variables should be done in the setup() method,
// so that the values can be overridden from project.godot or project.binary.
CRASH_COND_MSG(singleton != nullptr, "Instantiating a new ProjectSettings singleton is not supported.");
singleton = this;
last_order = NO_BUILTIN_ORDER_BASE;
last_builtin_order = 0;

View File

@ -3426,33 +3426,63 @@ bool String::is_valid_identifier() const {
return true;
}
//kind of poor should be rewritten properly
String String::word_wrap(int p_chars_per_line) const {
int from = 0;
int last_space = 0;
String ret;
int line_start = 0;
int line_end = 0; // End of last word on current line.
int word_start = 0; // -1 if no word encountered. Leading spaces are part of a word.
int word_length = 0;
for (int i = 0; i < length(); i++) {
if (i - from >= p_chars_per_line) {
if (last_space == -1) {
ret += substr(from, i - from + 1) + "\n";
} else {
ret += substr(from, last_space - from) + "\n";
i = last_space; //rewind
}
from = i + 1;
last_space = -1;
} else if (operator[](i) == ' ' || operator[](i) == '\t') {
last_space = i;
} else if (operator[](i) == '\n') {
ret += substr(from, i - from) + "\n";
from = i + 1;
last_space = -1;
const CharType c = operator[](i);
switch (c) {
case '\n': {
// Force newline.
ret += substr(line_start, i - line_start + 1);
line_start = i + 1;
line_end = line_start;
word_start = line_start;
word_length = 0;
} break;
case ' ':
case '\t': {
// A whitespace ends current word.
if (word_length > 0) {
line_end = i - 1;
word_start = -1;
word_length = 0;
}
} break;
default: {
if (word_start == -1) {
word_start = i;
}
word_length += 1;
if (word_length > p_chars_per_line) {
// Word too long: wrap before current character.
ret += substr(line_start, i - line_start) + "\n";
line_start = i;
line_end = i;
word_start = i;
word_length = 1;
} else if (i - line_start + 1 > p_chars_per_line) {
// Line too long: wrap after the last word.
ret += substr(line_start, line_end - line_start + 1) + "\n";
line_start = word_start;
line_end = line_start;
}
} break;
}
}
if (from < length()) {
ret += substr(from, length());
const int remaining = length() - line_start;
if (remaining) {
ret += substr(line_start, remaining);
}
return ret;
@ -4339,15 +4369,18 @@ String String::sprintf(const Array &values, bool *error) const {
double value = values[value_index];
bool is_negative = (value < 0);
String str = String::num(ABS(value), min_decimals);
bool not_numeric = isinf(value) || isnan(value);
// Pad decimals out.
str = str.pad_decimals(min_decimals);
if (!not_numeric) {
str = str.pad_decimals(min_decimals);
}
int initial_len = str.length();
// Padding. Leave room for sign later if required.
int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars;
String pad_char = pad_with_zeros ? String("0") : String(" ");
String pad_char = (pad_with_zeros && !not_numeric) ? String("0") : String(" "); // Never pad NaN or inf with zeros
if (left_justified) {
str = str.rpad(pad_chars_count, pad_char);
} else {

View File

@ -186,6 +186,7 @@
array.resize(10)
array.fill(0) # Initialize the 10 elements to 0.
[/codeblock]
[b]Note:[/b] If [code]value[/code] is of a reference type ([Object]-derived, [Array], [Dictionary], etc.) then the array is filled with the references to the same object, i.e. no duplicates are created.
</description>
</method>
<method name="find">

View File

@ -27,7 +27,7 @@
<method name="get_frames_available" qualifiers="const">
<return type="int" />
<description>
Returns the number of audio data frames left to play. If this returned number reaches [code]0[/code], the audio will stop playing until frames are added again. Therefore, make sure your script can always generate and push new audio frames fast enough to avoid audio cracking.
Returns the number of frames that can be pushed to the audio sample data buffer without overflowing it. If the result is [code]0[/code], the buffer is full.
</description>
</method>
<method name="get_skips" qualifiers="const">

View File

@ -5,6 +5,7 @@
</brief_description>
<description>
Cylinder shape for collisions.
[b]Note:[/b] When using GodotPhysics instead of the default Bullet physics engine, there are several known bugs with cylinder collision shapes. Using [CapsuleShape] or [BoxShape] instead is recommended.
</description>
<tutorials>
<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>

View File

@ -51,6 +51,7 @@
</member>
<member name="light_size" type="float" setter="set_param" getter="get_param" default="0.0">
The size of the light in Godot units. Only considered in baked lightmaps and only if [member light_bake_mode] is set to [constant BAKE_ALL]. Increasing this value will make the shadows appear blurrier. This can be used to simulate area lights to an extent.
[b]Note:[/b] [member light_size] is not affected by [member Spatial.scale] (the light's scale or its parent's scale).
</member>
<member name="light_specular" type="float" setter="set_param" getter="get_param" default="0.5">
The intensity of the specular blob in objects affected by the light. At [code]0[/code], the light becomes a pure diffuse light. When not baking emission, this can be used to avoid unrealistic reflections when placing lights above an emissive surface.

View File

@ -6,6 +6,7 @@
<description>
3D agent that is used in navigation to reach a location while avoiding static and dynamic obstacles. The dynamic obstacles are avoided using RVO (Reciprocal Velocity Obstacles) collision avoidance. The agent needs navigation data to work correctly. By default this node will register to the default [World] navigation map. If this node is a child of a [Navigation] node it will register to the navigation map of the navigation node or the function [method set_navigation] can be used to set the navigation node directly. [NavigationAgent] is physics safe.
[b]Note:[/b] After [method set_target_location] is used it is required to use the [method get_next_location] function once every physics frame to update the internal path logic of the NavigationAgent. The returned vector position from this function should be used as the next movement position for the agent's parent Node.
[b]Note:[/b] By default, the expensive calculations for avoidance are done in a thread. In HTML5 exports without thread support, they will be done on the main thread, which can lead to performance issues.
</description>
<tutorials>
</tutorials>

View File

@ -6,6 +6,7 @@
<description>
2D agent that is used in navigation to reach a location while avoiding static and dynamic obstacles. The dynamic obstacles are avoided using RVO (Reciprocal Velocity Obstacles) collision avoidance. The agent needs navigation data to work correctly. By default this node will register to the default [World2D] navigation map. If this node is a child of a [Navigation2D] node it will register to the navigation map of the navigation node or the function [method set_navigation] can be used to set the navigation node directly. [NavigationAgent2D] is physics safe.
[b]Note:[/b] After [method set_target_location] is used it is required to use the [method get_next_location] function once every physics frame to update the internal path logic of the NavigationAgent. The returned vector position from this function should be used as the next movement position for the agent's parent Node.
[b]Note:[/b] By default, the expensive calculations for avoidance are done in a thread. In HTML5 exports without thread support, they will be done on the main thread, which can lead to performance issues.
</description>
<tutorials>
</tutorials>

View File

@ -10,6 +10,7 @@
For two regions to be connected to each other, they must share a similar edge. An edge is considered connected to another if both of its two vertices are at a distance less than [member Navigation.edge_connection_margin] to the respective other edge's vertex.
To use the collision avoidance system, you may use agents. You can set an agent's target velocity, then the servers will emit a callback with a modified velocity.
[b]Note:[/b] The collision avoidance system ignores regions. Using the modified velocity as-is might lead to pushing and agent outside of a navigable area. This is a limitation of the collision avoidance system, any more complex situation may require the use of the physics engine.
[b]Note:[/b] By default, the expensive calculations for avoidance are done in a thread. In HTML5 exports without thread support, they will be done on the main thread, which can lead to performance issues.
This server keeps tracks of any call and executes them during the sync phase. This means that you can request any change to the map, using any thread, without worrying.
</description>
<tutorials>

View File

@ -18,6 +18,7 @@
</member>
<member name="omni_range" type="float" setter="set_param" getter="get_param" default="5.0">
The light's radius. Note that the effectively lit area may appear to be smaller depending on the [member omni_attenuation] in use. No matter the [member omni_attenuation] in use, the light will never reach anything outside this radius.
[b]Note:[/b] [member omni_range] is not affected by [member Spatial.scale] (the light's scale or its parent's scale).
</member>
<member name="omni_shadow_detail" type="int" setter="set_shadow_detail" getter="get_shadow_detail" enum="OmniLight.ShadowDetail" default="1">
See [enum ShadowDetail].

View File

@ -273,6 +273,7 @@
<member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3( 1, 1, 1 )">
Scale part of the local transformation.
[b]Note:[/b] Mixed negative scales in 3D are not decomposable from the transformation matrix. Due to the way scale is represented with transformation matrices in Godot, the scale values will either be all positive or all negative.
[b]Note:[/b] Not all nodes are visually scaled by the [member scale] property. For example, [Light]s are not visually affected by [member scale].
</member>
<member name="transform" type="Transform" setter="set_transform" getter="get_transform" default="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )">
Local space [Transform] of this node, with respect to the parent node.

View File

@ -16,6 +16,7 @@
<members>
<member name="spot_angle" type="float" setter="set_param" getter="get_param" default="45.0">
The spotlight's angle in degrees.
[b]Note:[/b] [member spot_angle] is not affected by [member Spatial.scale] (the light's scale or its parent's scale).
</member>
<member name="spot_angle_attenuation" type="float" setter="set_param" getter="get_param" default="1.0">
The spotlight's angular attenuation curve.
@ -25,6 +26,7 @@
</member>
<member name="spot_range" type="float" setter="set_param" getter="get_param" default="5.0">
The maximal range that can be reached by the spotlight. Note that the effectively lit area may appear to be smaller depending on the [member spot_attenuation] in use. No matter the [member spot_attenuation] in use, the light will never reach anything outside this range.
[b]Note:[/b] [member spot_range] is not affected by [member Spatial.scale] (the light's scale or its parent's scale).
</member>
</members>
<constants>

View File

@ -1680,6 +1680,9 @@ void RasterizerStorageGLES2::shader_get_param_list(RID p_shader, List<PropertyIn
case ShaderLanguage::TYPE_USAMPLER3D: {
// Not implemented in GLES2
} break;
default: {
}
}
p_param_list->push_back(pi);

View File

@ -2460,6 +2460,8 @@ void RasterizerStorageGLES3::shader_get_param_list(RID p_shader, List<PropertyIn
pi.hint = PROPERTY_HINT_RESOURCE_TYPE;
pi.hint_string = "CubeMap";
} break;
default: {
}
};
p_param_list->push_back(pi);

View File

@ -114,6 +114,8 @@ static int _get_datatype_size(SL::DataType p_type) {
return 16;
case SL::TYPE_STRUCT:
return 0;
default: {
}
}
ERR_FAIL_V(0);
@ -185,6 +187,8 @@ static int _get_datatype_alignment(SL::DataType p_type) {
return 16;
case SL::TYPE_STRUCT:
return 0;
default: {
}
}
ERR_FAIL_V(0);

View File

@ -467,12 +467,6 @@ void main() {
color.rgb = apply_cas(color.rgb, full_exposure, uv_interp, sharpen_intensity);
#endif
#ifdef USE_DEBANDING
// For best results, debanding should be done before tonemapping.
// Otherwise, we're adding noise to an already-quantized image.
color.rgb += screen_space_dither(gl_FragCoord.xy);
#endif
// Early Tonemap & SRGB Conversion; note that Linear tonemapping does not clamp to [0, 1]; some operations below expect a [0, 1] range and will clamp
color.rgb = apply_tonemapping(color.rgb, white);
@ -507,6 +501,12 @@ void main() {
color.rgb = apply_color_correction(color.rgb, color_correction);
#endif
#ifdef USE_DEBANDING
// Debanding should be done at the end of tonemapping, but before writing to the LDR buffer.
// Otherwise, we're adding noise to an already-quantized image.
color.rgb += screen_space_dither(gl_FragCoord.xy);
#endif
frag_color = color;
#ifdef DISABLE_ALPHA

View File

@ -154,7 +154,7 @@ Error DirAccessWindows::make_dir(String p_dir) {
if (p_dir.is_rel_path())
p_dir = current_dir.plus_file(p_dir);
p_dir = p_dir.replace("/", "\\");
p_dir = p_dir.simplify_path().replace("/", "\\");
bool success;
int err;

View File

@ -845,6 +845,8 @@ void Collada::_parse_curve_geometry(XMLParser &parser, String p_id, String p_nam
CurveData &curvedata = state.curve_data_map[p_id];
curvedata.name = p_name;
String closed = parser.get_attribute_value_safe("closed").to_lower();
curvedata.closed = closed == "true" || closed == "1";
COLLADA_PRINT("curve name: " + p_name);

View File

@ -549,7 +549,12 @@ bool EditorFileSystem::_update_scan_actions() {
if (_test_for_reimport(full_path, false)) {
//must reimport
reimports.push_back(full_path);
reimports.append_array(_get_dependencies(full_path));
Vector<String> dependencies = _get_dependencies(full_path);
for (int i = 0; i < dependencies.size(); i++) {
if (import_extensions.has(dependencies[i].get_extension())) {
reimports.push_back(dependencies[i]);
}
}
} else {
//must not reimport, all was good
//update modified times, to avoid reimport

View File

@ -3840,7 +3840,7 @@ void EditorNode::_quick_opened() {
List<String> scene_extensions;
ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions);
if (open_scene_dialog || scene_extensions.find(files[i].get_extension())) {
if (open_scene_dialog || scene_extensions.find(files[i].get_extension().to_lower())) {
open_request(res_path);
} else {
load_resource(res_path);
@ -5630,6 +5630,8 @@ void EditorNode::_project_settings_changed() {
tree->set_debug_collision_contact_color(GLOBAL_GET("debug/shapes/collision/contact_color"));
tree->set_debug_navigation_color(GLOBAL_GET("debug/shapes/navigation/geometry_color"));
tree->set_debug_navigation_disabled_color(GLOBAL_GET("debug/shapes/navigation/disabled_geometry_color"));
_update_title();
}
void EditorNode::_feature_profile_changed() {

View File

@ -1074,6 +1074,12 @@ Error ColladaImport::_create_resources(Collada::Node *p_node, uint32_t p_use_com
c->set_point_tilt(i, tilts->array[i]);
}
}
if (cd.closed && pc > 1) {
Vector3 pos = c->get_point_position(0);
Vector3 in = c->get_point_in(0);
Vector3 out = c->get_point_out(0);
c->add_point(pos, in, out, -1);
}
curve_cache[ng->source] = c;
path->set_curve(c);

View File

@ -456,16 +456,22 @@ void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) {
undo_redo->commit_action();
}
void AnimationNodeBlendTreeEditor::_delete_nodes_request() {
void AnimationNodeBlendTreeEditor::_delete_nodes_request(const Array &p_nodes) {
List<StringName> to_erase;
for (int i = 0; i < graph->get_child_count(); i++) {
GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i));
if (gn) {
if (gn->is_selected() && gn->is_close_button_visible()) {
to_erase.push_back(gn->get_name());
if (p_nodes.empty()) {
for (int i = 0; i < graph->get_child_count(); i++) {
GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i));
if (gn) {
if (gn->is_selected() && gn->is_close_button_visible()) {
to_erase.push_back(gn->get_name());
}
}
}
} else {
for (int i = 0; i < p_nodes.size(); i++) {
to_erase.push_back(p_nodes[i]);
}
}
if (to_erase.empty()) {

View File

@ -103,7 +103,7 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
void _open_in_editor(const String &p_which);
void _anim_selected(int p_index, Array p_options, const String &p_node);
void _delete_request(const String &p_which);
void _delete_nodes_request();
void _delete_nodes_request(const Array &p_nodes);
bool _update_filters(const Ref<AnimationNode> &anode);
void _edit_filters(const String &p_which);

View File

@ -76,7 +76,7 @@ void MeshInstanceEditor::_menu_option(int p_option) {
StaticBody *body = memnew(StaticBody);
body->add_child(cshape);
Node *owner = node == get_tree()->get_edited_scene_root() ? node : node->get_owner();
Node *owner = get_tree()->get_edited_scene_root();
ur->create_action(TTR("Create Static Trimesh Body"));
ur->add_do_method(node, "add_child", body);
@ -111,7 +111,7 @@ void MeshInstanceEditor::_menu_option(int p_option) {
StaticBody *body = memnew(StaticBody);
body->add_child(cshape);
Node *owner = instance == get_tree()->get_edited_scene_root() ? instance : instance->get_owner();
Node *owner = get_tree()->get_edited_scene_root();
ur->add_do_method(instance, "add_child", body);
ur->add_do_method(body, "set_owner", owner);
@ -140,7 +140,7 @@ void MeshInstanceEditor::_menu_option(int p_option) {
cshape->set_shape(shape);
cshape->set_transform(node->get_transform());
Node *owner = node->get_owner();
Node *owner = get_tree()->get_edited_scene_root();
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
@ -183,7 +183,7 @@ void MeshInstanceEditor::_menu_option(int p_option) {
cshape->set_shape(shape);
cshape->set_transform(node->get_transform());
Node *owner = node->get_owner();
Node *owner = get_tree()->get_edited_scene_root();
ur->add_do_method(node->get_parent(), "add_child", cshape);
ur->add_do_method(node->get_parent(), "move_child", cshape, node->get_index() + 1);
@ -218,7 +218,7 @@ void MeshInstanceEditor::_menu_option(int p_option) {
cshape->set_shape(shapes[i]);
cshape->set_transform(node->get_transform());
Node *owner = node->get_owner();
Node *owner = get_tree()->get_edited_scene_root();
ur->add_do_method(node->get_parent(), "add_child", cshape);
ur->add_do_method(node->get_parent(), "move_child", cshape, node->get_index() + 1);
@ -241,7 +241,7 @@ void MeshInstanceEditor::_menu_option(int p_option) {
NavigationMeshInstance *nmi = memnew(NavigationMeshInstance);
nmi->set_navigation_mesh(nmesh);
Node *owner = node == get_tree()->get_edited_scene_root() ? node : node->get_owner();
Node *owner = get_tree()->get_edited_scene_root();
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
ur->create_action(TTR("Create Navigation Mesh"));
@ -419,10 +419,7 @@ void MeshInstanceEditor::_create_outline_mesh() {
MeshInstance *mi = memnew(MeshInstance);
mi->set_mesh(mesho);
Node *owner = node->get_owner();
if (get_tree()->get_edited_scene_root() == node) {
owner = node;
}
Node *owner = get_tree()->get_edited_scene_root();
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();

View File

@ -1476,16 +1476,17 @@ bool ScriptTextEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_
}
static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const Ref<Script> &script) {
if (p_edited_scene != p_current_node && p_current_node->get_owner() != p_edited_scene) {
return nullptr;
}
Ref<Script> scr = p_current_node->get_script();
if (scr.is_valid() && scr == script) {
return p_current_node;
// Check scripts only for the nodes belonging to the edited scene.
if (p_current_node == p_edited_scene || p_current_node->get_owner() == p_edited_scene) {
Ref<Script> scr = p_current_node->get_script();
if (scr.is_valid() && scr == script) {
return p_current_node;
}
}
// Traverse all children, even the ones not owned by the edited scene as they
// can still have child nodes added within the edited scene and thus owned by
// it (e.g. nodes added to subscene's root or to its editable children).
for (int i = 0; i < p_current_node->get_child_count(); i++) {
Node *n = _find_script_node(p_edited_scene, p_current_node->get_child(i), script);
if (n) {

View File

@ -51,7 +51,7 @@ void SkeletonEditor::_on_click_option(int p_option) {
void SkeletonEditor::create_physical_skeleton() {
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
Node *owner = skeleton == get_tree()->get_edited_scene_root() ? skeleton : skeleton->get_owner();
Node *owner = get_tree()->get_edited_scene_root();
const int bc = skeleton->get_bone_count();

View File

@ -3674,20 +3674,41 @@ void SpatialEditorViewport::assign_pending_data_pointers(Spatial *p_preview_node
Vector3 SpatialEditorViewport::_get_instance_position(const Point2 &p_pos) const {
const float MAX_DISTANCE = 50.0;
const float FALLBACK_DISTANCE = 5.0;
Vector3 world_ray = _get_ray(p_pos);
Vector3 world_pos = _get_ray_pos(p_pos);
Vector3 point = world_pos + world_ray * MAX_DISTANCE;
PhysicsDirectSpaceState *ss = get_tree()->get_root()->get_world()->get_direct_space_state();
PhysicsDirectSpaceState::RayResult result;
if (ss->intersect_ray(world_pos, world_pos + world_ray * MAX_DISTANCE, result)) {
point = result.position;
if (ss->intersect_ray(world_pos, world_pos + world_ray * camera->get_zfar(), result)) {
return result.position;
}
return point;
const bool is_orthogonal = camera->get_projection() == Camera::PROJECTION_ORTHOGONAL;
// The XZ plane.
Vector3 intersection;
Plane plane(Vector3(0, 1, 0), 0);
if (plane.intersects_ray(world_pos, world_ray, &intersection)) {
if (is_orthogonal || world_pos.distance_to(intersection) <= MAX_DISTANCE) {
return intersection;
}
}
// Plane facing the camera using fallback distance.
if (is_orthogonal) {
plane = Plane(cursor.pos - world_ray * (cursor.distance - FALLBACK_DISTANCE), world_ray);
} else {
plane = Plane(world_pos + world_ray * FALLBACK_DISTANCE, world_ray);
}
if (plane.intersects_ray(world_pos, world_ray, &intersection)) {
return intersection;
}
// Not likely, but just in case...
return world_pos + world_ray * FALLBACK_DISTANCE;
}
AABB SpatialEditorViewport::_calculate_spatial_bounds(const Spatial *p_parent, bool p_exclude_toplevel_transform) {
@ -3924,7 +3945,8 @@ bool SpatialEditorViewport::can_drop_data_fw(const Point2 &p_point, const Varian
ResourceLoader::get_recognized_extensions_for_type("Mesh", &mesh_extensions);
for (int i = 0; i < files.size(); i++) {
if (mesh_extensions.find(files[i].get_extension()) || scene_extensions.find(files[i].get_extension())) {
String extension = files[i].get_extension().to_lower();
if (mesh_extensions.find(extension) || scene_extensions.find(extension)) {
RES res = ResourceLoader::load(files[i]);
if (res.is_null()) {
continue;
@ -3959,7 +3981,7 @@ bool SpatialEditorViewport::can_drop_data_fw(const Point2 &p_point, const Varian
}
if (can_instance) {
Transform global_transform = Transform(Basis(), _get_instance_position(p_point));
Transform global_transform = Transform(Basis(), spatial_editor->snap_point(_get_instance_position(p_point)));
preview_node->set_global_transform(global_transform);
}
@ -6051,6 +6073,20 @@ bool SpatialEditor::is_any_freelook_active() const {
return false;
}
void SpatialEditor::_selection_changed() {
_refresh_menu_icons();
if (selected && editor_selection->get_selected_node_list().size() != 1) {
Ref<EditorSpatialGizmo> gizmo = selected->get_gizmo();
if (gizmo.is_valid()) {
gizmo->set_selected(false);
selected->update_gizmo();
}
selected = nullptr;
over_gizmo_handle = -1;
}
}
void SpatialEditor::_refresh_menu_icons() {
bool all_locked = true;
bool all_grouped = true;
@ -6275,7 +6311,7 @@ void SpatialEditor::_notification(int p_what) {
get_tree()->connect("node_removed", this, "_node_removed");
EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->connect("node_changed", this, "_refresh_menu_icons");
editor_selection->connect("selection_changed", this, "_refresh_menu_icons");
editor_selection->connect("selection_changed", this, "_selection_changed");
editor->connect("stop_pressed", this, "_update_camera_override_button", make_binds(false));
editor->connect("play_pressed", this, "_update_camera_override_button", make_binds(true));
@ -6513,6 +6549,7 @@ void SpatialEditor::_bind_methods() {
ClassDB::bind_method("_get_editor_data", &SpatialEditor::_get_editor_data);
ClassDB::bind_method("_request_gizmo", &SpatialEditor::_request_gizmo);
ClassDB::bind_method("_toggle_maximize_view", &SpatialEditor::_toggle_maximize_view);
ClassDB::bind_method("_selection_changed", &SpatialEditor::_selection_changed);
ClassDB::bind_method("_refresh_menu_icons", &SpatialEditor::_refresh_menu_icons);
ClassDB::bind_method("_update_camera_override_button", &SpatialEditor::_update_camera_override_button);
ClassDB::bind_method("_update_camera_override_viewport", &SpatialEditor::_update_camera_override_viewport);
@ -6974,7 +7011,13 @@ void SpatialEditorPlugin::edit(Object *p_object) {
}
bool SpatialEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("Spatial");
if (p_object->is_class("Spatial")) {
return true;
} else {
// This ensures that gizmos are cleared when selecting a non-Spatial node.
const_cast<SpatialEditorPlugin *>(this)->edit((Object *)nullptr);
return false;
}
}
Dictionary SpatialEditorPlugin::get_state() const {

View File

@ -752,6 +752,7 @@ private:
bool is_any_freelook_active() const;
void _selection_changed();
void _refresh_menu_icons();
protected:

View File

@ -67,14 +67,18 @@ int SpriteFramesEditor::_sheet_preview_position_to_frame_index(const Point2 &p_p
const Size2i block_size = frame_size + separation;
const Point2i position = p_position / sheet_zoom - offset;
if (position.x % block_size.x > frame_size.x || position.y % block_size.y > frame_size.y) {
if (position.x < 0 || position.y < 0) {
return -1; // Out of bounds.
}
if (position.x % block_size.x >= frame_size.x || position.y % block_size.y >= frame_size.y) {
return -1; // Gap between frames.
}
const Point2i frame = position / block_size;
const Size2i frame_count = _get_frame_count();
if (frame.x < 0 || frame.y < 0 || frame.x >= frame_count.x || frame.y >= frame_count.y) {
return -1; // Out of bound.
if (frame.x >= frame_count.x || frame.y >= frame_count.y) {
return -1; // Out of bounds.
}
return frame_count.x * frame.y + frame.x;

View File

@ -430,17 +430,17 @@ private:
return;
}
ProjectSettings *current = memnew(ProjectSettings);
int err = current->setup(dir2, "");
// Load project.godot as ConfigFile to set the new name.
ConfigFile cfg;
String project_godot = dir2.plus_file("project.godot");
Error err = cfg.load(project_godot);
if (err != OK) {
set_message(vformat(TTR("Couldn't load project.godot in project path (error %d). It may be missing or corrupted."), err), MESSAGE_ERROR);
set_message(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err), MESSAGE_ERROR);
} else {
ProjectSettings::CustomMap edited_settings;
edited_settings["application/config/name"] = project_name->get_text().strip_edges();
if (current->save_custom(dir2.plus_file("project.godot"), edited_settings, Vector<String>(), true) != OK) {
set_message(TTR("Couldn't edit project.godot in project path."), MESSAGE_ERROR);
cfg.set_value("application", "config/name", project_name->get_text().strip_edges());
err = cfg.save(project_godot);
if (err != OK) {
set_message(vformat(TTR("Couldn't save project at '%s' (error %d)."), project_godot, err), MESSAGE_ERROR);
}
}
@ -688,18 +688,19 @@ public:
rasterizer_container->hide();
get_ok()->set_disabled(false);
ProjectSettings *current = memnew(ProjectSettings);
int err = current->setup(project_path->get_text(), "");
// Fetch current name from project.godot to prefill the text input.
ConfigFile cfg;
String project_godot = project_path->get_text().plus_file("project.godot");
Error err = cfg.load(project_godot);
if (err != OK) {
set_message(vformat(TTR("Couldn't load project.godot in project path (error %d). It may be missing or corrupted."), err), MESSAGE_ERROR);
set_message(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err), MESSAGE_ERROR);
status_rect->show();
msg->show();
get_ok()->set_disabled(true);
} else if (current->has_setting("application/config/name")) {
String proj = current->get("application/config/name");
project_name->set_text(proj);
_text_changed(proj);
} else {
String cur_name = cfg.get_value("application", "config/name", "");
project_name->set_text(cur_name);
_text_changed(cur_name);
}
project_name->call_deferred("grab_focus");

View File

@ -2289,8 +2289,22 @@ void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) {
ti = ti->get_parent();
}
// We only need the first child here (C++ source stack trace).
// Find the child with the "C++ Source".
// It's not at a fixed position as "C++ Error" may come first.
TreeItem *ci = ti->get_children();
const String cpp_source = "<" + TTR("C++ Source") + ">";
while (ci) {
if (ci->get_text(0) == cpp_source) {
break;
}
ci = ci->get_next();
}
if (!ci) {
WARN_PRINT("No C++ source reference is available for this error.");
return;
}
// Parse back the `file:line @ method()` string.
const Vector<String> file_line_number = ci->get_text(1).split("@")[0].strip_edges().split(":");
ERR_FAIL_COND_MSG(file_line_number.size() < 2, "Incorrect C++ source stack trace file:line format (please report).");

View File

@ -607,6 +607,8 @@ void InputDefault::action_press(const StringName &p_action, float p_strength) {
action.idle_frame = Engine::get_singleton()->get_idle_frames();
action.pressed = true;
action.strength = p_strength;
action.raw_strength = p_strength;
action.exact = true;
action_state[p_action] = action;
}
@ -618,6 +620,8 @@ void InputDefault::action_release(const StringName &p_action) {
action.idle_frame = Engine::get_singleton()->get_idle_frames();
action.pressed = false;
action.strength = 0.f;
action.raw_strength = 0.f;
action.exact = true;
action_state[p_action] = action;
}

View File

@ -638,6 +638,14 @@ bool test_28() {
OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
state = state && success;
// Real (infinity) left-padded
format = "fish %11f frog";
args.clear();
args.push_back(INFINITY);
output = format.sprintf(args, &error);
success = (output == String("fish inf frog") && !error);
state = state && success;
// Real right-padded
format = "fish %-11f frog";
args.clear();
@ -1211,6 +1219,41 @@ bool test_36() {
return true;
}
bool test_37() {
#define CHECK_EQ(X, Y) \
if ((X) != (Y)) { \
OS::get_singleton()->print("\tFAIL: %s != %s\n", #X, #Y); \
return false; \
} else { \
OS::get_singleton()->print("\tPASS\n"); \
}
OS::get_singleton()->print("\n\nTest 37: Word wrap\n");
// Long words.
CHECK_EQ(String("12345678").word_wrap(8), "12345678");
CHECK_EQ(String("1234567812345678").word_wrap(8), "12345678\n12345678");
CHECK_EQ(String("123456781234567812345678").word_wrap(8), "12345678\n12345678\n12345678");
// Long line.
CHECK_EQ(String("123 567 123456 123").word_wrap(8), "123 567\n123456\n123");
// Force newline at line length should not create another newline.
CHECK_EQ(String("12345678 123").word_wrap(8), "12345678\n123");
CHECK_EQ(String("12345678\n123").word_wrap(8), "12345678\n123");
// Wrapping removes spaces.
CHECK_EQ(String("1234567 123").word_wrap(8), "1234567\n123");
CHECK_EQ(String("12345678 123").word_wrap(8), "12345678\n123");
// Wrapping does not remove leading space.
CHECK_EQ(String(" 123456 123 12").word_wrap(8), " 123456\n123 12");
CHECK_EQ(String(" 123456\n 456 12").word_wrap(8), " 123456\n 456\n12");
CHECK_EQ(String(" 123456\n 4 12345678").word_wrap(8), " 123456\n 4\n12345678");
CHECK_EQ(String(" 123456\n 4 12345678123").word_wrap(8), " 123456\n 4\n12345678\n123");
return true;
}
typedef bool (*TestFunc)();
TestFunc test_funcs[] = {
@ -1251,6 +1294,7 @@ TestFunc test_funcs[] = {
test_34,
test_35,
test_36,
test_37,
nullptr
};

View File

@ -1757,6 +1757,15 @@ void NativeReloadNode::_notification(int p_what) {
}
for (Map<String, Set<NativeScript *>>::Element *U = NSL->library_script_users.front(); U; U = U->next()) {
// Multiple GDNative libraries may be reloaded. The library and script
// path should match to prevent failing `NSL->library_classes` lookup
// from `get_script_desc()` in `script->_update_placeholder` below.
// This check also prevents "!script_data is true" error from occuring
// when e. g. re-focusing editor window.
if (L->key() != U->key()) {
continue;
}
for (Set<NativeScript *>::Element *S = U->get().front(); S; S = S->next()) {
NativeScript *script = S->get();

View File

@ -4310,7 +4310,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
_set_error("Expected \")\" in the layers 2D navigation hint.");
return;
}
current_export.hint = PROPERTY_HINT_LAYERS_2D_PHYSICS;
current_export.hint = PROPERTY_HINT_LAYERS_2D_NAVIGATION;
break;
}

View File

@ -561,10 +561,12 @@ bool GridMap::_octant_update(const OctantKey &p_key) {
NavigationServer::get_singleton()->region_set_navigation_layers(region, navigation_layers);
NavigationServer::get_singleton()->region_set_navmesh(region, navmesh);
NavigationServer::get_singleton()->region_set_transform(region, get_global_transform() * nm.xform);
if (navigation) {
NavigationServer::get_singleton()->region_set_map(region, navigation->get_rid());
} else {
NavigationServer::get_singleton()->region_set_map(region, get_world()->get_navigation_map());
if (is_inside_tree()) {
if (navigation) {
NavigationServer::get_singleton()->region_set_map(region, navigation->get_rid());
} else {
NavigationServer::get_singleton()->region_set_map(region, get_world()->get_navigation_map());
}
}
nm.region = region;

View File

@ -326,7 +326,7 @@ namespace Godot.Collections
internal static extern Error godot_icall_Array_Resize(IntPtr ptr, int newSize);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern Error godot_icall_Array_Shuffle(IntPtr ptr);
internal static extern void godot_icall_Array_Shuffle(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void godot_icall_Array_Generic_GetElementTypeInfo(Type elemType, out int elemTypeEncoding, out IntPtr elemTypeClass);

View File

@ -71,6 +71,8 @@ void godot_icall_Array_RemoveAt(Array *ptr, int index);
int32_t godot_icall_Array_Resize(Array *ptr, int new_size);
void godot_icall_Array_Shuffle(Array *ptr);
void godot_icall_Array_Generic_GetElementTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class);
MonoString *godot_icall_Array_ToString(Array *ptr);

View File

@ -141,20 +141,17 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
// This is an implementation of the A* algorithm.
int least_cost_id = 0;
int prev_least_cost_id = -1;
bool found_route = false;
const gd::Polygon *reachable_end = nullptr;
float reachable_d = 1e30;
bool is_reachable = true;
gd::NavigationPoly *prev_least_cost_poly = nullptr;
while (true) {
// Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance.
for (size_t i = 0; i < navigation_polys[least_cost_id].poly->edges.size(); i++) {
gd::NavigationPoly *least_cost_poly = &navigation_polys[least_cost_id];
const gd::Edge &edge = least_cost_poly->poly->edges[i];
const gd::Edge &edge = navigation_polys[least_cost_id].poly->edges[i];
// Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon.
for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) {
@ -165,17 +162,18 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
continue;
}
const gd::NavigationPoly &least_cost_poly = navigation_polys[least_cost_id];
float region_enter_cost = 0.0;
float region_travel_cost = least_cost_poly->poly->owner->get_travel_cost();
float region_travel_cost = least_cost_poly.poly->owner->get_travel_cost();
if (prev_least_cost_poly != nullptr && !(prev_least_cost_poly->poly->owner->get_self() == least_cost_poly->poly->owner->get_self())) {
region_enter_cost = least_cost_poly->poly->owner->get_enter_cost();
if (prev_least_cost_id != -1 && !(navigation_polys[prev_least_cost_id].poly->owner->get_self() == least_cost_poly.poly->owner->get_self())) {
region_enter_cost = least_cost_poly.poly->owner->get_enter_cost();
}
prev_least_cost_poly = least_cost_poly;
prev_least_cost_id = least_cost_id;
Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end };
const Vector3 new_entry = Geometry::get_closest_point_to_segment(least_cost_poly->entry, pathway);
const float new_distance = (least_cost_poly->entry.distance_to(new_entry) * region_travel_cost) + region_enter_cost + least_cost_poly->traveled_distance;
const Vector3 new_entry = Geometry::get_closest_point_to_segment(least_cost_poly.entry, pathway);
const float new_distance = (least_cost_poly.entry.distance_to(new_entry) * region_travel_cost) + region_enter_cost + least_cost_poly.traveled_distance;
int64_t already_visited_polygon_index = navigation_polys.find(gd::NavigationPoly(connection.polygon));
@ -241,6 +239,7 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
to_visit.clear();
to_visit.push_back(0);
least_cost_id = 0;
prev_least_cost_id = -1;
reachable_end = nullptr;
@ -690,6 +689,7 @@ void NavMap::compute_single_step(uint32_t index, RvoAgent **agent) {
void NavMap::step(real_t p_deltatime) {
deltatime = p_deltatime;
if (controlled_agents.size() > 0) {
#ifndef NO_THREADS
if (step_work_pool.get_thread_count() == 0) {
step_work_pool.init();
}
@ -698,6 +698,12 @@ void NavMap::step(real_t p_deltatime) {
this,
&NavMap::compute_single_step,
controlled_agents.ptr());
#else
for (int i(0); i < static_cast<int>(controlled_agents.size()); i++) {
controlled_agents[i]->get_agent()->computeNeighbors(&rvo);
controlled_agents[i]->get_agent()->computeNewVelocity(deltatime);
}
#endif // NO_THREADS
}
}
@ -743,5 +749,7 @@ NavMap::NavMap() {
}
NavMap::~NavMap() {
#ifndef NO_THREADS
step_work_pool.finish();
#endif // !NO_THREADS
}

View File

@ -82,8 +82,10 @@ class NavMap : public NavRid {
/// Change the id each time the map is updated.
uint32_t map_update_id = 0;
#ifndef NO_THREADS
/// Pooled threads for computing steps
ThreadWorkPool step_work_pool;
#endif // NO_THREADS
public:
NavMap();

View File

@ -92,7 +92,8 @@
<argument index="1" name="offset" type="int" default="0" />
<argument index="2" name="end" type="int" default="-1" />
<description>
Searches the text for the compiled pattern. Returns a [RegExMatch] container of the first matching result if found, otherwise [code]null[/code]. The region to search within can be specified without modifying where the start and end anchor would be.
Searches the text for the compiled pattern. Returns a [RegExMatch] container of the first matching result if found, otherwise [code]null[/code].
The region to search within can be specified with [code]offset[/code] and [code]end[/code]. This is useful when searching for another match in the same [code]subject[/code] by calling this method again after a previous success. Setting these parameters differs from passing over a shortened string. For example, the start anchor [code]^[/code] is not affected by [code]offset[/code], and the character before [code]offset[/code] will be checked for the word boundary [code]\b[/code].
</description>
</method>
<method name="search_all" qualifiers="const">
@ -101,7 +102,8 @@
<argument index="1" name="offset" type="int" default="0" />
<argument index="2" name="end" type="int" default="-1" />
<description>
Searches the text for the compiled pattern. Returns an array of [RegExMatch] containers for each non-overlapping result. If no results were found, an empty array is returned instead. The region to search within can be specified without modifying where the start and end anchor would be.
Searches the text for the compiled pattern. Returns an array of [RegExMatch] containers for each non-overlapping result. If no results were found, an empty array is returned instead.
The region to search within can be specified with [code]offset[/code] and [code]end[/code]. This is useful when searching for another match in the same [code]subject[/code] by calling this method again after a previous success. Setting these parameters differs from passing over a shortened string. For example, the start anchor [code]^[/code] is not affected by [code]offset[/code], and the character before [code]offset[/code] will be checked for the word boundary [code]\b[/code].
</description>
</method>
<method name="sub" qualifiers="const">
@ -112,7 +114,8 @@
<argument index="3" name="offset" type="int" default="0" />
<argument index="4" name="end" type="int" default="-1" />
<description>
Searches the text for the compiled pattern and replaces it with the specified string. Escapes and backreferences such as [code]$1[/code] and [code]$name[/code] are expanded and resolved. By default, only the first instance is replaced, but it can be changed for all instances (global replacement). The region to search within can be specified without modifying where the start and end anchor would be.
Searches the text for the compiled pattern and replaces it with the specified string. Escapes and backreferences such as [code]$1[/code] and [code]$name[/code] are expanded and resolved. By default, only the first instance is replaced, but it can be changed for all instances (global replacement).
The region to search within can be specified with [code]offset[/code] and [code]end[/code]. This is useful when searching for another match in the same [code]subject[/code] by calling this method again after a previous success. Setting these parameters differs from passing over a shortened string. For example, the start anchor [code]^[/code] is not affected by [code]offset[/code], and the character before [code]offset[/code] will be checked for the word boundary [code]\b[/code].
</description>
</method>
</methods>

View File

@ -265,14 +265,21 @@ Error ImageLoaderTGA::load_image(Ref<Image> p_image, FileAccess *f, bool p_force
err = FAILED;
}
uint64_t color_map_size;
if (has_color_map) {
if (tga_header.color_map_length > 256 || (tga_header.color_map_depth != 24) || tga_header.color_map_type != 1) {
err = FAILED;
}
color_map_size = tga_header.color_map_length * (tga_header.color_map_depth >> 3);
} else {
if (tga_header.color_map_type) {
err = FAILED;
}
color_map_size = 0;
}
if ((src_image_len - f->get_position()) < (tga_header.id_length + color_map_size)) {
err = FAILED; // TGA data appears to be truncated (fewer bytes than expected).
}
if (tga_header.image_width <= 0 || tga_header.image_height <= 0) {
@ -289,7 +296,6 @@ Error ImageLoaderTGA::load_image(Ref<Image> p_image, FileAccess *f, bool p_force
PoolVector<uint8_t> palette;
if (has_color_map) {
size_t color_map_size = tga_header.color_map_length * (tga_header.color_map_depth >> 3);
err = palette.resize(color_map_size);
if (err == OK) {
PoolVector<uint8_t>::Write palette_w = palette.write();

View File

@ -2563,8 +2563,8 @@ void EditorExportPlatformAndroid::_update_custom_build_project() {
append_line = true;
} else {
String base = l.substr(0, last_tag_pos + last_tag.length());
if (manifest_sections.has("application_attribs")) {
for (List<String>::Element *E = manifest_sections["application_attribs"].front(); E; E = E->next()) {
if (manifest_sections.has("APPLICATION_ATTRIBS")) {
for (List<String>::Element *E = manifest_sections["APPLICATION_ATTRIBS"].front(); E; E = E->next()) {
String to_add = E->get().strip_edges();
base += " " + to_add + " ";
}

View File

@ -281,6 +281,11 @@ task generateDevTemplate {
finalizedBy 'zipCustomBuild'
}
task clean(type: Delete) {
dependsOn 'cleanGodotEditor'
dependsOn 'cleanGodotTemplates'
}
/**
* Clean the generated editor artifacts.
*/
@ -297,8 +302,6 @@ task cleanGodotEditor(type: Delete) {
// Delete the Godot editor apks in the Godot bin directory
delete("$binDir/android_editor.apk")
delete("$binDir/android_editor_dev.apk")
finalizedBy getTasksByName("clean", true)
}
/**
@ -325,6 +328,4 @@ task cleanGodotTemplates(type: Delete) {
delete("$binDir/godot-lib.debug.aar")
delete("$binDir/godot-lib.dev.aar")
delete("$binDir/godot-lib.release.aar")
finalizedBy getTasksByName("clean", true)
}

View File

@ -126,7 +126,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
@Override
public boolean onEditorAction(final TextView pTextView, final int pActionID, final KeyEvent pKeyEvent) {
if (this.mEdit == pTextView && this.isFullScreenEdit()) {
if (this.mEdit == pTextView && this.isFullScreenEdit() && pKeyEvent != null) {
final String characters = pKeyEvent.getCharacters();
for (int i = 0; i < characters.length(); i++) {

View File

@ -370,6 +370,10 @@ void OS_JavaScript::set_cursor_shape(CursorShape p_shape) {
godot_js_display_cursor_set_shape(godot2dom_cursor(cursor_shape));
}
OS::CursorShape OS_JavaScript::get_cursor_shape() const {
return cursor_shape;
}
void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
if (p_cursor.is_valid()) {
Ref<Texture> texture = p_cursor;

View File

@ -148,6 +148,7 @@ public:
virtual Point2 get_mouse_position() const;
virtual int get_mouse_button_state() const;
virtual void set_cursor_shape(CursorShape p_shape);
virtual CursorShape get_cursor_shape() const;
virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
virtual void set_mouse_mode(MouseMode p_mode);
virtual MouseMode get_mouse_mode() const;

View File

@ -193,7 +193,9 @@ static NSCursor *cursorFromSelector(SEL selector, SEL fallback = nil) {
- (void)applicationDidFinishLaunching:(NSNotification *)notice {
NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
if (nsappname == nil || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO)) {
NSString *nsbundleid_env = [NSString stringWithUTF8String:getenv("__CFBundleIdentifier")];
NSString *nsbundleid = [[NSBundle mainBundle] bundleIdentifier];
if (nsappname == nil || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO) || ![nsbundleid isEqualToString:nsbundleid_env]) {
// If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
[self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
}

View File

@ -1183,7 +1183,7 @@ void OS_Windows::process_key_events() {
k->set_control(ke.control);
k->set_metakey(ke.meta);
k->set_pressed(true);
k->set_scancode(KeyMappingWindows::get_keysym(ke.wParam));
k->set_scancode(KeyMappingWindows::get_keysym(MapVirtualKey((ke.lParam >> 16) & 0xFF, MAPVK_VSC_TO_VK)));
k->set_physical_scancode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)));
k->set_unicode(ke.wParam);
if (k->get_unicode() && gr_mem) {

View File

@ -203,7 +203,10 @@ int detect_prime() {
print_verbose("Couldn't write vendor/renderer string.");
}
close(fdset[1]);
exit(0);
// The function quick_exit() is used because exit() will call destructors on static objects copied by fork().
// These objects will be freed anyway when the process finishes execution.
quick_exit(0);
}
}

View File

@ -481,8 +481,8 @@ void NavigationAgent2D::_request_repath() {
void NavigationAgent2D::_check_distance_to_target() {
if (!target_reached) {
if (distance_to_target() < target_desired_distance) {
emit_signal("target_reached");
target_reached = true;
emit_signal("target_reached");
}
}
}

View File

@ -134,7 +134,7 @@ NavigationObstacle2D::~NavigationObstacle2D() {
}
void NavigationObstacle2D::set_navigation(Navigation2D *p_nav) {
if (navigation == p_nav) {
if (navigation == p_nav && navigation != nullptr) {
return; // Pointless
}

View File

@ -664,6 +664,12 @@ void MeshInstance::_notification(int p_what) {
_resolve_skeleton_path();
}
if (p_what == NOTIFICATION_TRANSLATION_CHANGED) {
if (mesh.is_valid()) {
mesh->notification(NOTIFICATION_TRANSLATION_CHANGED);
}
}
if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
if (skin_ref.is_valid() && mesh.is_valid() && _is_software_skinning_enabled()) {
ERR_FAIL_COND(!skin_ref->get_skeleton_node());

View File

@ -140,7 +140,7 @@ NavigationObstacle::~NavigationObstacle() {
}
void NavigationObstacle::set_navigation(Navigation *p_nav) {
if (navigation == p_nav) {
if (navigation == p_nav && navigation != nullptr) {
return; // Pointless
}
@ -216,12 +216,11 @@ real_t NavigationObstacle::estimate_agent_radius() const {
}
Vector3 s = parent_spatial->get_global_transform().basis.get_scale();
radius *= MAX(s.x, MAX(s.y, s.z));
}
if (radius > 0.0) {
return radius;
if (radius > 0.0) {
return radius;
}
}
return 1.0; // Never a 0 radius
}

View File

@ -140,8 +140,9 @@ bool Skeleton::_get(const StringName &p_path, Variant &r_ret) const {
} else if (what == "bound_children") {
Array children;
for (const List<uint32_t>::Element *E = bones[which].nodes_bound.front(); E; E = E->next()) {
Object *obj = ObjectDB::get_instance(E->get());
const LocalVectori<uint32_t> &nodes = bones[which].nodes_bound;
for (int i = 0; i < nodes.size(); i++) {
Object *obj = ObjectDB::get_instance(nodes[i]);
ERR_CONTINUE(!obj);
Node *node = Object::cast_to<Node>(obj);
ERR_CONTINUE(!node);
@ -290,8 +291,9 @@ void Skeleton::_notification(int p_what) {
b.global_pose_override_amount = 0.0;
}
for (List<uint32_t>::Element *E = b.nodes_bound.front(); E; E = E->next()) {
Object *obj = ObjectDB::get_instance(E->get());
const LocalVectori<uint32_t> &nodes = b.nodes_bound;
for (int j = 0; j < nodes.size(); j++) {
Object *obj = ObjectDB::get_instance(nodes[j]);
ERR_CONTINUE(!obj);
Spatial *sp = Object::cast_to<Spatial>(obj);
ERR_CONTINUE(!sp);
@ -525,10 +527,8 @@ void Skeleton::bind_child_node_to_bone(int p_bone, Node *p_node) {
uint32_t id = p_node->get_instance_id();
for (const List<uint32_t>::Element *E = bones[p_bone].nodes_bound.front(); E; E = E->next()) {
if (E->get() == id) {
return; // already here
}
if (bones[p_bone].nodes_bound.find(id) != -1) {
return; // Already here.
}
bones.write[p_bone].nodes_bound.push_back(id);
@ -538,13 +538,20 @@ void Skeleton::unbind_child_node_from_bone(int p_bone, Node *p_node) {
ERR_FAIL_INDEX(p_bone, bones.size());
uint32_t id = p_node->get_instance_id();
bones.write[p_bone].nodes_bound.erase(id);
int index = bones[p_bone].nodes_bound.find(id);
if (index == -1) {
return; // Doesn't exist in the first place.
}
bones.write[p_bone].nodes_bound.remove(index);
}
void Skeleton::get_bound_child_nodes_to_bone(int p_bone, List<Node *> *p_bound) const {
ERR_FAIL_INDEX(p_bone, bones.size());
for (const List<uint32_t>::Element *E = bones[p_bone].nodes_bound.front(); E; E = E->next()) {
Object *obj = ObjectDB::get_instance(E->get());
const LocalVectori<uint32_t> &nodes = bones[p_bone].nodes_bound;
for (int i = 0; i < nodes.size(); i++) {
Object *obj = ObjectDB::get_instance(nodes[i]);
ERR_CONTINUE(!obj);
p_bound->push_back(Object::cast_to<Node>(obj));
}
@ -700,10 +707,14 @@ void _pb_start_simulation(const Skeleton *p_skeleton, Node *p_node, const Vector
PhysicalBone *pb = Object::cast_to<PhysicalBone>(p_node);
if (pb) {
bool sim = false;
for (int i = p_sim_bones.size() - 1; 0 <= i; --i) {
if (p_sim_bones[i] == pb->get_bone_id() || p_skeleton->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) {
sim = true;
break;
if (p_sim_bones.empty()) { // If no bones is specified, activate ragdoll on full body.
sim = true;
} else {
for (int i = p_sim_bones.size() - 1; 0 <= i; --i) {
if (p_sim_bones[i] == pb->get_bone_id() || p_skeleton->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) {
sim = true;
break;
}
}
}
@ -718,9 +729,7 @@ void _pb_start_simulation(const Skeleton *p_skeleton, Node *p_node, const Vector
void Skeleton::physical_bones_start_simulation_on(const Array &p_bones) {
Vector<int> sim_bones;
if (p_bones.size() <= 0) {
sim_bones.push_back(0); // if no bones is specified, activate ragdoll on full body
} else {
if (p_bones.size() > 0) {
sim_bones.resize(p_bones.size());
int c = 0;
for (int i = sim_bones.size() - 1; 0 <= i; --i) {

View File

@ -102,7 +102,7 @@ private:
PhysicalBone *cache_parent_physical_bone;
#endif // _3D_DISABLED
List<uint32_t> nodes_bound;
LocalVectori<uint32_t> nodes_bound;
Bone() {
parent = -1;

View File

@ -89,6 +89,157 @@ void SpriteBase3D::_notification(int p_what) {
}
}
void SpriteBase3D::draw_texture_rect(Ref<Texture> p_texture, Rect2 p_dst_rect, Rect2 p_src_rect) {
ERR_FAIL_COND(p_texture.is_null());
Rect2 final_rect;
Rect2 final_src_rect;
if (!p_texture->get_rect_region(p_dst_rect, p_src_rect, final_rect, final_src_rect)) {
return;
}
if (final_rect.size.x == 0 || final_rect.size.y == 0) {
return;
}
// 2D: 3D plane (axes match exactly when `axis == Vector3::AXIS_Z`):
// -X+ -X+
// - +
// Y +--------+ +--------+ +--------+ Y +--------+
// + | +--+ | | | (2) | | - | 0--1 |
// | |ab| | (1) | +--+ | (3) | 3--2 | | |ab| |
// | |cd| | --> | |ab| | --> | |cd| | <==> | |cd| |
// | +--+ | | |cd| | | |ab| | | 3--2 |
// | | | +--+ | | 0--1 | | |
// +--------+ +--------+ +--------+ +--------+
// (1) Y-wise shift `final_rect` within `p_dst_rect` so after inverting Y
// axis distances between top/bottom borders will be preserved (so for
// example AtlasTextures with vertical margins will look the same in 2D/3D).
final_rect.position.y = (p_dst_rect.position.y + p_dst_rect.size.y) - ((final_rect.position.y + final_rect.size.y) - p_dst_rect.position.y);
Color color = _get_color_accum();
color.a *= get_opacity();
float pixel_size = get_pixel_size();
// (2) Order vertices (0123) bottom-top in 2D / top-bottom in 3D.
Vector2 vertices[4] = {
(final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size,
(final_rect.position + final_rect.size) * pixel_size,
(final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size,
final_rect.position * pixel_size,
};
Vector2 src_tsize = p_texture->get_size();
// Properly setup UVs for impostor textures (AtlasTexture).
Ref<AtlasTexture> atlas_tex = p_texture;
if (atlas_tex != nullptr) {
src_tsize[0] = atlas_tex->get_atlas()->get_width();
src_tsize[1] = atlas_tex->get_atlas()->get_height();
}
// (3) Assign UVs (abcd) according to the vertices order (bottom-top in 2D / top-bottom in 3D).
Vector2 uvs[4] = {
final_src_rect.position / src_tsize,
(final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize,
(final_src_rect.position + final_src_rect.size) / src_tsize,
(final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize,
};
if (is_flipped_h()) {
SWAP(uvs[0], uvs[1]);
SWAP(uvs[2], uvs[3]);
}
if (is_flipped_v()) {
SWAP(uvs[0], uvs[3]);
SWAP(uvs[1], uvs[2]);
}
Vector3 normal;
int axis = get_axis();
normal[axis] = 1.0;
Plane tangent;
if (axis == Vector3::AXIS_X) {
tangent = Plane(0, 0, -1, -1);
} else {
tangent = Plane(1, 0, 0, -1);
}
int x_axis = ((axis + 1) % 3);
int y_axis = ((axis + 2) % 3);
if (axis != Vector3::AXIS_Z) {
SWAP(x_axis, y_axis);
for (int i = 0; i < 4; i++) {
//uvs[i] = Vector2(1.0,1.0)-uvs[i];
//SWAP(vertices[i].x,vertices[i].y);
if (axis == Vector3::AXIS_Y) {
vertices[i].y = -vertices[i].y;
} else if (axis == Vector3::AXIS_X) {
vertices[i].x = -vertices[i].x;
}
}
}
AABB aabb;
// Everything except position, color, and UV is compressed
PoolVector<uint8_t>::Write write_buffer = mesh_buffer.write();
Vector2 normal_oct = VisualServer::get_singleton()->norm_to_oct(normal);
int8_t v_normal[2] = {
(int8_t)CLAMP(normal_oct.x * 127, -128, 127),
(int8_t)CLAMP(normal_oct.y * 127, -128, 127),
};
Vector2 tangent_oct = VisualServer::get_singleton()->tangent_to_oct(tangent.normal, tangent.d, false);
int8_t v_tangent[2] = {
(int8_t)CLAMP(tangent_oct.x * 127, -128, 127),
(int8_t)CLAMP(tangent_oct.y * 127, -128, 127),
};
for (int i = 0; i < 4; i++) {
Vector3 vtx;
vtx[x_axis] = vertices[i][0];
vtx[y_axis] = vertices[i][1];
if (i == 0) {
aabb.position = vtx;
aabb.size = Vector3();
} else {
aabb.expand_to(vtx);
}
float v_uv[2] = { uvs[i].x, uvs[i].y };
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_TEX_UV] + mesh_surface_offsets[VS::ARRAY_TEX_UV]], v_uv, 8);
float v_vertex[3] = { vtx.x, vtx.y, vtx.z };
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_VERTEX] + mesh_surface_offsets[VS::ARRAY_VERTEX]], &v_vertex, sizeof(float) * 3);
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_NORMAL] + mesh_surface_offsets[VS::ARRAY_NORMAL]], v_normal, 2);
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_TANGENT] + mesh_surface_offsets[VS::ARRAY_TANGENT]], v_tangent, 2);
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_COLOR] + mesh_surface_offsets[VS::ARRAY_COLOR]], color.components, 4 * 4);
}
write_buffer.release();
RID mesh = get_mesh();
VS::get_singleton()->mesh_surface_update_region(mesh, 0, 0, mesh_buffer);
VS::get_singleton()->mesh_set_custom_aabb(mesh, aabb);
set_aabb(aabb);
RID mat = SpatialMaterial::get_material_rid_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == SpatialMaterial::BILLBOARD_ENABLED, get_billboard_mode() == SpatialMaterial::BILLBOARD_FIXED_Y, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE));
VS::get_singleton()->material_set_shader(get_material(), VS::get_singleton()->material_get_shader(mat));
VS::get_singleton()->material_set_param(get_material(), "texture_albedo", p_texture->get_rid());
if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) {
VS::get_singleton()->material_set_render_priority(get_material(), get_render_priority());
}
VS::get_singleton()->instance_set_surface_material(get_instance(), 0, get_material());
}
void SpriteBase3D::set_centered(bool p_center) {
centered = p_center;
_queue_update();
@ -469,143 +620,15 @@ void Sprite3D::_draw() {
Point2 frame_offset = Point2(frame % hframes, frame / hframes);
frame_offset *= frame_size;
Point2 dest_offset = get_offset();
Point2 dst_offset = get_offset();
if (is_centered()) {
dest_offset -= frame_size / 2;
dst_offset -= frame_size / 2;
}
Rect2 src_rect(base_rect.position + frame_offset, frame_size);
Rect2 final_dst_rect(dest_offset, frame_size);
Rect2 final_rect;
Rect2 final_src_rect;
if (!texture->get_rect_region(final_dst_rect, src_rect, final_rect, final_src_rect)) {
return;
}
Rect2 dst_rect(dst_offset, frame_size);
if (final_rect.size.x == 0 || final_rect.size.y == 0) {
return;
}
Color color = _get_color_accum();
color.a *= get_opacity();
float pixel_size = get_pixel_size();
Vector2 vertices[4] = {
(final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size,
(final_rect.position + final_rect.size) * pixel_size,
(final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size,
final_rect.position * pixel_size,
};
Vector2 src_tsize = tsize;
// Properly setup UVs for impostor textures (AtlasTexture).
Ref<AtlasTexture> atlas_tex = texture;
if (atlas_tex != nullptr) {
src_tsize[0] = atlas_tex->get_atlas()->get_width();
src_tsize[1] = atlas_tex->get_atlas()->get_height();
}
Vector2 uvs[4] = {
final_src_rect.position / src_tsize,
(final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize,
(final_src_rect.position + final_src_rect.size) / src_tsize,
(final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize,
};
if (is_flipped_h()) {
SWAP(uvs[0], uvs[1]);
SWAP(uvs[2], uvs[3]);
}
if (is_flipped_v()) {
SWAP(uvs[0], uvs[3]);
SWAP(uvs[1], uvs[2]);
}
Vector3 normal;
int axis = get_axis();
normal[axis] = 1.0;
Plane tangent;
if (axis == Vector3::AXIS_X) {
tangent = Plane(0, 0, -1, 1);
} else {
tangent = Plane(1, 0, 0, 1);
}
int x_axis = ((axis + 1) % 3);
int y_axis = ((axis + 2) % 3);
if (axis != Vector3::AXIS_Z) {
SWAP(x_axis, y_axis);
for (int i = 0; i < 4; i++) {
//uvs[i] = Vector2(1.0,1.0)-uvs[i];
//SWAP(vertices[i].x,vertices[i].y);
if (axis == Vector3::AXIS_Y) {
vertices[i].y = -vertices[i].y;
} else if (axis == Vector3::AXIS_X) {
vertices[i].x = -vertices[i].x;
}
}
}
AABB aabb;
// Everything except position, color, and UV is compressed
PoolVector<uint8_t>::Write write_buffer = mesh_buffer.write();
Vector2 normal_oct = VisualServer::get_singleton()->norm_to_oct(normal);
int8_t v_normal[2] = {
(int8_t)CLAMP(normal_oct.x * 127, -128, 127),
(int8_t)CLAMP(normal_oct.y * 127, -128, 127),
};
Vector2 tangent_oct = VisualServer::get_singleton()->tangent_to_oct(tangent.normal, tangent.d, false);
int8_t v_tangent[2] = {
(int8_t)CLAMP(tangent_oct.x * 127, -128, 127),
(int8_t)CLAMP(tangent_oct.y * 127, -128, 127),
};
for (int i = 0; i < 4; i++) {
Vector3 vtx;
vtx[x_axis] = vertices[i][0];
vtx[y_axis] = vertices[i][1];
if (i == 0) {
aabb.position = vtx;
aabb.size = Vector3();
} else {
aabb.expand_to(vtx);
}
float v_uv[2] = { uvs[i].x, uvs[i].y };
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_TEX_UV] + mesh_surface_offsets[VS::ARRAY_TEX_UV]], v_uv, 8);
float v_vertex[3] = { vtx.x, vtx.y, vtx.z };
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_VERTEX] + mesh_surface_offsets[VS::ARRAY_VERTEX]], &v_vertex, sizeof(float) * 3);
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_NORMAL] + mesh_surface_offsets[VS::ARRAY_NORMAL]], v_normal, 2);
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_TANGENT] + mesh_surface_offsets[VS::ARRAY_TANGENT]], v_tangent, 2);
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_COLOR] + mesh_surface_offsets[VS::ARRAY_COLOR]], color.components, 4 * 4);
}
write_buffer.release();
RID mesh = get_mesh();
VS::get_singleton()->mesh_surface_update_region(mesh, 0, 0, mesh_buffer);
VS::get_singleton()->mesh_set_custom_aabb(mesh, aabb);
set_aabb(aabb);
RID mat = SpatialMaterial::get_material_rid_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == SpatialMaterial::BILLBOARD_ENABLED, get_billboard_mode() == SpatialMaterial::BILLBOARD_FIXED_Y, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE));
VS::get_singleton()->material_set_shader(get_material(), VS::get_singleton()->material_get_shader(mat));
VS::get_singleton()->material_set_param(get_material(), "texture_albedo", texture->get_rid());
if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) {
VS::get_singleton()->material_set_render_priority(get_material(), get_render_priority());
}
VS::get_singleton()->instance_set_surface_material(get_instance(), 0, get_material());
draw_texture_rect(texture, dst_rect, src_rect);
}
void Sprite3D::set_texture(const Ref<Texture> &p_texture) {
@ -822,136 +845,7 @@ void AnimatedSprite3D::_draw() {
Rect2 dst_rect(ofs, tsize);
Rect2 final_rect;
Rect2 final_src_rect;
if (!texture->get_rect_region(dst_rect, src_rect, final_rect, final_src_rect)) {
return;
}
if (final_rect.size.x == 0 || final_rect.size.y == 0) {
return;
}
Color color = _get_color_accum();
color.a *= get_opacity();
float pixel_size = get_pixel_size();
Vector2 vertices[4] = {
(final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size,
(final_rect.position + final_rect.size) * pixel_size,
(final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size,
final_rect.position * pixel_size,
};
Vector2 src_tsize = tsize;
// Properly setup UVs for impostor textures (AtlasTexture).
Ref<AtlasTexture> atlas_tex = texture;
if (atlas_tex != nullptr) {
src_tsize[0] = atlas_tex->get_atlas()->get_width();
src_tsize[1] = atlas_tex->get_atlas()->get_height();
}
Vector2 uvs[4] = {
final_src_rect.position / src_tsize,
(final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize,
(final_src_rect.position + final_src_rect.size) / src_tsize,
(final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize,
};
if (is_flipped_h()) {
SWAP(uvs[0], uvs[1]);
SWAP(uvs[2], uvs[3]);
}
if (is_flipped_v()) {
SWAP(uvs[0], uvs[3]);
SWAP(uvs[1], uvs[2]);
}
Vector3 normal;
int axis = get_axis();
normal[axis] = 1.0;
Plane tangent;
if (axis == Vector3::AXIS_X) {
tangent = Plane(0, 0, -1, -1);
} else {
tangent = Plane(1, 0, 0, -1);
}
int x_axis = ((axis + 1) % 3);
int y_axis = ((axis + 2) % 3);
if (axis != Vector3::AXIS_Z) {
SWAP(x_axis, y_axis);
for (int i = 0; i < 4; i++) {
//uvs[i] = Vector2(1.0,1.0)-uvs[i];
//SWAP(vertices[i].x,vertices[i].y);
if (axis == Vector3::AXIS_Y) {
vertices[i].y = -vertices[i].y;
} else if (axis == Vector3::AXIS_X) {
vertices[i].x = -vertices[i].x;
}
}
}
AABB aabb;
// Everything except position, color, and UV is compressed
PoolVector<uint8_t>::Write write_buffer = mesh_buffer.write();
Vector2 normal_oct = VisualServer::get_singleton()->norm_to_oct(normal);
int8_t v_normal[2] = {
(int8_t)CLAMP(normal_oct.x * 127, -128, 127),
(int8_t)CLAMP(normal_oct.y * 127, -128, 127),
};
Vector2 tangent_oct = VisualServer::get_singleton()->tangent_to_oct(tangent.normal, tangent.d, false);
int8_t v_tangent[2] = {
(int8_t)CLAMP(tangent_oct.x * 127, -128, 127),
(int8_t)CLAMP(tangent_oct.y * 127, -128, 127),
};
for (int i = 0; i < 4; i++) {
Vector3 vtx;
vtx[x_axis] = vertices[i][0];
vtx[y_axis] = vertices[i][1];
if (i == 0) {
aabb.position = vtx;
aabb.size = Vector3();
} else {
aabb.expand_to(vtx);
}
float v_uv[2] = { uvs[i].x, uvs[i].y };
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_TEX_UV] + mesh_surface_offsets[VS::ARRAY_TEX_UV]], v_uv, 8);
float v_vertex[3] = { vtx.x, vtx.y, vtx.z };
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_VERTEX] + mesh_surface_offsets[VS::ARRAY_VERTEX]], &v_vertex, sizeof(float) * 3);
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_NORMAL] + mesh_surface_offsets[VS::ARRAY_NORMAL]], v_normal, 2);
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_TANGENT] + mesh_surface_offsets[VS::ARRAY_TANGENT]], v_tangent, 2);
memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_COLOR] + mesh_surface_offsets[VS::ARRAY_COLOR]], color.components, 4 * 4);
}
write_buffer.release();
RID mesh = get_mesh();
VS::get_singleton()->mesh_surface_update_region(mesh, 0, 0, mesh_buffer);
VS::get_singleton()->mesh_set_custom_aabb(mesh, aabb);
set_aabb(aabb);
RID mat = SpatialMaterial::get_material_rid_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == SpatialMaterial::BILLBOARD_ENABLED, get_billboard_mode() == SpatialMaterial::BILLBOARD_FIXED_Y, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE));
VS::get_singleton()->material_set_shader(get_material(), VS::get_singleton()->material_get_shader(mat));
VS::get_singleton()->material_set_param(get_material(), "texture_albedo", texture->get_rid());
if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) {
VS::get_singleton()->material_set_render_priority(get_material(), get_render_priority());
}
VS::get_singleton()->instance_set_surface_material(get_instance(), 0, get_material());
draw_texture_rect(texture, dst_rect, src_rect);
}
void AnimatedSprite3D::_validate_property(PropertyInfo &property) const {

View File

@ -94,6 +94,7 @@ protected:
void _notification(int p_what);
static void _bind_methods();
virtual void _draw() = 0;
void draw_texture_rect(Ref<Texture> p_texture, Rect2 p_dst_rect, Rect2 p_src_rect);
_FORCE_INLINE_ void set_aabb(const AABB &p_aabb) { aabb = p_aabb; }
_FORCE_INLINE_ RID &get_mesh() { return mesh; }
_FORCE_INLINE_ RID &get_material() { return material; }

View File

@ -1001,6 +1001,12 @@ void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, float p_delta_
}
}
void RichTextLabel::_validate_property(PropertyInfo &p_property) const {
if (use_bbcode && p_property.name == "text") {
p_property.usage &= ~PROPERTY_USAGE_EDITOR;
}
}
void RichTextLabel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_MOUSE_EXIT: {
@ -2726,6 +2732,7 @@ void RichTextLabel::set_use_bbcode(bool p_enable) {
}
use_bbcode = p_enable;
set_bbcode(bbcode);
property_list_changed_notify();
}
bool RichTextLabel::is_using_bbcode() const {

View File

@ -414,6 +414,7 @@ private:
bool fit_content_height;
protected:
virtual void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
public:

View File

@ -430,16 +430,16 @@ void TreeItem::remove_child(TreeItem *p_item) {
*c = (*c)->next;
aux->parent = nullptr;
if (tree) {
tree->update();
}
return;
}
c = &(*c)->next;
}
if (tree) {
tree->update();
}
ERR_FAIL();
}

View File

@ -1458,12 +1458,23 @@ Node *Node::get_node_or_null(const NodePath &p_path) const {
Node *Node::get_node(const NodePath &p_path) const {
Node *node = get_node_or_null(p_path);
if (unlikely(!node)) {
// Try to get a clear description of this node in the error message.
String desc;
if (is_inside_tree()) {
desc = get_path();
} else {
desc = get_name();
if (desc.empty()) {
desc = get_class();
}
}
if (p_path.is_absolute()) {
ERR_FAIL_V_MSG(nullptr,
vformat("(Node not found: \"%s\" (absolute path attempted from \"%s\").)", p_path, get_path()));
vformat("(Node not found: \"%s\" (absolute path attempted from \"%s\").)", p_path, desc));
} else {
ERR_FAIL_V_MSG(nullptr,
vformat("(Node not found: \"%s\" (relative to \"%s\").)", p_path, get_path()));
vformat("(Node not found: \"%s\" (relative to \"%s\").)", p_path, desc));
}
}

View File

@ -2303,9 +2303,11 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
Ref<InputEventScreenTouch> touch_event = p_event;
if (touch_event.is_valid()) {
Size2 pos = touch_event->get_position();
const int touch_index = touch_event->get_index();
if (touch_event->is_pressed()) {
Control *over = _gui_find_control(pos);
if (over) {
gui.touch_focus[touch_index] = over->get_instance_id();
if (!gui.modal_stack.empty()) {
Control *top = gui.modal_stack.back()->get();
if (over != top && !top->is_a_parent_of(over)) {
@ -2325,14 +2327,22 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
set_input_as_handled();
return;
}
} else if (touch_event->get_index() == 0 && gui.last_mouse_focus) {
if (gui.last_mouse_focus->can_process()) {
} else {
ObjectID control_id = gui.touch_focus[touch_index];
Control *over = Object::cast_to<Control>(ObjectDB::get_instance(control_id));
if (over && over->can_process()) {
touch_event = touch_event->xformed_by(Transform2D()); //make a copy
touch_event->set_position(gui.focus_inv_xform.xform(pos));
if (over == gui.last_mouse_focus) {
pos = gui.focus_inv_xform.xform(pos);
} else {
pos = over->get_global_transform_with_canvas().affine_inverse().xform(pos);
}
touch_event->set_position(pos);
_gui_call_input(gui.last_mouse_focus, touch_event);
_gui_call_input(over, touch_event);
}
set_input_as_handled();
gui.touch_focus.erase(touch_index);
return;
}
}
@ -2364,7 +2374,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
Ref<InputEventScreenDrag> drag_event = p_event;
if (drag_event.is_valid()) {
Control *over = gui.mouse_focus;
const int drag_event_index = drag_event->get_index();
ObjectID control_id = gui.touch_focus[drag_event_index];
Control *over = Object::cast_to<Control>(ObjectDB::get_instance(control_id));
if (!over) {
over = _gui_find_control(drag_event->get_position());
}

View File

@ -295,6 +295,7 @@ private:
// info used when this is a window
bool key_event_accepted;
Map<int, ObjectID> touch_focus;
Control *mouse_focus;
Control *last_mouse_focus;
Control *mouse_click_grabber;

View File

@ -167,8 +167,10 @@ static bool _collect_inheritance_chain(const Ref<SceneState> &p_state, const Nod
state = state->get_base_scene_state();
}
for (int i = inheritance_states.size() - 1; i >= 0; --i) {
r_states_stack.push_back(inheritance_states[i]);
if (inheritance_states.size() > 0) {
for (int i = inheritance_states.size() - 1; i >= 0; --i) {
r_states_stack.push_back(inheritance_states[i]);
}
}
return found;
@ -212,10 +214,12 @@ Vector<SceneState::PackState> PropertyUtils::get_node_states_stack(const Node *p
{
states_stack_ret.resize(states_stack.size());
_FastPackState *ps = states_stack.ptr();
for (int i = states_stack.size() - 1; i >= 0; --i) {
states_stack_ret.write[i].state.reference_ptr(ps->state);
states_stack_ret.write[i].node = ps->node;
++ps;
if (states_stack.size() > 0) {
for (int i = states_stack.size() - 1; i >= 0; --i) {
states_stack_ret.write[i].state.reference_ptr(ps->state);
states_stack_ret.write[i].node = ps->node;
++ps;
}
}
}
return states_stack_ret;

View File

@ -774,6 +774,7 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto
thisrow = 0;
prevrow = 0;
const real_t side_normal_y = (bottom_radius - top_radius) / height;
for (j = 0; j <= (rings + 1); j++) {
v = j;
v /= (rings + 1);
@ -792,7 +793,7 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto
Vector3 p = Vector3(x * radius, y, z * radius);
points.push_back(p);
normals.push_back(Vector3(x, 0.0, z));
normals.push_back(Vector3(x, side_normal_y, z).normalized());
ADD_TANGENT(z, 0.0, -x, 1.0)
uvs.push_back(Vector2(u, v * 0.5));
point++;
@ -1858,24 +1859,25 @@ void TextMesh::_create_mesh_array(Array &p_arr) const {
if ((c & 0xfffffc00) == 0xdc00) { // skip trail surrogate.
continue;
}
if (utf32_char >= 0x20) {
_generate_glyph_mesh_data(utf32_char, font, c, n);
GlyphMeshData &gl_data = cache[utf32_char];
_generate_glyph_mesh_data(utf32_char, font, c, n);
GlyphMeshData &gl_data = cache[utf32_char];
p_size += gl_data.triangles.size() * ((has_depth) ? 2 : 1);
i_size += gl_data.triangles.size() * ((has_depth) ? 2 : 1);
p_size += gl_data.triangles.size() * ((has_depth) ? 2 : 1);
i_size += gl_data.triangles.size() * ((has_depth) ? 2 : 1);
if (has_depth) {
for (int j = 0; j < gl_data.contours.size(); j++) {
p_size += gl_data.contours[j].size() * 4;
i_size += gl_data.contours[j].size() * 6;
if (has_depth) {
for (int j = 0; j < gl_data.contours.size(); j++) {
p_size += gl_data.contours[j].size() * 4;
i_size += gl_data.contours[j].size() * 6;
}
}
}
min_p.x = MIN(gl_data.min_p.x + offset_pre.x, min_p.x);
min_p.y = MIN(gl_data.min_p.y + offset_pre.y, min_p.y);
max_p.x = MAX(gl_data.max_p.x + offset_pre.x, max_p.x);
max_p.y = MAX(gl_data.max_p.y + offset_pre.y, max_p.y);
min_p.x = MIN(gl_data.min_p.x + offset_pre.x, min_p.x);
min_p.y = MIN(gl_data.min_p.y + offset_pre.y, min_p.y);
max_p.x = MAX(gl_data.max_p.x + offset_pre.x, max_p.x);
max_p.y = MAX(gl_data.max_p.y + offset_pre.y, max_p.y);
}
offset_pre.x += font->get_char_size(c, n).x * pixel_size;
}
@ -1912,97 +1914,99 @@ void TextMesh::_create_mesh_array(Array &p_arr) const {
if ((c & 0xfffffc00) == 0xdc00) { // skip trail surrogate.
continue;
}
_generate_glyph_mesh_data(utf32_char, font, c, n);
GlyphMeshData &gl_data = cache[utf32_char];
if (utf32_char >= 0x20) {
_generate_glyph_mesh_data(utf32_char, font, c, n);
GlyphMeshData &gl_data = cache[utf32_char];
int64_t ts = gl_data.triangles.size();
const Vector2 *ts_ptr = gl_data.triangles.ptr();
int64_t ts = gl_data.triangles.size();
const Vector2 *ts_ptr = gl_data.triangles.ptr();
for (int k = 0; k < ts; k += 3) {
// Add front face.
for (int l = 0; l < 3; l++) {
Vector3 point = Vector3(ts_ptr[k + l].x + offset.x, -ts_ptr[k + l].y + offset.y, depth / 2.0);
vertices_ptr[p_idx] = point;
normals_ptr[p_idx] = Vector3(0.0, 0.0, 1.0);
if (has_depth) {
uvs_ptr[p_idx] = Vector2(Math::range_lerp(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(point.y, -min_p.y, -max_p.y, real_t(0.0), real_t(0.4)));
} else {
uvs_ptr[p_idx] = Vector2(Math::range_lerp(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(point.y, -min_p.y, -max_p.y, real_t(0.0), real_t(1.0)));
}
tangents_ptr[p_idx * 4 + 0] = 1.0;
tangents_ptr[p_idx * 4 + 1] = 0.0;
tangents_ptr[p_idx * 4 + 2] = 0.0;
tangents_ptr[p_idx * 4 + 3] = 1.0;
indices_ptr[i_idx++] = p_idx;
p_idx++;
}
if (has_depth) {
// Add back face.
for (int l = 2; l >= 0; l--) {
Vector3 point = Vector3(ts_ptr[k + l].x + offset.x, -ts_ptr[k + l].y + offset.y, -depth / 2.0);
for (int k = 0; k < ts; k += 3) {
// Add front face.
for (int l = 0; l < 3; l++) {
Vector3 point = Vector3(ts_ptr[k + l].x + offset.x, -ts_ptr[k + l].y + offset.y, depth / 2.0);
vertices_ptr[p_idx] = point;
normals_ptr[p_idx] = Vector3(0.0, 0.0, -1.0);
uvs_ptr[p_idx] = Vector2(Math::range_lerp(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(point.y, -min_p.y, -max_p.y, real_t(0.4), real_t(0.8)));
tangents_ptr[p_idx * 4 + 0] = -1.0;
normals_ptr[p_idx] = Vector3(0.0, 0.0, 1.0);
if (has_depth) {
uvs_ptr[p_idx] = Vector2(Math::range_lerp(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(point.y, -min_p.y, -max_p.y, real_t(0.0), real_t(0.4)));
} else {
uvs_ptr[p_idx] = Vector2(Math::range_lerp(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(point.y, -min_p.y, -max_p.y, real_t(0.0), real_t(1.0)));
}
tangents_ptr[p_idx * 4 + 0] = 1.0;
tangents_ptr[p_idx * 4 + 1] = 0.0;
tangents_ptr[p_idx * 4 + 2] = 0.0;
tangents_ptr[p_idx * 4 + 3] = 1.0;
indices_ptr[i_idx++] = p_idx;
p_idx++;
}
if (has_depth) {
// Add back face.
for (int l = 2; l >= 0; l--) {
Vector3 point = Vector3(ts_ptr[k + l].x + offset.x, -ts_ptr[k + l].y + offset.y, -depth / 2.0);
vertices_ptr[p_idx] = point;
normals_ptr[p_idx] = Vector3(0.0, 0.0, -1.0);
uvs_ptr[p_idx] = Vector2(Math::range_lerp(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(point.y, -min_p.y, -max_p.y, real_t(0.4), real_t(0.8)));
tangents_ptr[p_idx * 4 + 0] = -1.0;
tangents_ptr[p_idx * 4 + 1] = 0.0;
tangents_ptr[p_idx * 4 + 2] = 0.0;
tangents_ptr[p_idx * 4 + 3] = 1.0;
indices_ptr[i_idx++] = p_idx;
p_idx++;
}
}
}
}
// Add sides.
if (has_depth) {
for (int k = 0; k < gl_data.contours.size(); k++) {
int64_t ps = gl_data.contours[k].size();
const ContourPoint *ps_ptr = gl_data.contours[k].ptr();
const ContourInfo &ps_info = gl_data.contours_info[k];
real_t length = 0.0;
for (int l = 0; l < ps; l++) {
int prev = (l == 0) ? (ps - 1) : (l - 1);
int next = (l + 1 == ps) ? 0 : (l + 1);
Vector2 d1;
Vector2 d2 = (ps_ptr[next].point - ps_ptr[l].point).normalized();
if (ps_ptr[l].sharp) {
d1 = d2;
} else {
d1 = (ps_ptr[l].point - ps_ptr[prev].point).normalized();
}
real_t seg_len = (ps_ptr[next].point - ps_ptr[l].point).length();
Vector3 quad_faces[4] = {
Vector3(ps_ptr[l].point.x + offset.x, -ps_ptr[l].point.y + offset.y, -depth / 2.0),
Vector3(ps_ptr[next].point.x + offset.x, -ps_ptr[next].point.y + offset.y, -depth / 2.0),
Vector3(ps_ptr[l].point.x + offset.x, -ps_ptr[l].point.y + offset.y, depth / 2.0),
Vector3(ps_ptr[next].point.x + offset.x, -ps_ptr[next].point.y + offset.y, depth / 2.0),
};
for (int m = 0; m < 4; m++) {
const Vector2 &d = ((m % 2) == 0) ? d1 : d2;
real_t u_pos = ((m % 2) == 0) ? length : length + seg_len;
vertices_ptr[p_idx + m] = quad_faces[m];
normals_ptr[p_idx + m] = Vector3(d.y, d.x, 0.0);
if (m < 2) {
uvs_ptr[p_idx + m] = Vector2(Math::range_lerp(u_pos, 0, ps_info.length, real_t(0.0), real_t(1.0)), (ps_info.ccw) ? 0.8 : 0.9);
// Add sides.
if (has_depth) {
for (int k = 0; k < gl_data.contours.size(); k++) {
int64_t ps = gl_data.contours[k].size();
const ContourPoint *ps_ptr = gl_data.contours[k].ptr();
const ContourInfo &ps_info = gl_data.contours_info[k];
real_t length = 0.0;
for (int l = 0; l < ps; l++) {
int prev = (l == 0) ? (ps - 1) : (l - 1);
int next = (l + 1 == ps) ? 0 : (l + 1);
Vector2 d1;
Vector2 d2 = (ps_ptr[next].point - ps_ptr[l].point).normalized();
if (ps_ptr[l].sharp) {
d1 = d2;
} else {
uvs_ptr[p_idx + m] = Vector2(Math::range_lerp(u_pos, 0, ps_info.length, real_t(0.0), real_t(1.0)), (ps_info.ccw) ? 0.9 : 1.0);
d1 = (ps_ptr[l].point - ps_ptr[prev].point).normalized();
}
tangents_ptr[(p_idx + m) * 4 + 0] = d.x;
tangents_ptr[(p_idx + m) * 4 + 1] = -d.y;
tangents_ptr[(p_idx + m) * 4 + 2] = 0.0;
tangents_ptr[(p_idx + m) * 4 + 3] = 1.0;
real_t seg_len = (ps_ptr[next].point - ps_ptr[l].point).length();
Vector3 quad_faces[4] = {
Vector3(ps_ptr[l].point.x + offset.x, -ps_ptr[l].point.y + offset.y, -depth / 2.0),
Vector3(ps_ptr[next].point.x + offset.x, -ps_ptr[next].point.y + offset.y, -depth / 2.0),
Vector3(ps_ptr[l].point.x + offset.x, -ps_ptr[l].point.y + offset.y, depth / 2.0),
Vector3(ps_ptr[next].point.x + offset.x, -ps_ptr[next].point.y + offset.y, depth / 2.0),
};
for (int m = 0; m < 4; m++) {
const Vector2 &d = ((m % 2) == 0) ? d1 : d2;
real_t u_pos = ((m % 2) == 0) ? length : length + seg_len;
vertices_ptr[p_idx + m] = quad_faces[m];
normals_ptr[p_idx + m] = Vector3(d.y, d.x, 0.0);
if (m < 2) {
uvs_ptr[p_idx + m] = Vector2(Math::range_lerp(u_pos, 0, ps_info.length, real_t(0.0), real_t(1.0)), (ps_info.ccw) ? 0.8 : 0.9);
} else {
uvs_ptr[p_idx + m] = Vector2(Math::range_lerp(u_pos, 0, ps_info.length, real_t(0.0), real_t(1.0)), (ps_info.ccw) ? 0.9 : 1.0);
}
tangents_ptr[(p_idx + m) * 4 + 0] = d.x;
tangents_ptr[(p_idx + m) * 4 + 1] = -d.y;
tangents_ptr[(p_idx + m) * 4 + 2] = 0.0;
tangents_ptr[(p_idx + m) * 4 + 3] = 1.0;
}
indices_ptr[i_idx++] = p_idx;
indices_ptr[i_idx++] = p_idx + 1;
indices_ptr[i_idx++] = p_idx + 2;
indices_ptr[i_idx++] = p_idx + 1;
indices_ptr[i_idx++] = p_idx + 3;
indices_ptr[i_idx++] = p_idx + 2;
length += seg_len;
p_idx += 4;
}
indices_ptr[i_idx++] = p_idx;
indices_ptr[i_idx++] = p_idx + 1;
indices_ptr[i_idx++] = p_idx + 2;
indices_ptr[i_idx++] = p_idx + 1;
indices_ptr[i_idx++] = p_idx + 3;
indices_ptr[i_idx++] = p_idx + 2;
length += seg_len;
p_idx += 4;
}
}
}

View File

@ -44,6 +44,12 @@ void Body2DSW::update_inertias() {
switch (mode) {
case Physics2DServer::BODY_MODE_RIGID: {
if (mass) {
_inv_mass = 1.0 / mass;
} else {
_inv_mass = 0;
}
if (user_inertia) {
_inv_inertia = inertia > 0 ? (1.0 / inertia) : 0;
break;
@ -78,13 +84,6 @@ void Body2DSW::update_inertias() {
}
_inv_inertia = inertia > 0 ? (1.0 / inertia) : 0;
if (mass) {
_inv_mass = 1.0 / mass;
} else {
_inv_mass = 0;
}
} break;
case Physics2DServer::BODY_MODE_KINEMATIC:
case Physics2DServer::BODY_MODE_STATIC: {

View File

@ -505,6 +505,9 @@ void RasterizerStorage::_multimesh_add_to_interpolation_lists(RID p_multimesh, M
void RasterizerStorage::multimesh_set_as_bulk_array_interpolated(RID p_multimesh, const PoolVector<float> &p_array, const PoolVector<float> &p_array_prev) {
MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
if (mmi) {
ERR_FAIL_COND_MSG(p_array.size() != mmi->_data_curr.size(), vformat("Array for current frame should have %d elements, got %d instead.", mmi->_data_curr.size(), p_array.size()));
ERR_FAIL_COND_MSG(p_array_prev.size() != mmi->_data_prev.size(), vformat("Array for previous frame should have %d elements, got %d instead.", mmi->_data_prev.size(), p_array_prev.size()));
// We are assuming that mmi->interpolated is the case,
// (can possibly assert this?)
// even if this flag hasn't been set - just calling this function suggests
@ -528,6 +531,8 @@ void RasterizerStorage::multimesh_set_as_bulk_array(RID p_multimesh, const PoolV
MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
if (mmi) {
if (mmi->interpolated) {
ERR_FAIL_COND_MSG(p_array.size() != mmi->_data_curr.size(), vformat("Array should have %d elements, got %d instead.", mmi->_data_curr.size(), p_array.size()));
mmi->_data_curr = p_array;
_multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)

View File

@ -926,6 +926,8 @@ String ShaderLanguage::get_datatype_name(DataType p_type) {
return "samplerExternalOES";
case TYPE_STRUCT:
return "struct";
default: {
}
}
return "";
@ -2701,6 +2703,8 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C
break;
case ShaderLanguage::TYPE_VOID:
break;
default: {
}
}
return value;
}
@ -2786,8 +2790,18 @@ ShaderLanguage::DataType ShaderLanguage::get_scalar_type(DataType p_type) {
TYPE_INT,
TYPE_UINT,
TYPE_FLOAT,
TYPE_INT,
TYPE_UINT,
TYPE_FLOAT,
TYPE_INT,
TYPE_UINT,
TYPE_FLOAT,
TYPE_FLOAT,
TYPE_VOID,
};
static_assert(sizeof(scalar_types) / sizeof(*scalar_types) == TYPE_MAX, "get_scalar_type must be updated if DataType is updated");
return scalar_types[p_type];
}
@ -2817,8 +2831,18 @@ int ShaderLanguage::get_cardinality(DataType p_type) {
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
};
static_assert(sizeof(cardinality_table) / sizeof(*cardinality_table) == TYPE_MAX, "get_cardinality must be updated if DataType is updated");
return cardinality_table[p_type];
}

View File

@ -210,6 +210,7 @@ public:
TYPE_SAMPLERCUBE,
TYPE_SAMPLEREXT,
TYPE_STRUCT,
TYPE_MAX,
};
enum DataPrecision {

View File

@ -399,7 +399,7 @@ Collection of single-file libraries used in Godot components.
## nanosvg
- Upstream: https://github.com/memononen/nanosvg
- Version: git (bd16c4e6b2842e1f0286dc374d21f85c659862e5, 2022)
- Version: git (f0a3e1034dd22e2e87e5db22401e44998383124e, 2022)
- License: zlib
Files extracted from the upstream source:
@ -494,7 +494,7 @@ Files extracted from upstream source:
## recastnavigation
- Upstream: https://github.com/recastnavigation/recastnavigation
- Version: git (5a870d427e47abd4a8e4ce58a95582ec049434d5, 2022)
- Version: git (4fef0446609b23d6ac180ed822817571525528a1, 2022)
- License: zlib
Files extracted from upstream source:

View File

@ -610,7 +610,7 @@ static void nsvg__curveBounds(float* bounds, float* curve)
}
}
static NSVGparser* nsvg__createParser()
static NSVGparser* nsvg__createParser(void)
{
NSVGparser* p;
p = (NSVGparser*)malloc(sizeof(NSVGparser));
@ -1194,6 +1194,19 @@ static const char* nsvg__parseNumber(const char* s, char* it, const int size)
return s;
}
static const char* nsvg__getNextPathItemWhenArcFlag(const char* s, char* it)
{
it[0] = '\0';
while (*s && (nsvg__isspace(*s) || *s == ',')) s++;
if (!*s) return s;
if (*s == '0' || *s == '1') {
it[0] = *s++;
it[1] = '\0';
return s;
}
return s;
}
static const char* nsvg__getNextPathItem(const char* s, char* it)
{
it[0] = '\0';
@ -2279,7 +2292,11 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr)
nargs = 0;
while (*s) {
s = nsvg__getNextPathItem(s, item);
item[0] = '\0';
if ((cmd == 'A' || cmd == 'a') && (nargs == 3 || nargs == 4))
s = nsvg__getNextPathItemWhenArcFlag(s, item);
if (!*item)
s = nsvg__getNextPathItem(s, item);
if (!*item) break;
if (cmd != '\0' && nsvg__isCoordinate(item)) {
if (nargs < 10)

View File

@ -49,7 +49,7 @@ typedef struct NSVGrasterizer NSVGrasterizer;
*/
// Allocated rasterizer context.
NSVGrasterizer* nsvgCreateRasterizer();
NSVGrasterizer* nsvgCreateRasterizer(void);
// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha)
// r - pointer to rasterizer context
@ -150,7 +150,7 @@ struct NSVGrasterizer
int width, height, stride;
};
NSVGrasterizer* nsvgCreateRasterizer()
NSVGrasterizer* nsvgCreateRasterizer(void)
{
NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer));
if (r == NULL) goto error;

View File

@ -22,13 +22,16 @@
/// The value of PI used by Recast.
static const float RC_PI = 3.14159265f;
/// Used to ignore unused function parameters and silence any compiler warnings.
template<class T> void rcIgnoreUnused(const T&) { }
/// Recast log categories.
/// @see rcContext
enum rcLogCategory
{
RC_LOG_PROGRESS = 1, ///< A progress log entry.
RC_LOG_WARNING, ///< A warning log entry.
RC_LOG_ERROR, ///< An error log entry.
RC_LOG_ERROR ///< An error log entry.
};
/// Recast performance timer categories.
@ -101,7 +104,6 @@ enum rcTimerLabel
class rcContext
{
public:
/// Contructor.
/// @param[in] state TRUE if the logging and performance timers should be enabled. [Default: true]
inline rcContext(bool state = true) : m_logEnabled(state), m_timerEnabled(state) {}
@ -140,31 +142,30 @@ public:
inline int getAccumulatedTime(const rcTimerLabel label) const { return m_timerEnabled ? doGetAccumulatedTime(label) : -1; }
protected:
/// Clears all log entries.
virtual void doResetLog() {}
virtual void doResetLog();
/// Logs a message.
/// @param[in] category The category of the message.
/// @param[in] msg The formatted message.
/// @param[in] len The length of the formatted message.
virtual void doLog(const rcLogCategory /*category*/, const char* /*msg*/, const int /*len*/) {}
virtual void doLog(const rcLogCategory category, const char* msg, const int len) { rcIgnoreUnused(category); rcIgnoreUnused(msg); rcIgnoreUnused(len); }
/// Clears all timers. (Resets all to unused.)
virtual void doResetTimers() {}
/// Starts the specified performance timer.
/// @param[in] label The category of timer.
virtual void doStartTimer(const rcTimerLabel /*label*/) {}
virtual void doStartTimer(const rcTimerLabel label) { rcIgnoreUnused(label); }
/// Stops the specified performance timer.
/// @param[in] label The category of the timer.
virtual void doStopTimer(const rcTimerLabel /*label*/) {}
virtual void doStopTimer(const rcTimerLabel label) { rcIgnoreUnused(label); }
/// Returns the total accumulated time of the specified performance timer.
/// @param[in] label The category of the timer.
/// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started.
virtual int doGetAccumulatedTime(const rcTimerLabel /*label*/) const { return -1; }
virtual int doGetAccumulatedTime(const rcTimerLabel label) const { rcIgnoreUnused(label); return -1; }
/// True if logging is enabled.
bool m_logEnabled;
@ -564,7 +565,7 @@ static const int RC_AREA_BORDER = 0x20000;
enum rcBuildContoursFlags
{
RC_CONTOUR_TESS_WALL_EDGES = 0x01, ///< Tessellate solid (impassable) edges during contour simplification.
RC_CONTOUR_TESS_AREA_EDGES = 0x02, ///< Tessellate edges between areas during contour simplification.
RC_CONTOUR_TESS_AREA_EDGES = 0x02 ///< Tessellate edges between areas during contour simplification.
};
/// Applied to the region id field of contour vertices in order to extract the region id.
@ -595,11 +596,6 @@ static const int RC_NOT_CONNECTED = 0x3f;
/// @name General helper functions
/// @{
/// Used to ignore a function parameter. VS complains about unused parameters
/// and this silences the warning.
/// @param [in] _ Unused parameter
template<class T> void rcIgnoreUnused(const T&) { }
/// Swaps the values of the two parameters.
/// @param[in,out] a Value A
/// @param[in,out] b Value B
@ -996,6 +992,7 @@ void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts,
/// @ingroup recast
/// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p nverts]
/// @param[in] nverts The number of vertices in the polygon.
/// @param[in] offset How much to offset the polygon by. [Units: wu]
/// @param[out] outVerts The offset vertices (should hold up to 2 * @p nverts) [Form: (x, y, z) * return value]
/// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts.
/// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts.

View File

@ -112,7 +112,7 @@ class rcVectorBase {
typedef rcSizeType size_type;
typedef T value_type;
rcVectorBase() : m_size(0), m_cap(0), m_data(0) {};
rcVectorBase() : m_size(0), m_cap(0), m_data(0) {}
rcVectorBase(const rcVectorBase<T, H>& other) : m_size(0), m_cap(0), m_data(0) { assign(other.begin(), other.end()); }
explicit rcVectorBase(rcSizeType count) : m_size(0), m_cap(0), m_data(0) { resize(count); }
rcVectorBase(rcSizeType count, const T& value) : m_size(0), m_cap(0), m_data(0) { resize(count, value); }
@ -142,8 +142,8 @@ class rcVectorBase {
const T& front() const { rcAssert(m_size); return m_data[0]; }
T& front() { rcAssert(m_size); return m_data[0]; }
const T& back() const { rcAssert(m_size); return m_data[m_size - 1]; };
T& back() { rcAssert(m_size); return m_data[m_size - 1]; };
const T& back() const { rcAssert(m_size); return m_data[m_size - 1]; }
T& back() { rcAssert(m_size); return m_data[m_size - 1]; }
const T* data() const { return m_data; }
T* data() { return m_data; }

View File

@ -94,6 +94,11 @@ void rcContext::log(const rcLogCategory category, const char* format, ...)
doLog(category, msg, len);
}
void rcContext::doResetLog()
{
// Defined out of line to fix the weak v-tables warning
}
rcHeightfield* rcAllocHeightfield()
{
return rcNew<rcHeightfield>(RC_ALLOC_PERM);

View File

@ -566,7 +566,6 @@ static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned sho
const int nvp = mesh.nvp;
// Count number of polygons to remove.
int numRemovedVerts = 0;
int numTouchedVerts = 0;
int numRemainingEdges = 0;
for (int i = 0; i < mesh.npolys; ++i)
@ -586,7 +585,6 @@ static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned sho
}
if (numRemoved)
{
numRemovedVerts += numRemoved;
numRemainingEdges += numVerts-(numRemoved+1);
}
}

View File

@ -284,7 +284,7 @@ static unsigned short getHeight(const float fx, const float fy, const float fz,
enum EdgeValues
{
EV_UNDEF = -1,
EV_HULL = -2,
EV_HULL = -2
};
static int findEdge(const int* edges, int nedges, int s, int t)

View File

@ -264,7 +264,8 @@ static bool rasterizeTri(const float* v0, const float* v1, const float* v2,
// Calculate the footprint of the triangle on the grid's y-axis
int y0 = (int)((tmin[2] - bmin[2])*ics);
int y1 = (int)((tmax[2] - bmin[2])*ics);
y0 = rcClamp(y0, 0, h-1);
// use -1 rather than 0 to cut the polygon properly at the start of the tile
y0 = rcClamp(y0, -1, h-1);
y1 = rcClamp(y1, 0, h-1);
// Clip the triangle into all grid cells it touches.
@ -283,7 +284,7 @@ static bool rasterizeTri(const float* v0, const float* v1, const float* v2,
dividePoly(in, nvIn, inrow, &nvrow, p1, &nvIn, cz+cs, 2);
rcSwap(in, p1);
if (nvrow < 3) continue;
if (y < 0) continue;
// find the horizontal bounds in the row
float minX = inrow[0], maxX = inrow[0];
for (int i=1; i<nvrow; ++i)
@ -293,7 +294,10 @@ static bool rasterizeTri(const float* v0, const float* v1, const float* v2,
}
int x0 = (int)((minX - bmin[0])*ics);
int x1 = (int)((maxX - bmin[0])*ics);
x0 = rcClamp(x0, 0, w-1);
if (x1 < 0 || x0 >= w) {
continue;
}
x0 = rcClamp(x0, -1, w-1);
x1 = rcClamp(x1, 0, w-1);
int nv, nv2 = nvrow;
@ -305,7 +309,7 @@ static bool rasterizeTri(const float* v0, const float* v1, const float* v2,
dividePoly(inrow, nv2, p1, &nv, p2, &nv2, cx+cs, 0);
rcSwap(inrow, p2);
if (nv < 3) continue;
if (x < 0) continue;
// Calculate min and max of the span.
float smin = p1[1], smax = p1[1];
for (int i = 1; i < nv; ++i)