diff options
Diffstat (limited to 'scene')
50 files changed, 1465 insertions, 558 deletions
diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index cf2632f380..bf26ec1f20 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -727,7 +727,7 @@ void CPUParticles2D::_particles_process(double p_delta) { p.hue_rot_rand = Math::randf(); p.anim_offset_rand = Math::randf(); - real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); + real_t angle1_rad = direction.angle() + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); Vector2 rot = Vector2(Math::cos(angle1_rad), Math::sin(angle1_rad)); p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], Math::randf()); diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 72ea6541e3..cbf0d50c4e 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -464,7 +464,7 @@ void NavigationRegion2D::_notification(int p_what) { draw_line(a, b, doors_color); // Draw a circle to illustrate the margins. - real_t angle = (b - a).angle(); + real_t angle = b.angle_to_point(a); draw_arc(a, radius, angle + Math_PI / 2.0, angle - Math_PI / 2.0 + Math_TAU, 10, doors_color); draw_arc(b, radius, angle - Math_PI / 2.0, angle + Math_PI / 2.0, 10, doors_color); } diff --git a/scene/2d/physical_bone_2d.cpp b/scene/2d/physical_bone_2d.cpp index c4b2608812..48817679bc 100644 --- a/scene/2d/physical_bone_2d.cpp +++ b/scene/2d/physical_bone_2d.cpp @@ -150,27 +150,15 @@ void PhysicalBone2D::_start_physics_simulation() { return; } - // Reset to Bone2D position + // Reset to Bone2D position. _position_at_bone2d(); - // Apply the layers and masks + // Apply the layers and masks. PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); - // Apply the correct mode - RigidDynamicBody2D::Mode rigid_mode = get_mode(); - if (rigid_mode == RigidDynamicBody2D::MODE_STATIC) { - set_body_mode(PhysicsServer2D::BODY_MODE_STATIC); - } else if (rigid_mode == RigidDynamicBody2D::MODE_DYNAMIC) { - set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC); - } else if (rigid_mode == RigidDynamicBody2D::MODE_KINEMATIC) { - set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC); - } else if (rigid_mode == RigidDynamicBody2D::MODE_DYNAMIC_LOCKED) { - set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC_LOCKED); - } else { - // Default to Dynamic. - set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC); - } + // Apply the correct mode. + _apply_body_mode(); _internal_simulate_physics = true; set_physics_process_internal(true); diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index c3dc9ab92b..09bcbdbf0f 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -34,8 +34,8 @@ #include "scene/scene_string_names.h" void PhysicsBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "test_only", "safe_margin"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08)); - ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "collision", "safe_margin"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("move_and_collide", "linear_velocity", "test_only", "safe_margin"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("test_move", "from", "linear_velocity", "collision", "safe_margin"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08)); ClassDB::bind_method(D_METHOD("get_collision_exceptions"), &PhysicsBody2D::get_collision_exceptions); ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &PhysicsBody2D::add_collision_exception_with); @@ -57,7 +57,10 @@ PhysicsBody2D::~PhysicsBody2D() { Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_test_only, real_t p_margin) { PhysicsServer2D::MotionResult result; - if (move_and_collide(p_motion, result, p_margin, p_test_only)) { + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky. + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + + if (move_and_collide(p_motion * delta, result, p_margin, p_test_only)) { // Create a new instance when the cached reference is invalid or still in use in script. if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) { motion_cache.instantiate(); @@ -133,7 +136,10 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion r = const_cast<PhysicsServer2D::MotionResult *>(&r_collision->result); } - return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_margin, r); + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky. + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + + return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion * delta, p_margin, r); } TypedArray<PhysicsBody2D> PhysicsBody2D::get_collision_exceptions() { @@ -282,6 +288,12 @@ void AnimatableBody2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { last_valid_transform = get_global_transform(); + _update_kinematic_motion(); + } break; + + case NOTIFICATION_EXIT_TREE: { + set_only_update_transform_changes(false); + set_notify_local_transform(false); } break; case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { @@ -307,7 +319,6 @@ void AnimatableBody2D::_bind_methods() { AnimatableBody2D::AnimatableBody2D() : StaticBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) { - _update_kinematic_motion(); } void RigidDynamicBody2D::_body_enter_tree(ObjectID p_id) { @@ -430,7 +441,7 @@ void RigidDynamicBody2D::_body_state_changed_callback(void *p_instance, PhysicsD void RigidDynamicBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { set_block_transform_notify(true); // don't want notify (would feedback loop) - if (mode != MODE_KINEMATIC) { + if (!freeze || freeze_mode != FREEZE_MODE_KINEMATIC) { set_global_transform(p_state->get_transform()); } @@ -524,29 +535,60 @@ void RigidDynamicBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) } } -void RigidDynamicBody2D::set_mode(Mode p_mode) { - mode = p_mode; - switch (p_mode) { - case MODE_DYNAMIC: { - set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC); - } break; - case MODE_STATIC: { - set_body_mode(PhysicsServer2D::BODY_MODE_STATIC); +void RigidDynamicBody2D::_apply_body_mode() { + if (freeze) { + switch (freeze_mode) { + case FREEZE_MODE_STATIC: { + set_body_mode(PhysicsServer2D::BODY_MODE_STATIC); + } break; + case FREEZE_MODE_KINEMATIC: { + set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC); + } break; + } + } else if (lock_rotation) { + set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC_LINEAR); + } else { + set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC); + } +} - } break; - case MODE_KINEMATIC: { - set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC); +void RigidDynamicBody2D::set_lock_rotation_enabled(bool p_lock_rotation) { + if (p_lock_rotation == lock_rotation) { + return; + } - } break; - case MODE_DYNAMIC_LOCKED: { - set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC_LOCKED); + lock_rotation = p_lock_rotation; + _apply_body_mode(); +} - } break; +bool RigidDynamicBody2D::is_lock_rotation_enabled() const { + return lock_rotation; +} + +void RigidDynamicBody2D::set_freeze_enabled(bool p_freeze) { + if (p_freeze == freeze) { + return; + } + + freeze = p_freeze; + _apply_body_mode(); +} + +bool RigidDynamicBody2D::is_freeze_enabled() const { + return freeze; +} + +void RigidDynamicBody2D::set_freeze_mode(FreezeMode p_freeze_mode) { + if (p_freeze_mode == freeze_mode) { + return; } + + freeze_mode = p_freeze_mode; + _apply_body_mode(); } -RigidDynamicBody2D::Mode RigidDynamicBody2D::get_mode() const { - return mode; +RigidDynamicBody2D::FreezeMode RigidDynamicBody2D::get_freeze_mode() const { + return freeze_mode; } void RigidDynamicBody2D::set_mass(real_t p_mass) { @@ -844,17 +886,14 @@ TypedArray<String> RigidDynamicBody2D::get_configuration_warnings() const { TypedArray<String> warnings = CollisionObject2D::get_configuration_warnings(); - if ((get_mode() == MODE_DYNAMIC || get_mode() == MODE_DYNAMIC_LOCKED) && (ABS(t.elements[0].length() - 1.0) > 0.05 || ABS(t.elements[1].length() - 1.0) > 0.05)) { - warnings.push_back(TTR("Size changes to RigidDynamicBody2D (in dynamic modes) will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); + if (ABS(t.elements[0].length() - 1.0) > 0.05 || ABS(t.elements[1].length() - 1.0) > 0.05) { + warnings.push_back(TTR("Size changes to RigidDynamicBody2D will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); } return warnings; } void RigidDynamicBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_mode", "mode"), &RigidDynamicBody2D::set_mode); - ClassDB::bind_method(D_METHOD("get_mode"), &RigidDynamicBody2D::get_mode); - ClassDB::bind_method(D_METHOD("set_mass", "mass"), &RigidDynamicBody2D::set_mass); ClassDB::bind_method(D_METHOD("get_mass"), &RigidDynamicBody2D::get_mass); @@ -918,11 +957,19 @@ void RigidDynamicBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_can_sleep", "able_to_sleep"), &RigidDynamicBody2D::set_can_sleep); ClassDB::bind_method(D_METHOD("is_able_to_sleep"), &RigidDynamicBody2D::is_able_to_sleep); + ClassDB::bind_method(D_METHOD("set_lock_rotation_enabled", "lock_rotation"), &RigidDynamicBody2D::set_lock_rotation_enabled); + ClassDB::bind_method(D_METHOD("is_lock_rotation_enabled"), &RigidDynamicBody2D::is_lock_rotation_enabled); + + ClassDB::bind_method(D_METHOD("set_freeze_enabled", "freeze_mode"), &RigidDynamicBody2D::set_freeze_enabled); + ClassDB::bind_method(D_METHOD("is_freeze_enabled"), &RigidDynamicBody2D::is_freeze_enabled); + + ClassDB::bind_method(D_METHOD("set_freeze_mode", "freeze_mode"), &RigidDynamicBody2D::set_freeze_mode); + ClassDB::bind_method(D_METHOD("get_freeze_mode"), &RigidDynamicBody2D::get_freeze_mode); + ClassDB::bind_method(D_METHOD("get_colliding_bodies"), &RigidDynamicBody2D::get_colliding_bodies); GDVIRTUAL_BIND(_integrate_forces, "state"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Dynamic,Static,DynamicLocked,Kinematic"), "set_mode", "get_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater,exp"), "set_inertia", "get_inertia"); ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_center_of_mass_mode", "get_center_of_mass_mode"); @@ -936,6 +983,9 @@ void RigidDynamicBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "contact_monitor"), "set_contact_monitor", "is_contact_monitor_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sleeping"), "set_sleeping", "is_sleeping"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "can_sleep"), "set_can_sleep", "is_able_to_sleep"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "lock_rotation"), "set_lock_rotation_enabled", "is_lock_rotation_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "freeze"), "set_freeze_enabled", "is_freeze_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "freeze_mode", PROPERTY_HINT_ENUM, "Static,Kinematic"), "set_freeze_mode", "get_freeze_mode"); ADD_GROUP("Linear", "linear_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); @@ -952,10 +1002,8 @@ void RigidDynamicBody2D::_bind_methods() { ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("sleeping_state_changed")); - BIND_ENUM_CONSTANT(MODE_DYNAMIC); - BIND_ENUM_CONSTANT(MODE_STATIC); - BIND_ENUM_CONSTANT(MODE_DYNAMIC_LOCKED); - BIND_ENUM_CONSTANT(MODE_KINEMATIC); + BIND_ENUM_CONSTANT(FREEZE_MODE_STATIC); + BIND_ENUM_CONSTANT(FREEZE_MODE_KINEMATIC); BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO); BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM); @@ -1004,6 +1052,8 @@ bool CharacterBody2D::move_and_slide() { double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); Vector2 current_platform_velocity = platform_velocity; + Transform2D gt = get_global_transform(); + previous_position = gt.elements[2]; if ((on_floor || on_wall) && platform_rid.is_valid()) { bool excluded = false; @@ -1016,7 +1066,6 @@ bool CharacterBody2D::move_and_slide() { //this approach makes sure there is less delay between the actual body velocity and the one we saved PhysicsDirectBodyState2D *bs = PhysicsServer2D::get_singleton()->body_get_direct_state(platform_rid); if (bs) { - Transform2D gt = get_global_transform(); Vector2 local_position = gt.elements[2] - bs->get_transform().elements[2]; current_platform_velocity = bs->get_velocity_at_local_position(local_position); } @@ -1026,6 +1075,7 @@ bool CharacterBody2D::move_and_slide() { } motion_results.clear(); + last_motion = Vector2(); bool was_on_floor = on_floor; on_floor = false; @@ -1048,16 +1098,24 @@ bool CharacterBody2D::move_and_slide() { _move_and_slide_free(delta); } - if (!on_floor && !on_wall) { + // Compute real velocity. + real_velocity = get_position_delta() / delta; + + if (moving_platform_apply_velocity_on_leave != PLATFORM_VEL_ON_LEAVE_NEVER) { // Add last platform velocity when just left a moving platform. - linear_velocity += current_platform_velocity; + if (!on_floor && !on_wall) { + if (moving_platform_apply_velocity_on_leave == PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY && current_platform_velocity.dot(up_direction) < 0) { + current_platform_velocity = current_platform_velocity.slide(up_direction); + } + motion_velocity += current_platform_velocity; + } } return motion_results.size() > 0; } void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity) { - Vector2 motion = linear_velocity * p_delta; + Vector2 motion = motion_velocity * p_delta; Vector2 motion_slide_up = motion.slide(up_direction); Vector2 prev_floor_normal = floor_normal; @@ -1074,7 +1132,7 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo // Constant speed can be applied only the first time sliding is enabled. bool can_apply_constant_speed = sliding_enabled; bool first_slide = true; - bool vel_dir_facing_up = linear_velocity.dot(up_direction) > 0; + bool vel_dir_facing_up = motion_velocity.dot(up_direction) > 0; Vector2 last_travel; for (int iteration = 0; iteration < max_slides; ++iteration) { @@ -1083,20 +1141,20 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo Vector2 prev_position = get_global_transform().elements[2]; bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled); + last_motion = result.travel; if (collided) { motion_results.push_back(result); _set_collision_direction(result); - if (on_floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { + if (on_floor && floor_stop_on_slope && (motion_velocity.normalized() + up_direction).length() < 0.01) { Transform2D gt = get_global_transform(); - if (result.travel.length() > margin) { - gt.elements[2] -= result.travel.slide(up_direction); - } else { + if (result.travel.length() <= margin + CMP_EPSILON) { gt.elements[2] -= result.travel; } set_global_transform(gt); - linear_velocity = Vector2(); + motion_velocity = Vector2(); + last_motion = Vector2(); motion = Vector2(); break; } @@ -1111,7 +1169,7 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo // Avoid to move forward on a wall if floor_block_on_wall is true. if (p_was_on_floor && !on_floor && !vel_dir_facing_up) { // If the movement is large the body can be prevented from reaching the walls. - if (result.travel.length() <= margin) { + if (result.travel.length() <= margin + CMP_EPSILON) { // Cancels the motion. Transform2D gt = get_global_transform(); gt.elements[2] -= result.travel; @@ -1122,7 +1180,8 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo platform_layer = prev_platform_layer; platform_velocity = p_prev_platform_velocity; floor_normal = prev_floor_normal; - linear_velocity = Vector2(); + motion_velocity = Vector2(); + last_motion = Vector2(); motion = Vector2(); break; } @@ -1143,7 +1202,7 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo // Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling. else if ((sliding_enabled || !on_floor) && (!on_ceiling || slide_on_ceiling || !vel_dir_facing_up)) { Vector2 slide_motion = result.remainder.slide(result.collision_normal); - if (slide_motion.dot(linear_velocity) > 0.0) { + if (slide_motion.dot(motion_velocity) > 0.0) { motion = slide_motion; } else { motion = Vector2(); @@ -1151,10 +1210,10 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo if (slide_on_ceiling && on_ceiling) { // Apply slide only in the direction of the input motion, otherwise just stop to avoid jittering when moving against a wall. if (vel_dir_facing_up) { - linear_velocity = linear_velocity.slide(result.collision_normal); + motion_velocity = motion_velocity.slide(result.collision_normal); } else { // Avoid acceleration in slope when falling. - linear_velocity = up_direction * up_direction.dot(linear_velocity); + motion_velocity = up_direction * up_direction.dot(motion_velocity); } } } @@ -1162,7 +1221,7 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo else { motion = result.remainder; if (on_ceiling && !slide_on_ceiling && vel_dir_facing_up) { - linear_velocity = linear_velocity.slide(up_direction); + motion_velocity = motion_velocity.slide(up_direction); motion = motion.slide(up_direction); } } @@ -1196,12 +1255,12 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo // Reset the gravity accumulation when touching the ground. if (on_floor && !vel_dir_facing_up) { - linear_velocity = linear_velocity.slide(up_direction); + motion_velocity = motion_velocity.slide(up_direction); } } void CharacterBody2D::_move_and_slide_free(double p_delta) { - Vector2 motion = linear_velocity * p_delta; + Vector2 motion = motion_velocity * p_delta; platform_rid = RID(); floor_normal = Vector2(); @@ -1212,12 +1271,18 @@ void CharacterBody2D::_move_and_slide_free(double p_delta) { PhysicsServer2D::MotionResult result; bool collided = move_and_collide(motion, result, margin, false, false); + last_motion = result.travel; if (collided) { motion_results.push_back(result); _set_collision_direction(result); - if (free_mode_min_slide_angle != 0 && result.get_angle(-linear_velocity.normalized()) < free_mode_min_slide_angle + FLOOR_ANGLE_THRESHOLD) { + if (result.remainder.is_equal_approx(Vector2())) { + motion = Vector2(); + break; + } + + if (free_mode_min_slide_angle != 0 && result.get_angle(-motion_velocity.normalized()) < free_mode_min_slide_angle + FLOOR_ANGLE_THRESHOLD) { motion = Vector2(); } else if (first_slide) { Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized(); @@ -1226,27 +1291,30 @@ void CharacterBody2D::_move_and_slide_free(double p_delta) { motion = result.remainder.slide(result.collision_normal); } - if (motion.dot(linear_velocity) <= 0.0) { + if (motion.dot(motion_velocity) <= 0.0) { motion = Vector2(); } } - first_slide = false; - if (!collided || motion.is_equal_approx(Vector2())) { break; } + + first_slide = false; } } void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) { - if (Math::is_equal_approx(floor_snap_length, 0) || on_floor || !was_on_floor || vel_dir_facing_up) { + if (on_floor || !was_on_floor || vel_dir_facing_up) { return; } + // Snap by at least collision margin to keep floor state consistent. + real_t length = MAX(floor_snap_length, margin); + Transform2D gt = get_global_transform(); PhysicsServer2D::MotionResult result; - if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) { + if (move_and_collide(-up_direction * length, result, margin, true, false, true)) { bool apply = true; if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { on_floor = true; @@ -1274,12 +1342,15 @@ void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) } bool CharacterBody2D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up) { - if (Math::is_equal_approx(floor_snap_length, 0) || up_direction == Vector2() || on_floor || !was_on_floor || vel_dir_facing_up) { + if (up_direction == Vector2() || on_floor || !was_on_floor || vel_dir_facing_up) { return false; } + // Snap by at least collision margin to keep floor state consistent. + real_t length = MAX(floor_snap_length, margin); + PhysicsServer2D::MotionResult result; - if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) { + if (move_and_collide(-up_direction * length, result, margin, true, false, true)) { if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { return true; } @@ -1297,6 +1368,7 @@ void CharacterBody2D::_set_collision_direction(const PhysicsServer2D::MotionResu on_ceiling = true; } else { on_wall = true; + wall_normal = p_result.collision_normal; // Don't apply wall velocity when the collider is a CharacterBody2D. if (Object::cast_to<CharacterBody2D>(ObjectDB::get_instance(p_result.collider_id)) == nullptr) { _set_platform_data(p_result); @@ -1310,12 +1382,12 @@ void CharacterBody2D::_set_platform_data(const PhysicsServer2D::MotionResult &p_ platform_layer = PhysicsServer2D::get_singleton()->body_get_collision_layer(platform_rid); } -const Vector2 &CharacterBody2D::get_linear_velocity() const { - return linear_velocity; +const Vector2 &CharacterBody2D::get_motion_velocity() const { + return motion_velocity; } -void CharacterBody2D::set_linear_velocity(const Vector2 &p_velocity) { - linear_velocity = p_velocity; +void CharacterBody2D::set_motion_velocity(const Vector2 &p_velocity) { + motion_velocity = p_velocity; } bool CharacterBody2D::is_on_floor() const { @@ -1342,16 +1414,32 @@ bool CharacterBody2D::is_on_ceiling_only() const { return on_ceiling && !on_floor && !on_wall; } -Vector2 CharacterBody2D::get_floor_normal() const { +const Vector2 &CharacterBody2D::get_floor_normal() const { return floor_normal; } +const Vector2 &CharacterBody2D::get_wall_normal() const { + return wall_normal; +} + +const Vector2 &CharacterBody2D::get_last_motion() const { + return last_motion; +} + +Vector2 CharacterBody2D::get_position_delta() const { + return get_global_transform().elements[2] - previous_position; +} + +const Vector2 &CharacterBody2D::get_real_velocity() const { + return real_velocity; +} + real_t CharacterBody2D::get_floor_angle(const Vector2 &p_up_direction) const { ERR_FAIL_COND_V(p_up_direction == Vector2(), 0); return Math::acos(floor_normal.dot(p_up_direction)); } -Vector2 CharacterBody2D::get_platform_velocity() const { +const Vector2 &CharacterBody2D::get_platform_velocity() const { return platform_velocity; } @@ -1451,6 +1539,14 @@ CharacterBody2D::MotionMode CharacterBody2D::get_motion_mode() const { return motion_mode; } +void CharacterBody2D::set_moving_platform_apply_velocity_on_leave(MovingPlatformApplyVelocityOnLeave p_on_leave_apply_velocity) { + moving_platform_apply_velocity_on_leave = p_on_leave_apply_velocity; +} + +CharacterBody2D::MovingPlatformApplyVelocityOnLeave CharacterBody2D::get_moving_platform_apply_velocity_on_leave() const { + return moving_platform_apply_velocity_on_leave; +} + int CharacterBody2D::get_max_slides() const { return max_slides; } @@ -1511,8 +1607,8 @@ void CharacterBody2D::_notification(int p_what) { void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("move_and_slide"), &CharacterBody2D::move_and_slide); - ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &CharacterBody2D::set_linear_velocity); - ClassDB::bind_method(D_METHOD("get_linear_velocity"), &CharacterBody2D::get_linear_velocity); + ClassDB::bind_method(D_METHOD("set_motion_velocity", "motion_velocity"), &CharacterBody2D::set_motion_velocity); + ClassDB::bind_method(D_METHOD("get_motion_velocity"), &CharacterBody2D::get_motion_velocity); ClassDB::bind_method(D_METHOD("set_safe_margin", "pixels"), &CharacterBody2D::set_safe_margin); ClassDB::bind_method(D_METHOD("get_safe_margin"), &CharacterBody2D::get_safe_margin); @@ -1542,6 +1638,8 @@ void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody2D::set_up_direction); ClassDB::bind_method(D_METHOD("set_motion_mode", "mode"), &CharacterBody2D::set_motion_mode); ClassDB::bind_method(D_METHOD("get_motion_mode"), &CharacterBody2D::get_motion_mode); + ClassDB::bind_method(D_METHOD("set_moving_platform_apply_velocity_on_leave", "on_leave_apply_velocity"), &CharacterBody2D::set_moving_platform_apply_velocity_on_leave); + ClassDB::bind_method(D_METHOD("get_moving_platform_apply_velocity_on_leave"), &CharacterBody2D::get_moving_platform_apply_velocity_on_leave); ClassDB::bind_method(D_METHOD("is_on_floor"), &CharacterBody2D::is_on_floor); ClassDB::bind_method(D_METHOD("is_on_floor_only"), &CharacterBody2D::is_on_floor_only); @@ -1550,6 +1648,10 @@ void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_on_wall"), &CharacterBody2D::is_on_wall); ClassDB::bind_method(D_METHOD("is_on_wall_only"), &CharacterBody2D::is_on_wall_only); ClassDB::bind_method(D_METHOD("get_floor_normal"), &CharacterBody2D::get_floor_normal); + ClassDB::bind_method(D_METHOD("get_wall_normal"), &CharacterBody2D::get_wall_normal); + ClassDB::bind_method(D_METHOD("get_last_motion"), &CharacterBody2D::get_last_motion); + ClassDB::bind_method(D_METHOD("get_position_delta"), &CharacterBody2D::get_position_delta); + ClassDB::bind_method(D_METHOD("get_real_velocity"), &CharacterBody2D::get_real_velocity); ClassDB::bind_method(D_METHOD("get_floor_angle", "up_direction"), &CharacterBody2D::get_floor_angle, DEFVAL(Vector2(0.0, -1.0))); ClassDB::bind_method(D_METHOD("get_platform_velocity"), &CharacterBody2D::get_platform_velocity); ClassDB::bind_method(D_METHOD("get_slide_collision_count"), &CharacterBody2D::get_slide_collision_count); @@ -1557,10 +1659,11 @@ void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody2D::_get_last_slide_collision); ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "up_direction"), "set_up_direction", "get_up_direction"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_motion_velocity", "get_motion_velocity"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "up_direction"), "set_up_direction", "get_up_direction"); + ADD_GROUP("Free Mode", "free_mode_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "free_mode_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_free_mode_min_slide_angle", "get_free_mode_min_slide_angle"); ADD_GROUP("Floor", "floor_"); @@ -1568,14 +1671,19 @@ void CharacterBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_constant_speed"), "set_floor_constant_speed_enabled", "is_floor_constant_speed_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_block_on_wall"), "set_floor_block_on_wall_enabled", "is_floor_block_on_wall_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,1000,0.1"), "set_floor_snap_length", "get_floor_snap_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,32,0.1,or_greater"), "set_floor_snap_length", "get_floor_snap_length"); ADD_GROUP("Moving platform", "moving_platform"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_apply_velocity_on_leave", PROPERTY_HINT_ENUM, "Always,Upward Only,Never", PROPERTY_USAGE_DEFAULT), "set_moving_platform_apply_velocity_on_leave", "get_moving_platform_apply_velocity_on_leave"); ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_floor_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_floor_layers", "get_moving_platform_floor_layers"); ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_wall_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_wall_layers", "get_moving_platform_wall_layers"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED); BIND_ENUM_CONSTANT(MOTION_MODE_FREE); + + BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_ALWAYS); + BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY); + BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_NEVER); } void CharacterBody2D::_validate_property(PropertyInfo &property) const { diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 5d0d98a2df..92b7fd1648 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -92,7 +92,7 @@ class AnimatableBody2D : public StaticBody2D { GDCLASS(AnimatableBody2D, StaticBody2D); private: - bool sync_to_physics = false; + bool sync_to_physics = true; Transform2D last_valid_transform; @@ -117,11 +117,9 @@ class RigidDynamicBody2D : public PhysicsBody2D { GDCLASS(RigidDynamicBody2D, PhysicsBody2D); public: - enum Mode { - MODE_DYNAMIC, - MODE_STATIC, - MODE_DYNAMIC_LOCKED, - MODE_KINEMATIC, + enum FreezeMode { + FREEZE_MODE_STATIC, + FREEZE_MODE_KINEMATIC, }; enum CenterOfMassMode { @@ -137,7 +135,9 @@ public: private: bool can_sleep = true; - Mode mode = MODE_DYNAMIC; + bool lock_rotation = false; + bool freeze = false; + FreezeMode freeze_mode = FREEZE_MODE_STATIC; real_t mass = 1.0; real_t inertia = 0.0; @@ -211,9 +211,17 @@ protected: GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState2D *) + void _apply_body_mode(); + public: - void set_mode(Mode p_mode); - Mode get_mode() const; + void set_lock_rotation_enabled(bool p_lock_rotation); + bool is_lock_rotation_enabled() const; + + void set_freeze_enabled(bool p_freeze); + bool is_freeze_enabled() const; + + void set_freeze_mode(FreezeMode p_freeze_mode); + FreezeMode get_freeze_mode() const; void set_mass(real_t p_mass); real_t get_mass() const; @@ -290,7 +298,7 @@ private: void _reload_physics_characteristics(); }; -VARIANT_ENUM_CAST(RigidDynamicBody2D::Mode); +VARIANT_ENUM_CAST(RigidDynamicBody2D::FreezeMode); VARIANT_ENUM_CAST(RigidDynamicBody2D::CenterOfMassMode); VARIANT_ENUM_CAST(RigidDynamicBody2D::CCDMode); @@ -302,10 +310,15 @@ public: MOTION_MODE_GROUNDED, MOTION_MODE_FREE, }; + enum MovingPlatformApplyVelocityOnLeave { + PLATFORM_VEL_ON_LEAVE_ALWAYS, + PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY, + PLATFORM_VEL_ON_LEAVE_NEVER, + }; bool move_and_slide(); - const Vector2 &get_linear_velocity() const; - void set_linear_velocity(const Vector2 &p_velocity); + const Vector2 &get_motion_velocity() const; + void set_motion_velocity(const Vector2 &p_velocity); bool is_on_floor() const; bool is_on_floor_only() const; @@ -313,9 +326,14 @@ public: bool is_on_wall_only() const; bool is_on_ceiling() const; bool is_on_ceiling_only() const; - Vector2 get_floor_normal() const; + const Vector2 &get_last_motion() const; + Vector2 get_position_delta() const; + const Vector2 &get_floor_normal() const; + const Vector2 &get_wall_normal() const; + const Vector2 &get_real_velocity() const; + real_t get_floor_angle(const Vector2 &p_up_direction = Vector2(0.0, -1.0)) const; - Vector2 get_platform_velocity() const; + const Vector2 &get_platform_velocity() const; int get_slide_collision_count() const; PhysicsServer2D::MotionResult get_slide_collision(int p_bounce) const; @@ -326,23 +344,29 @@ public: private: real_t margin = 0.08; MotionMode motion_mode = MOTION_MODE_GROUNDED; + MovingPlatformApplyVelocityOnLeave moving_platform_apply_velocity_on_leave = PLATFORM_VEL_ON_LEAVE_ALWAYS; - bool floor_stop_on_slope = false; bool floor_constant_speed = false; + bool floor_stop_on_slope = true; bool floor_block_on_wall = true; bool slide_on_ceiling = true; int max_slides = 4; - int platform_layer; + int platform_layer = 0; real_t floor_max_angle = Math::deg2rad((real_t)45.0); - real_t floor_snap_length = 0; + real_t floor_snap_length = 1; real_t free_mode_min_slide_angle = Math::deg2rad((real_t)15.0); Vector2 up_direction = Vector2(0.0, -1.0); uint32_t moving_platform_floor_layers = UINT32_MAX; uint32_t moving_platform_wall_layers = 0; - Vector2 linear_velocity; + Vector2 motion_velocity; Vector2 floor_normal; Vector2 platform_velocity; + Vector2 wall_normal; + Vector2 last_motion; + Vector2 previous_position; + Vector2 real_velocity; + RID platform_rid; bool on_floor = false; bool on_ceiling = false; @@ -387,6 +411,9 @@ private: void set_motion_mode(MotionMode p_mode); MotionMode get_motion_mode() const; + void set_moving_platform_apply_velocity_on_leave(MovingPlatformApplyVelocityOnLeave p_on_leave_velocity); + MovingPlatformApplyVelocityOnLeave get_moving_platform_apply_velocity_on_leave() const; + void _move_and_slide_free(double p_delta); void _move_and_slide_grounded(double p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity); @@ -406,6 +433,7 @@ protected: }; VARIANT_ENUM_CAST(CharacterBody2D::MotionMode); +VARIANT_ENUM_CAST(CharacterBody2D::MovingPlatformApplyVelocityOnLeave); class KinematicCollision2D : public RefCounted { GDCLASS(KinematicCollision2D, RefCounted); diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 4bbbc3575d..63a0fb9b89 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -456,7 +456,7 @@ void Bone2D::calculate_length_and_rotation() { if (child) { Vector2 child_local_pos = to_local(child->get_global_position()); length = child_local_pos.length(); - bone_angle = Math::atan2(child_local_pos.normalized().y, child_local_pos.normalized().x); + bone_angle = child_local_pos.normalized().angle(); calculated = true; break; } diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 03db9c0d32..78d018ac53 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -452,6 +452,19 @@ int TileMap::get_layer_z_index(int p_layer) const { return layers[p_layer].z_index; } +void TileMap::set_collision_animatable(bool p_enabled) { + collision_animatable = p_enabled; + _clear_internals(); + set_notify_local_transform(p_enabled); + set_physics_process_internal(p_enabled); + _recreate_internals(); + emit_signal(SNAME("changed")); +} + +bool TileMap::is_collision_animatable() const { + return collision_animatable; +} + void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_collision) { collision_visibility_mode = p_show_collision; _clear_internals(); @@ -508,7 +521,6 @@ Map<Vector2i, TileMapQuadrant>::Element *TileMap::_create_quadrant(int p_layer, // Call the create_quadrant method on plugins if (tile_set.is_valid()) { _rendering_create_quadrant(&q); - _physics_create_quadrant(&q); } return layers[p_layer].quadrant_map.insert(p_qk, q); @@ -1092,24 +1104,67 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe void TileMap::_physics_notification(int p_what) { switch (p_what) { + case CanvasItem::NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + bool in_editor = false; +#ifdef TOOLS_ENABLED + in_editor = Engine::get_singleton()->is_editor_hint(); +#endif + if (is_inside_tree() && collision_animatable && !in_editor) { + // Update tranform on the physics tick when in animatable mode. + last_valid_transform = new_transform; + set_notify_local_transform(false); + set_global_transform(new_transform); + set_notify_local_transform(true); + } + } break; case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { - // Update the bodies transforms. - if (is_inside_tree()) { + bool in_editor = false; +#ifdef TOOLS_ENABLED + in_editor = Engine::get_singleton()->is_editor_hint(); +#endif + if (is_inside_tree() && (!collision_animatable || in_editor)) { + // Update the new transform directly if we are not in animatable mode. + Transform2D global_transform = get_global_transform(); for (int layer = 0; layer < (int)layers.size(); layer++) { - Transform2D global_transform = get_global_transform(); + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + TileMapQuadrant &q = E->get(); + for (RID body : q.bodies) { + Transform2D xform; + xform.set_origin(map_to_world(bodies_coords[body])); + xform = global_transform * xform; + PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + } + } + } + } break; + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + bool in_editor = false; +#ifdef TOOLS_ENABLED + in_editor = Engine::get_singleton()->is_editor_hint(); +#endif + if (is_inside_tree() && !in_editor && collision_animatable) { + // Only active when animatable. Send the new transform to the physics... + new_transform = get_global_transform(); + for (int layer = 0; layer < (int)layers.size(); layer++) { for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { TileMapQuadrant &q = E->get(); - Transform2D xform; - xform.set_origin(map_to_world(E->key() * get_effective_quadrant_size(layer))); - xform = global_transform * xform; + for (RID body : q.bodies) { + Transform2D xform; + xform.set_origin(map_to_world(bodies_coords[body])); + xform = new_transform * xform; - for (int body_index = 0; body_index < q.bodies.size(); body_index++) { - PhysicsServer2D::get_singleton()->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); } } } + + // ... but then revert changes. + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); } } break; } @@ -1120,29 +1175,23 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r ERR_FAIL_COND(!tile_set.is_valid()); Transform2D global_transform = get_global_transform(); + last_valid_transform = global_transform; + new_transform = global_transform; PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); + RID space = get_world_2d()->get_space(); SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); while (q_list_element) { TileMapQuadrant &q = *q_list_element->self(); - Vector2 quadrant_pos = map_to_world(q.coords * get_effective_quadrant_size(q.layer)); - - LocalVector<int> body_shape_count; - body_shape_count.resize(q.bodies.size()); - - // Clear shapes. - for (int body_index = 0; body_index < q.bodies.size(); body_index++) { - ps->body_clear_shapes(q.bodies[body_index]); - body_shape_count[body_index] = 0; - - // Position the bodies. - Transform2D xform; - xform.set_origin(quadrant_pos); - xform = global_transform * xform; - ps->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + // Clear bodies. + for (RID body : q.bodies) { + bodies_coords.erase(body); + ps->free(body); } + q.bodies.clear(); + // Recreate bodies and shapes. for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { TileMapCell c = get_cell(q.layer, E_cell->get(), true); @@ -1158,26 +1207,53 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r if (atlas_source) { TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); - for (int body_index = 0; body_index < q.bodies.size(); body_index++) { - int &body_shape_index = body_shape_count[body_index]; + for (int tile_set_physics_layer = 0; tile_set_physics_layer < tile_set->get_physics_layers_count(); tile_set_physics_layer++) { + Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer); + uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer); + uint32_t physics_mask = tile_set->get_physics_layer_collision_mask(tile_set_physics_layer); - // Add the shapes again. - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { - bool one_way_collision = tile_data->is_collision_polygon_one_way(body_index, polygon_index); - float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(body_index, polygon_index); + // Create the body. + RID body = ps->body_create(); + bodies_coords[body] = E_cell->get(); + ps->body_set_mode(body, collision_animatable ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); + ps->body_set_space(body, space); - int shapes_count = tile_data->get_collision_polygon_shapes_count(body_index, polygon_index); - for (int shape_index = 0; shape_index < shapes_count; shape_index++) { - Transform2D xform = Transform2D(); - xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); + Transform2D xform; + xform.set_origin(map_to_world(E_cell->get())); + xform = global_transform * xform; + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + + ps->body_attach_object_instance_id(body, get_instance_id()); + ps->body_set_collision_layer(body, physics_layer); + ps->body_set_collision_mask(body, physics_mask); + ps->body_set_pickable(body, false); + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, tile_data->get_constant_linear_velocity(tile_set_physics_layer)); + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, tile_data->get_constant_angular_velocity(tile_set_physics_layer)); + + if (!physics_material.is_valid()) { + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); + } else { + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); + } + + q.bodies.push_back(body); + // Add the shapes to the body. + int body_shape_index = 0; + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(tile_set_physics_layer); polygon_index++) { + // Iterate over the polygons. + bool one_way_collision = tile_data->is_collision_polygon_one_way(tile_set_physics_layer, polygon_index); + float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(tile_set_physics_layer, polygon_index); + int shapes_count = tile_data->get_collision_polygon_shapes_count(tile_set_physics_layer, polygon_index); + for (int shape_index = 0; shape_index < shapes_count; shape_index++) { // Add decomposed convex shapes. - Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(body_index, polygon_index, shape_index); - ps->body_add_shape(q.bodies[body_index], shape->get_rid(), xform); - ps->body_set_shape_metadata(q.bodies[body_index], body_shape_index, E_cell->get()); - ps->body_set_shape_as_one_way_collision(q.bodies[body_index], body_shape_index, one_way_collision, one_way_collision_margin); + Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index); + ps->body_add_shape(body, shape->get_rid()); + ps->body_set_shape_as_one_way_collision(body, body_shape_index, one_way_collision, one_way_collision_margin); - ++body_shape_index; + body_shape_index++; } } } @@ -1189,54 +1265,11 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r } } -void TileMap::_physics_create_quadrant(TileMapQuadrant *p_quadrant) { - ERR_FAIL_COND(!tile_set.is_valid()); - - //Get the TileMap's gobla transform. - Transform2D global_transform; - if (is_inside_tree()) { - global_transform = get_global_transform(); - } - - // Clear all bodies. - p_quadrant->bodies.clear(); - - // Create the body and set its parameters. - for (int layer = 0; layer < tile_set->get_physics_layers_count(); layer++) { - RID body = PhysicsServer2D::get_singleton()->body_create(); - PhysicsServer2D::get_singleton()->body_set_mode(body, PhysicsServer2D::BODY_MODE_STATIC); - - PhysicsServer2D::get_singleton()->body_attach_object_instance_id(body, get_instance_id()); - PhysicsServer2D::get_singleton()->body_set_collision_layer(body, tile_set->get_physics_layer_collision_layer(layer)); - PhysicsServer2D::get_singleton()->body_set_collision_mask(body, tile_set->get_physics_layer_collision_mask(layer)); - - Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(layer); - if (!physics_material.is_valid()) { - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); - } else { - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); - } - - if (is_inside_tree()) { - RID space = get_world_2d()->get_space(); - PhysicsServer2D::get_singleton()->body_set_space(body, space); - - Transform2D xform; - xform.set_origin(map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer))); - xform = global_transform * xform; - PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } - - p_quadrant->bodies.push_back(body); - } -} - void TileMap::_physics_cleanup_quadrant(TileMapQuadrant *p_quadrant) { // Remove a quadrant. - for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { - PhysicsServer2D::get_singleton()->free(p_quadrant->bodies[body_index]); + for (RID body : p_quadrant->bodies) { + bodies_coords.erase(body); + PhysicsServer2D::get_singleton()->free(body); } p_quadrant->bodies.clear(); } @@ -1252,7 +1285,7 @@ void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { bool show_collision = false; switch (collision_visibility_mode) { case TileMap::VISIBILITY_MODE_DEFAULT: - show_collision = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_navigation_hint()); + show_collision = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_collisions_hint()); break; case TileMap::VISIBILITY_MODE_FORCE_HIDE: show_collision = false; @@ -1266,39 +1299,28 @@ void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } RenderingServer *rs = RenderingServer::get_singleton(); - - Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); Color debug_collision_color = get_tree()->get_debug_collisions_color(); - for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = get_cell(p_quadrant->layer, E_cell->get(), true); - - Transform2D xform; - xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); - rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); - - if (tile_set->has_source(c.source_id)) { - TileSetSource *source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } + Vector<Color> color; + color.push_back(debug_collision_color); - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + Transform2D qudrant_xform; + qudrant_xform.set_origin(quadrant_pos); + Transform2D global_transform_inv = (get_global_transform() * qudrant_xform).affine_inverse(); - for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { - // Draw the debug polygon. - Vector<Vector2> polygon = tile_data->get_collision_polygon_points(body_index, polygon_index); - if (polygon.size() >= 3) { - Vector<Color> color; - color.push_back(debug_collision_color); - rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, polygon, color); - } - } - } + for (RID body : p_quadrant->bodies) { + Transform2D xform = Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM)) * global_transform_inv; + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + for (int shape_index = 0; shape_index < ps->body_get_shape_count(body); shape_index++) { + const RID &shape = ps->body_get_shape(body, shape_index); + PhysicsServer2D::ShapeType type = ps->shape_get_type(shape); + if (type == PhysicsServer2D::SHAPE_CONVEX_POLYGON) { + Vector<Vector2> polygon = ps->shape_get_data(shape); + rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, polygon, color); + } else { + WARN_PRINT("Wrong shape type for a tile, should be SHAPE_CONVEX_POLYGON."); } } rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, Transform2D()); @@ -1858,6 +1880,11 @@ Map<Vector2i, TileMapQuadrant> *TileMap::get_quadrant_map(int p_layer) { return &layers[p_layer].quadrant_map; } +Vector2i TileMap::get_coords_for_body_rid(RID p_physics_body) { + ERR_FAIL_COND_V_MSG(!bodies_coords.has(p_physics_body), Vector2i(), vformat("No tiles for the given body RID %d.", p_physics_body)); + return bodies_coords[p_physics_body]; +} + void TileMap::fix_invalid_tiles() { ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); @@ -2034,10 +2061,22 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { return false; } else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { int index = components[0].trim_prefix("layer_").to_int(); - if (index < 0 || index >= (int)layers.size()) { + if (index < 0) { return false; } + if (index >= (int)layers.size()) { + _clear_internals(); + while (index >= (int)layers.size()) { + layers.push_back(TileMapLayer()); + } + _recreate_internals(); + + notify_property_list_changed(); + emit_signal(SNAME("changed")); + update_configuration_warnings(); + } + if (components[1] == "name") { set_layer_name(index, p_value); return true; @@ -2995,6 +3034,8 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_layer_z_index", "layer", "z_index"), &TileMap::set_layer_z_index); ClassDB::bind_method(D_METHOD("get_layer_z_index", "layer"), &TileMap::get_layer_z_index); + ClassDB::bind_method(D_METHOD("set_collision_animatable", "enabled"), &TileMap::set_collision_animatable); + ClassDB::bind_method(D_METHOD("is_collision_animatable"), &TileMap::is_collision_animatable); ClassDB::bind_method(D_METHOD("set_collision_visibility_mode", "collision_visibility_mode"), &TileMap::set_collision_visibility_mode); ClassDB::bind_method(D_METHOD("get_collision_visibility_mode"), &TileMap::get_collision_visibility_mode); @@ -3006,10 +3047,14 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "layer", "coords", "use_proxies"), &TileMap::get_cell_atlas_coords); ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "layer", "coords", "use_proxies"), &TileMap::get_cell_alternative_tile); + ClassDB::bind_method(D_METHOD("get_coords_for_body_rid", "body"), &TileMap::get_coords_for_body_rid); + ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMap::fix_invalid_tiles); - ClassDB::bind_method(D_METHOD("get_surrounding_tiles", "coords"), &TileMap::get_surrounding_tiles); + ClassDB::bind_method(D_METHOD("clear_layer", "layer"), &TileMap::clear_layer); ClassDB::bind_method(D_METHOD("clear"), &TileMap::clear); + ClassDB::bind_method(D_METHOD("get_surrounding_tiles", "coords"), &TileMap::get_surrounding_tiles); + ClassDB::bind_method(D_METHOD("get_used_cells", "layer"), &TileMap::get_used_cells); ClassDB::bind_method(D_METHOD("get_used_rect"), &TileMap::get_used_rect); @@ -3025,6 +3070,7 @@ void TileMap::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_quadrant_size", "get_quadrant_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_animatable"), "set_collision_animatable", "is_collision_animatable"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode"); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index ca38baa550..2faede2445 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -202,6 +202,7 @@ private: // Properties. Ref<TileSet> tile_set; int quadrant_size = 16; + bool collision_animatable = false; VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT; VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT; @@ -229,6 +230,9 @@ private: LocalVector<TileMapLayer> layers; int selected_layer = -1; + // Mapping for RID to coords. + Map<RID, Vector2i> bodies_coords; + // Quadrants and internals management. Vector2i _coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const; @@ -259,9 +263,10 @@ private: void _rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant); void _rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + Transform2D last_valid_transform; + Transform2D new_transform; void _physics_notification(int p_what); void _physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); - void _physics_create_quadrant(TileMapQuadrant *p_quadrant); void _physics_cleanup_quadrant(TileMapQuadrant *p_quadrant); void _physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant); @@ -325,6 +330,9 @@ public: void set_selected_layer(int p_layer_id); // For editor use. int get_selected_layer() const; + void set_collision_animatable(bool p_enabled); + bool is_collision_animatable() const; + void set_collision_visibility_mode(VisibilityMode p_show_collision); VisibilityMode get_collision_visibility_mode(); @@ -364,6 +372,9 @@ public: virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override; virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override; + // For finding tiles from collision. + Vector2i get_coords_for_body_rid(RID p_physics_body); + // Fixing a nclearing methods. void fix_invalid_tiles(); diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index c34c150145..afd11482e3 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -110,7 +110,7 @@ TypedArray<String> BoneAttachment3D::get_configuration_warnings() const { } else { Skeleton3D *parent = Object::cast_to<Skeleton3D>(get_parent()); if (!parent) { - warnings.append(TTR("Parent node is not a Skeleton3D node! Please use an extenral Skeleton3D if you intend to use the BoneAttachment3D without it being a child of a Skeleton3D node.")); + warnings.append(TTR("Parent node is not a Skeleton3D node! Please use an external Skeleton3D if you intend to use the BoneAttachment3D without it being a child of a Skeleton3D node.")); } } diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp index c79f956642..4e496fba47 100644 --- a/scene/3d/collision_shape_3d.cpp +++ b/scene/3d/collision_shape_3d.cpp @@ -124,8 +124,7 @@ TypedArray<String> CollisionShape3D::get_configuration_warnings() const { if (shape.is_valid() && Object::cast_to<RigidDynamicBody3D>(get_parent()) && - Object::cast_to<ConcavePolygonShape3D>(*shape) && - Object::cast_to<RigidDynamicBody3D>(get_parent())->get_mode() != RigidDynamicBody3D::MODE_STATIC) { + Object::cast_to<ConcavePolygonShape3D>(*shape)) { warnings.push_back(TTR("ConcavePolygonShape3D doesn't support RigidDynamicBody3D in another mode than static.")); } diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index baf28ae102..32a62d8c7e 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -469,7 +469,7 @@ void GPUParticles3D::_skinning_changed() { if (draw_pass.is_valid() && draw_pass->get_builtin_bind_pose_count() > 0) { xforms.resize(draw_pass->get_builtin_bind_pose_count()); for (int j = 0; j < draw_pass->get_builtin_bind_pose_count(); j++) { - xforms.write[i] = draw_pass->get_builtin_bind_pose(j); + xforms.write[j] = draw_pass->get_builtin_bind_pose(j); } break; } diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 48da186860..9e2358ed39 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -38,8 +38,8 @@ #endif void PhysicsBody3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "test_only", "safe_margin", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(1)); - ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "collision", "safe_margin", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("move_and_collide", "linear_velocity", "test_only", "safe_margin", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("test_move", "from", "linear_velocity", "collision", "safe_margin", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(1)); ClassDB::bind_method(D_METHOD("set_axis_lock", "axis", "lock"), &PhysicsBody3D::set_axis_lock); ClassDB::bind_method(D_METHOD("get_axis_lock", "axis"), &PhysicsBody3D::get_axis_lock); @@ -97,7 +97,10 @@ void PhysicsBody3D::remove_collision_exception_with(Node *p_node) { Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_test_only, real_t p_margin, int p_max_collisions) { PhysicsServer3D::MotionResult result; - if (move_and_collide(p_motion, result, p_margin, p_test_only, p_max_collisions)) { + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + + if (move_and_collide(p_motion * delta, result, p_margin, p_test_only, p_max_collisions)) { // Create a new instance when the cached reference is invalid or still in use in script. if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) { motion_cache.instantiate(); @@ -177,7 +180,10 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion r = const_cast<PhysicsServer3D::MotionResult *>(&r_collision->result); } - return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_margin, r, p_max_collisions); + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + + return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion * delta, p_margin, r, p_max_collisions); } void PhysicsBody3D::set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock) { @@ -334,6 +340,12 @@ void AnimatableBody3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { last_valid_transform = get_global_transform(); + _update_kinematic_motion(); + } break; + + case NOTIFICATION_EXIT_TREE: { + set_only_update_transform_changes(false); + set_notify_local_transform(false); } break; case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { @@ -361,8 +373,6 @@ void AnimatableBody3D::_bind_methods() { AnimatableBody3D::AnimatableBody3D() : StaticBody3D(PhysicsServer3D::BODY_MODE_KINEMATIC) { PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); - - _update_kinematic_motion(); } void RigidDynamicBody3D::_body_enter_tree(ObjectID p_id) { @@ -599,27 +609,60 @@ void RigidDynamicBody3D::_notification(int p_what) { #endif } -void RigidDynamicBody3D::set_mode(Mode p_mode) { - mode = p_mode; - switch (p_mode) { - case MODE_DYNAMIC: { - set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC); - } break; - case MODE_STATIC: { - set_body_mode(PhysicsServer3D::BODY_MODE_STATIC); - } break; - case MODE_DYNAMIC_LOCKED: { - set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC_LOCKED); - } break; - case MODE_KINEMATIC: { - set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC); - } break; +void RigidDynamicBody3D::_apply_body_mode() { + if (freeze) { + switch (freeze_mode) { + case FREEZE_MODE_STATIC: { + set_body_mode(PhysicsServer3D::BODY_MODE_STATIC); + } break; + case FREEZE_MODE_KINEMATIC: { + set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC); + } break; + } + } else if (lock_rotation) { + set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC_LINEAR); + } else { + set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC); } - update_configuration_warnings(); } -RigidDynamicBody3D::Mode RigidDynamicBody3D::get_mode() const { - return mode; +void RigidDynamicBody3D::set_lock_rotation_enabled(bool p_lock_rotation) { + if (p_lock_rotation == lock_rotation) { + return; + } + + lock_rotation = p_lock_rotation; + _apply_body_mode(); +} + +bool RigidDynamicBody3D::is_lock_rotation_enabled() const { + return lock_rotation; +} + +void RigidDynamicBody3D::set_freeze_enabled(bool p_freeze) { + if (p_freeze == freeze) { + return; + } + + freeze = p_freeze; + _apply_body_mode(); +} + +bool RigidDynamicBody3D::is_freeze_enabled() const { + return freeze; +} + +void RigidDynamicBody3D::set_freeze_mode(FreezeMode p_freeze_mode) { + if (p_freeze_mode == freeze_mode) { + return; + } + + freeze_mode = p_freeze_mode; + _apply_body_mode(); +} + +RigidDynamicBody3D::FreezeMode RigidDynamicBody3D::get_freeze_mode() const { + return freeze_mode; } void RigidDynamicBody3D::set_mass(real_t p_mass) { @@ -892,17 +935,14 @@ TypedArray<String> RigidDynamicBody3D::get_configuration_warnings() const { TypedArray<String> warnings = Node::get_configuration_warnings(); - if ((get_mode() == MODE_DYNAMIC || get_mode() == MODE_DYNAMIC_LOCKED) && (ABS(t.basis.get_axis(0).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(1).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(2).length() - 1.0) > 0.05)) { - warnings.push_back(TTR("Size changes to RigidDynamicBody (in dynamic modes) will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); + if (ABS(t.basis.get_axis(0).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(1).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(2).length() - 1.0) > 0.05) { + warnings.push_back(TTR("Size changes to RigidDynamicBody will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); } return warnings; } void RigidDynamicBody3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_mode", "mode"), &RigidDynamicBody3D::set_mode); - ClassDB::bind_method(D_METHOD("get_mode"), &RigidDynamicBody3D::get_mode); - ClassDB::bind_method(D_METHOD("set_mass", "mass"), &RigidDynamicBody3D::set_mass); ClassDB::bind_method(D_METHOD("get_mass"), &RigidDynamicBody3D::get_mass); @@ -963,11 +1003,19 @@ void RigidDynamicBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_can_sleep", "able_to_sleep"), &RigidDynamicBody3D::set_can_sleep); ClassDB::bind_method(D_METHOD("is_able_to_sleep"), &RigidDynamicBody3D::is_able_to_sleep); + ClassDB::bind_method(D_METHOD("set_lock_rotation_enabled", "lock_rotation"), &RigidDynamicBody3D::set_lock_rotation_enabled); + ClassDB::bind_method(D_METHOD("is_lock_rotation_enabled"), &RigidDynamicBody3D::is_lock_rotation_enabled); + + ClassDB::bind_method(D_METHOD("set_freeze_enabled", "freeze_mode"), &RigidDynamicBody3D::set_freeze_enabled); + ClassDB::bind_method(D_METHOD("is_freeze_enabled"), &RigidDynamicBody3D::is_freeze_enabled); + + ClassDB::bind_method(D_METHOD("set_freeze_mode", "freeze_mode"), &RigidDynamicBody3D::set_freeze_mode); + ClassDB::bind_method(D_METHOD("get_freeze_mode"), &RigidDynamicBody3D::get_freeze_mode); + ClassDB::bind_method(D_METHOD("get_colliding_bodies"), &RigidDynamicBody3D::get_colliding_bodies); GDVIRTUAL_BIND(_integrate_forces, "state"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Dynamic,Static,DynamicLocked,Kinematic"), "set_mode", "get_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inertia", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater,exp"), "set_inertia", "get_inertia"); ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_center_of_mass_mode", "get_center_of_mass_mode"); @@ -981,6 +1029,9 @@ void RigidDynamicBody3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "contact_monitor"), "set_contact_monitor", "is_contact_monitor_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sleeping"), "set_sleeping", "is_sleeping"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "can_sleep"), "set_can_sleep", "is_able_to_sleep"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "lock_rotation"), "set_lock_rotation_enabled", "is_lock_rotation_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "freeze"), "set_freeze_enabled", "is_freeze_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "freeze_mode", PROPERTY_HINT_ENUM, "Static,Kinematic"), "set_freeze_mode", "get_freeze_mode"); ADD_GROUP("Linear", "linear_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); @@ -994,10 +1045,8 @@ void RigidDynamicBody3D::_bind_methods() { ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("sleeping_state_changed")); - BIND_ENUM_CONSTANT(MODE_DYNAMIC); - BIND_ENUM_CONSTANT(MODE_STATIC); - BIND_ENUM_CONSTANT(MODE_DYNAMIC_LOCKED); - BIND_ENUM_CONSTANT(MODE_KINEMATIC); + BIND_ENUM_CONSTANT(FREEZE_MODE_STATIC); + BIND_ENUM_CONSTANT(FREEZE_MODE_KINEMATIC); BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO); BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM); @@ -1040,13 +1089,16 @@ void RigidDynamicBody3D::_reload_physics_characteristics() { bool CharacterBody3D::move_and_slide() { // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); - previous_position = get_global_transform().origin; + for (int i = 0; i < 3; i++) { if (locked_axis & (1 << i)) { - linear_velocity[i] = 0.0; + motion_velocity[i] = 0.0; } } + Transform3D gt = get_global_transform(); + previous_position = gt.origin; + Vector3 current_platform_velocity = platform_velocity; if ((collision_state.floor || collision_state.wall) && platform_rid.is_valid()) { @@ -1060,7 +1112,6 @@ bool CharacterBody3D::move_and_slide() { //this approach makes sure there is less delay between the actual body velocity and the one we saved PhysicsDirectBodyState3D *bs = PhysicsServer3D::get_singleton()->body_get_direct_state(platform_rid); if (bs) { - Transform3D gt = get_global_transform(); Vector3 local_position = gt.origin - bs->get_transform().origin; current_platform_velocity = bs->get_velocity_at_local_position(local_position); } @@ -1074,6 +1125,8 @@ bool CharacterBody3D::move_and_slide() { bool was_on_floor = collision_state.floor; collision_state.state = 0; + last_motion = Vector3(); + if (!current_platform_velocity.is_equal_approx(Vector3())) { PhysicsServer3D::MotionResult floor_result; Set<RID> exclude; @@ -1101,20 +1154,15 @@ bool CharacterBody3D::move_and_slide() { if (moving_platform_apply_velocity_on_leave == PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY && current_platform_velocity.dot(up_direction) < 0) { current_platform_velocity = current_platform_velocity.slide(up_direction); } - linear_velocity += current_platform_velocity; + motion_velocity += current_platform_velocity; } } - // Reset the gravity accumulation when touching the ground. - if (collision_state.floor && linear_velocity.dot(up_direction) <= 0) { - linear_velocity = linear_velocity.slide(up_direction); - } - return motion_results.size() > 0; } void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor) { - Vector3 motion = linear_velocity * p_delta; + Vector3 motion = motion_velocity * p_delta; Vector3 motion_slide_up = motion.slide(up_direction); Vector3 prev_floor_normal = floor_normal; @@ -1129,12 +1177,13 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo // Constant speed can be applied only the first time sliding is enabled. bool can_apply_constant_speed = sliding_enabled; bool first_slide = true; - bool vel_dir_facing_up = linear_velocity.dot(up_direction) > 0; + bool vel_dir_facing_up = motion_velocity.dot(up_direction) > 0; Vector3 total_travel; for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionResult result; bool collided = move_and_collide(motion, result, margin, false, 4, !sliding_enabled); + last_motion = result.travel; if (collided) { motion_results.push_back(result); @@ -1144,21 +1193,20 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo CollisionState result_state; _set_collision_direction(result, result_state); - if (collision_state.floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { + if (collision_state.floor && floor_stop_on_slope && (motion_velocity.normalized() + up_direction).length() < 0.01) { Transform3D gt = get_global_transform(); - real_t travel_total = result.travel.length(); - if (travel_total <= margin + CMP_EPSILON) { + if (result.travel.length() <= margin + CMP_EPSILON) { gt.origin -= result.travel; } set_global_transform(gt); - linear_velocity = Vector3(); + motion_velocity = Vector3(); motion = Vector3(); + last_motion = Vector3(); break; } if (result.remainder.is_equal_approx(Vector3())) { motion = Vector3(); - last_motion = result.travel; break; } @@ -1186,7 +1234,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo Transform3D gt = get_global_transform(); real_t travel_total = result.travel.length(); real_t cancel_dist_max = MIN(0.1, margin * 20); - if (travel_total < margin + CMP_EPSILON) { + if (travel_total <= margin + CMP_EPSILON) { gt.origin -= result.travel; } else if (travel_total < cancel_dist_max) { // If the movement is large the body can be prevented from reaching the walls. gt.origin -= result.travel.slide(up_direction); @@ -1207,7 +1255,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo Vector3 forward = wall_normal.slide(up_direction).normalized(); motion = motion.slide(forward); // Avoid accelerating when you jump on the wall and smooth falling. - linear_velocity = linear_velocity.slide(forward); + motion_velocity = motion_velocity.slide(forward); // Allow only lateral motion along previous floor when already on floor. // Fixes slowing down when moving in diagonal against an inclined wall. @@ -1236,7 +1284,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo if (stop_all_motion) { motion = Vector3(); - linear_velocity = Vector3(); + motion_velocity = Vector3(); } } } @@ -1247,7 +1295,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo real_t motion_angle = Math::abs(Math::acos(-horizontal_normal.dot(motion_slide_up.normalized()))); if (motion_angle < wall_min_slide_angle) { motion = up_direction * motion.dot(up_direction); - linear_velocity = up_direction * linear_velocity.dot(up_direction); + motion_velocity = up_direction * motion_velocity.dot(up_direction); apply_default_sliding = false; } @@ -1258,19 +1306,33 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo // Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling. if ((sliding_enabled || !collision_state.floor) && (!collision_state.ceiling || slide_on_ceiling || !vel_dir_facing_up)) { const PhysicsServer3D::MotionCollision &collision = result.collisions[0]; + Vector3 slide_motion = result.remainder.slide(collision.normal); - if (slide_motion.dot(linear_velocity) > 0.0) { + if (collision_state.floor && !collision_state.wall) { + // Slide using the intersection between the motion plane and the floor plane, + // in order to keep the direction intact. + real_t motion_length = slide_motion.length(); + slide_motion = up_direction.cross(result.remainder).cross(floor_normal); + + // Keep the length from default slide to change speed in slopes by default, + // when constant speed is not enabled. + slide_motion.normalize(); + slide_motion *= motion_length; + } + + if (slide_motion.dot(motion_velocity) > 0.0) { motion = slide_motion; } else { motion = Vector3(); } + if (slide_on_ceiling && result_state.ceiling) { // Apply slide only in the direction of the input motion, otherwise just stop to avoid jittering when moving against a wall. if (vel_dir_facing_up) { - linear_velocity = linear_velocity.slide(collision.normal); + motion_velocity = motion_velocity.slide(collision.normal); } else { // Avoid acceleration in slope when falling. - linear_velocity = up_direction * up_direction.dot(linear_velocity); + motion_velocity = up_direction * up_direction.dot(motion_velocity); } } } @@ -1278,18 +1340,19 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo else { motion = result.remainder; if (result_state.ceiling && !slide_on_ceiling && vel_dir_facing_up) { - linear_velocity = linear_velocity.slide(up_direction); + motion_velocity = motion_velocity.slide(up_direction); motion = motion.slide(up_direction); } } } + total_travel += result.travel; + // Apply Constant Speed. - if (p_was_on_floor && floor_constant_speed && collision_state.floor && !motion.is_equal_approx(Vector3())) { - motion = motion.normalized() * MAX(0, (motion_slide_up.length() - result.travel.slide(up_direction).length() - total_travel.slide(up_direction).length())); + if (p_was_on_floor && floor_constant_speed && can_apply_constant_speed && collision_state.floor && !motion.is_equal_approx(Vector3())) { + Vector3 travel_slide_up = total_travel.slide(up_direction); + motion = motion.normalized() * MAX(0, (motion_slide_up.length() - travel_slide_up.length())); } - - total_travel += result.travel; } // When you move forward in a downward slope you don’t collide because you will be in the air. // This test ensures that constant speed is applied, only if the player is still on the ground after the snap is applied. @@ -1300,34 +1363,34 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo gt.origin = gt.origin - result.travel; set_global_transform(gt); - Vector3 motion_slide_norm = motion.slide(prev_floor_normal).normalized(); + // Slide using the intersection between the motion plane and the floor plane, + // in order to keep the direction intact. + Vector3 motion_slide_norm = up_direction.cross(motion).cross(prev_floor_normal); + motion_slide_norm.normalize(); + motion = motion_slide_norm * (motion_slide_up.length()); collided = true; } - can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled; - sliding_enabled = true; - first_slide = false; - - if (!motion.is_equal_approx(Vector3())) { - last_motion = motion; - } - if (!collided || motion.is_equal_approx(Vector3())) { break; } + + can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled; + sliding_enabled = true; + first_slide = false; } _snap_on_floor(p_was_on_floor, vel_dir_facing_up); // Reset the gravity accumulation when touching the ground. if (collision_state.floor && !vel_dir_facing_up) { - linear_velocity = linear_velocity.slide(up_direction); + motion_velocity = motion_velocity.slide(up_direction); } } void CharacterBody3D::_move_and_slide_free(double p_delta) { - Vector3 motion = linear_velocity * p_delta; + Vector3 motion = motion_velocity * p_delta; platform_rid = RID(); floor_normal = Vector3(); @@ -1338,6 +1401,7 @@ void CharacterBody3D::_move_and_slide_free(double p_delta) { PhysicsServer3D::MotionResult result; bool collided = move_and_collide(motion, result, margin, false, 1, false); + last_motion = result.travel; if (collided) { motion_results.push_back(result); @@ -1345,7 +1409,12 @@ void CharacterBody3D::_move_and_slide_free(double p_delta) { CollisionState result_state; _set_collision_direction(result, result_state); - if (wall_min_slide_angle != 0 && Math::acos(wall_normal.dot(-linear_velocity.normalized())) < wall_min_slide_angle + FLOOR_ANGLE_THRESHOLD) { + if (result.remainder.is_equal_approx(Vector3())) { + motion = Vector3(); + break; + } + + if (wall_min_slide_angle != 0 && Math::acos(wall_normal.dot(-motion_velocity.normalized())) < wall_min_slide_angle + FLOOR_ANGLE_THRESHOLD) { motion = Vector3(); if (result.travel.length() < margin + CMP_EPSILON) { Transform3D gt = get_global_transform(); @@ -1359,16 +1428,16 @@ void CharacterBody3D::_move_and_slide_free(double p_delta) { motion = result.remainder.slide(wall_normal); } - if (motion.dot(linear_velocity) <= 0.0) { + if (motion.dot(motion_velocity) <= 0.0) { motion = Vector3(); } } - first_slide = false; - if (!collided || motion.is_equal_approx(Vector3())) { break; } + + first_slide = false; } } @@ -1377,7 +1446,9 @@ void CharacterBody3D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) return; } + // Snap by at least collision margin to keep floor state consistent. real_t length = MAX(floor_snap_length, margin); + Transform3D gt = get_global_transform(); PhysicsServer3D::MotionResult result; if (move_and_collide(-up_direction * length, result, margin, true, 4, false, true)) { @@ -1403,12 +1474,15 @@ void CharacterBody3D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) } bool CharacterBody3D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up) { - if (Math::is_zero_approx(floor_snap_length) || up_direction == Vector3() || collision_state.floor || !was_on_floor || vel_dir_facing_up) { + if (up_direction == Vector3() || collision_state.floor || !was_on_floor || vel_dir_facing_up) { return false; } + // Snap by at least collision margin to keep floor state consistent. + real_t length = MAX(floor_snap_length, margin); + PhysicsServer3D::MotionResult result; - if (move_and_collide(-up_direction * floor_snap_length, result, margin, true, 4, false, true)) { + if (move_and_collide(-up_direction * length, result, margin, true, 4, false, true)) { CollisionState result_state; // Don't apply direction for any type. _set_collision_direction(result, result_state, CollisionState()); @@ -1515,12 +1589,12 @@ real_t CharacterBody3D::get_safe_margin() const { return margin; } -Vector3 CharacterBody3D::get_linear_velocity() const { - return linear_velocity; +const Vector3 &CharacterBody3D::get_motion_velocity() const { + return motion_velocity; } -void CharacterBody3D::set_linear_velocity(const Vector3 &p_velocity) { - linear_velocity = p_velocity; +void CharacterBody3D::set_motion_velocity(const Vector3 &p_velocity) { + motion_velocity = p_velocity; } bool CharacterBody3D::is_on_floor() const { @@ -1547,15 +1621,15 @@ bool CharacterBody3D::is_on_ceiling_only() const { return collision_state.ceiling && !collision_state.floor && !collision_state.wall; } -Vector3 CharacterBody3D::get_floor_normal() const { +const Vector3 &CharacterBody3D::get_floor_normal() const { return floor_normal; } -Vector3 CharacterBody3D::get_wall_normal() const { +const Vector3 &CharacterBody3D::get_wall_normal() const { return wall_normal; } -Vector3 CharacterBody3D::get_last_motion() const { +const Vector3 &CharacterBody3D::get_last_motion() const { return last_motion; } @@ -1563,19 +1637,23 @@ Vector3 CharacterBody3D::get_position_delta() const { return get_transform().origin - previous_position; } -Vector3 CharacterBody3D::get_real_velocity() const { +const Vector3 &CharacterBody3D::get_real_velocity() const { return real_velocity; -}; +} real_t CharacterBody3D::get_floor_angle(const Vector3 &p_up_direction) const { ERR_FAIL_COND_V(p_up_direction == Vector3(), 0); return Math::acos(floor_normal.dot(p_up_direction)); } -Vector3 CharacterBody3D::get_platform_velocity() const { +const Vector3 &CharacterBody3D::get_platform_velocity() const { return platform_velocity; } +Vector3 CharacterBody3D::get_linear_velocity() const { + return get_real_velocity(); +} + int CharacterBody3D::get_slide_collision_count() const { return motion_results.size(); } @@ -1730,8 +1808,8 @@ void CharacterBody3D::_notification(int p_what) { void CharacterBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("move_and_slide"), &CharacterBody3D::move_and_slide); - ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &CharacterBody3D::set_linear_velocity); - ClassDB::bind_method(D_METHOD("get_linear_velocity"), &CharacterBody3D::get_linear_velocity); + ClassDB::bind_method(D_METHOD("set_motion_velocity", "motion_velocity"), &CharacterBody3D::set_motion_velocity); + ClassDB::bind_method(D_METHOD("get_motion_velocity"), &CharacterBody3D::get_motion_velocity); ClassDB::bind_method(D_METHOD("set_safe_margin", "pixels"), &CharacterBody3D::set_safe_margin); ClassDB::bind_method(D_METHOD("get_safe_margin"), &CharacterBody3D::get_safe_margin); @@ -1784,7 +1862,7 @@ void CharacterBody3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_direction"), "set_up_direction", "get_up_direction"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_linear_velocity", "get_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "motion_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_motion_velocity", "get_motion_velocity"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides"); ADD_GROUP("Free Mode", "free_mode_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wall_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_wall_min_slide_angle", "get_wall_min_slide_angle"); diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index 96f3d7d747..0f25d2469f 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -105,7 +105,7 @@ private: Vector3 linear_velocity; Vector3 angular_velocity; - bool sync_to_physics = false; + bool sync_to_physics = true; Transform3D last_valid_transform; @@ -133,11 +133,9 @@ class RigidDynamicBody3D : public PhysicsBody3D { GDCLASS(RigidDynamicBody3D, PhysicsBody3D); public: - enum Mode { - MODE_DYNAMIC, - MODE_STATIC, - MODE_DYNAMIC_LOCKED, - MODE_KINEMATIC, + enum FreezeMode { + FREEZE_MODE_STATIC, + FREEZE_MODE_KINEMATIC, }; enum CenterOfMassMode { @@ -145,11 +143,11 @@ public: CENTER_OF_MASS_MODE_CUSTOM, }; - GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState3D *) - -protected: +private: bool can_sleep = true; - Mode mode = MODE_DYNAMIC; + bool lock_rotation = false; + bool freeze = false; + FreezeMode freeze_mode = FREEZE_MODE_STATIC; real_t mass = 1.0; Vector3 inertia; @@ -214,16 +212,28 @@ protected: void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape); static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); - virtual void _body_state_changed(PhysicsDirectBodyState3D *p_state); +protected: void _notification(int p_what); static void _bind_methods(); virtual void _validate_property(PropertyInfo &property) const override; + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState3D *) + + virtual void _body_state_changed(PhysicsDirectBodyState3D *p_state); + + void _apply_body_mode(); + public: - void set_mode(Mode p_mode); - Mode get_mode() const; + void set_lock_rotation_enabled(bool p_lock_rotation); + bool is_lock_rotation_enabled() const; + + void set_freeze_enabled(bool p_freeze); + bool is_freeze_enabled() const; + + void set_freeze_mode(FreezeMode p_freeze_mode); + FreezeMode get_freeze_mode() const; void set_mass(real_t p_mass); real_t get_mass() const; @@ -298,7 +308,7 @@ private: void _reload_physics_characteristics(); }; -VARIANT_ENUM_CAST(RigidDynamicBody3D::Mode); +VARIANT_ENUM_CAST(RigidDynamicBody3D::FreezeMode); VARIANT_ENUM_CAST(RigidDynamicBody3D::CenterOfMassMode); class KinematicCollision3D; @@ -318,8 +328,8 @@ public: }; bool move_and_slide(); - virtual Vector3 get_linear_velocity() const override; - void set_linear_velocity(const Vector3 &p_velocity); + const Vector3 &get_motion_velocity() const; + void set_motion_velocity(const Vector3 &p_velocity); bool is_on_floor() const; bool is_on_floor_only() const; @@ -327,13 +337,15 @@ public: bool is_on_wall_only() const; bool is_on_ceiling() const; bool is_on_ceiling_only() const; - Vector3 get_last_motion() const; + const Vector3 &get_last_motion() const; Vector3 get_position_delta() const; - Vector3 get_floor_normal() const; - Vector3 get_wall_normal() const; - Vector3 get_real_velocity() const; + const Vector3 &get_floor_normal() const; + const Vector3 &get_wall_normal() const; + const Vector3 &get_real_velocity() const; real_t get_floor_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; - Vector3 get_platform_velocity() const; + const Vector3 &get_platform_velocity() const; + + virtual Vector3 get_linear_velocity() const override; int get_slide_collision_count() const; PhysicsServer3D::MotionResult get_slide_collision(int p_bounce) const; @@ -364,12 +376,12 @@ private: }; CollisionState collision_state; - bool floor_stop_on_slope = false; bool floor_constant_speed = false; + bool floor_stop_on_slope = true; bool floor_block_on_wall = true; bool slide_on_ceiling = true; int max_slides = 6; - int platform_layer; + int platform_layer = 0; RID platform_rid; uint32_t moving_platform_floor_layers = UINT32_MAX; uint32_t moving_platform_wall_layers = 0; @@ -377,7 +389,7 @@ private: real_t floor_max_angle = Math::deg2rad((real_t)45.0); real_t wall_min_slide_angle = Math::deg2rad((real_t)15.0); Vector3 up_direction = Vector3(0.0, 1.0, 0.0); - Vector3 linear_velocity; + Vector3 motion_velocity; Vector3 floor_normal; Vector3 wall_normal; Vector3 last_motion; diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 466f67afb8..8d90aabfac 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -99,7 +99,7 @@ bool FabrikInverseKinematic::build_chain(Task *p_task, bool p_force_simple_chain child_ci->current_pos = child_ci->initial_transform.origin; if (child_ci->parent_item) { - child_ci->length = (child_ci->current_pos - child_ci->parent_item->current_pos).length(); + child_ci->length = child_ci->parent_item->current_pos.distance_to(child_ci->current_pos); } } @@ -140,7 +140,7 @@ void FabrikInverseKinematic::solve_simple(Task *p_task, bool p_solve_magnet, Vec solve_simple_backwards(p_task->chain, p_solve_magnet); solve_simple_forwards(p_task->chain, p_solve_magnet, p_origin_pos); - distance_to_goal = (p_task->chain.tips[0].chain_item->current_pos - p_task->chain.tips[0].end_effector->goal_transform.origin).length(); + distance_to_goal = p_task->chain.tips[0].end_effector->goal_transform.origin.distance_to(p_task->chain.tips[0].chain_item->current_pos); } } diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp index bc3bb81ed4..9a2aaa8be2 100644 --- a/scene/3d/vehicle_body_3d.cpp +++ b/scene/3d/vehicle_body_3d.cpp @@ -470,7 +470,7 @@ real_t VehicleBody3D::_ray_cast(int p_idx, PhysicsDirectBodyState3D *s) { } void VehicleBody3D::_update_suspension(PhysicsDirectBodyState3D *s) { - real_t chassisMass = mass; + real_t chassisMass = get_mass(); for (int w_it = 0; w_it < wheels.size(); w_it++) { VehicleWheel3D &wheel_info = *wheels[w_it]; @@ -558,7 +558,7 @@ void VehicleBody3D::_resolve_single_bilateral(PhysicsDirectBodyState3D *s, const rel_pos2, normal, s->get_inverse_inertia_tensor().get_main_diagonal(), - 1.0 / mass, + 1.0 / get_mass(), b2invinertia, b2invmass); @@ -584,7 +584,7 @@ void VehicleBody3D::_resolve_single_bilateral(PhysicsDirectBodyState3D *s, const #define ONLY_USE_LINEAR_MASS #ifdef ONLY_USE_LINEAR_MASS - real_t massTerm = real_t(1.) / ((1.0 / mass) + b2invmass); + real_t massTerm = real_t(1.) / ((1.0 / get_mass()) + b2invmass); impulse = -contactDamping * rel_vel * massTerm; #else real_t velocityImpulse = -contactDamping * rel_vel * jacDiagABInv; diff --git a/scene/animation/SCsub b/scene/animation/SCsub index cc33a5af84..d0aa0bc8aa 100644 --- a/scene/animation/SCsub +++ b/scene/animation/SCsub @@ -6,11 +6,8 @@ Import("env") thirdparty_obj = [] -thirdparty_sources = "#thirdparty/misc/easing_equations.cpp" - env_thirdparty = env.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) env.scene_sources += thirdparty_obj # Godot source files diff --git a/scene/animation/easing_equations.h b/scene/animation/easing_equations.h new file mode 100644 index 0000000000..c38d083b7f --- /dev/null +++ b/scene/animation/easing_equations.h @@ -0,0 +1,405 @@ +/*************************************************************************/ +/* easing_equations.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/* + * Derived from Robert Penner's easing equations: http://robertpenner.com/easing/ + * + * Copyright (c) 2001 Robert Penner + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef EASING_EQUATIONS_H +#define EASING_EQUATIONS_H + +namespace linear { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c * t / d + b; +} +}; // namespace linear + +namespace sine { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return -c * cos(t / d * (Math_PI / 2)) + c + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + return c * sin(t / d * (Math_PI / 2)) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + return -c / 2 * (cos(Math_PI * t / d) - 1) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace sine + +namespace quint { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c * pow(t / d, 5) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + return c * (pow(t / d - 1, 5) + 1) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + t = t / d * 2; + + if (t < 1) { + return c / 2 * pow(t, 5) + b; + } + return c / 2 * (pow(t - 2, 5) + 2) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace quint + +namespace quart { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c * pow(t / d, 4) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + return -c * (pow(t / d - 1, 4) - 1) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + t = t / d * 2; + + if (t < 1) { + return c / 2 * pow(t, 4) + b; + } + return -c / 2 * (pow(t - 2, 4) - 2) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace quart + +namespace quad { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c * pow(t / d, 2) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + t /= d; + return -c * t * (t - 2) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + t = t / d * 2; + + if (t < 1) { + return c / 2 * pow(t, 2) + b; + } + return -c / 2 * ((t - 1) * (t - 3) - 1) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace quad + +namespace expo { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + if (t == 0) { + return b; + } + return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + if (t == d) { + return b + c; + } + return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + if (t == 0) { + return b; + } + + if (t == d) { + return b + c; + } + + t = t / d * 2; + + if (t < 1) { + return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005; + } + return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace expo + +namespace elastic { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + if (t == 0) { + return b; + } + + t /= d; + if (t == 1) { + return b + c; + } + + t -= 1; + float p = d * 0.3f; + float a = c * pow(2, 10 * t); + float s = p / 4; + + return -(a * sin((t * d - s) * (2 * Math_PI) / p)) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + if (t == 0) { + return b; + } + + t /= d; + if (t == 1) { + return b + c; + } + + float p = d * 0.3f; + float s = p / 4; + + return (c * pow(2, -10 * t) * sin((t * d - s) * (2 * Math_PI) / p) + c + b); +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + if (t == 0) { + return b; + } + + if ((t /= d / 2) == 2) { + return b + c; + } + + float p = d * (0.3f * 1.5f); + float a = c; + float s = p / 4; + + if (t < 1) { + t -= 1; + a *= pow(2, 10 * t); + return -0.5f * (a * sin((t * d - s) * (2 * Math_PI) / p)) + b; + } + + t -= 1; + a *= pow(2, -10 * t); + return a * sin((t * d - s) * (2 * Math_PI) / p) * 0.5f + c + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace elastic + +namespace cubic { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + t /= d; + return c * t * t * t + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + t = t / d - 1; + return c * (t * t * t + 1) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t + b; + } + + t -= 2; + return c / 2 * (t * t * t + 2) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace cubic + +namespace circ { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + t /= d; + return -c * (sqrt(1 - t * t) - 1) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + t = t / d - 1; + return c * sqrt(1 - t * t) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + t /= d / 2; + if (t < 1) { + return -c / 2 * (sqrt(1 - t * t) - 1) + b; + } + + t -= 2; + return c / 2 * (sqrt(1 - t * t) + 1) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace circ + +namespace bounce { +static real_t out(real_t t, real_t b, real_t c, real_t d) { + t /= d; + + if (t < (1 / 2.75f)) { + return c * (7.5625f * t * t) + b; + } + + if (t < (2 / 2.75f)) { + t -= 1.5f / 2.75f; + return c * (7.5625f * t * t + 0.75f) + b; + } + + if (t < (2.5 / 2.75)) { + t -= 2.25f / 2.75f; + return c * (7.5625f * t * t + 0.9375f) + b; + } + + t -= 2.625f / 2.75f; + return c * (7.5625f * t * t + 0.984375f) + b; +} + +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c - out(d - t, 0, c, d) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return in(t * 2, b, c / 2, d); + } + return out(t * 2 - d, b + c / 2, c / 2, d); +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace bounce + +namespace back { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + float s = 1.70158f; + t /= d; + + return c * t * t * ((s + 1) * t - s) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + float s = 1.70158f; + t = t / d - 1; + + return c * (t * t * ((s + 1) * t + s) + 1) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + float s = 1.70158f * 1.525f; + t /= d / 2; + + if (t < 1) { + return c / 2 * (t * t * ((s + 1) * t - s)) + b; + } + + t -= 2; + return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace back + +#endif diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 2847031375..c43b83747b 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -30,8 +30,23 @@ #include "tween.h" +#include "scene/animation/easing_equations.h" #include "scene/main/node.h" +Tween::interpolater Tween::interpolaters[Tween::TRANS_MAX][Tween::EASE_MAX] = { + { &linear::in, &linear::in, &linear::in, &linear::in }, // Linear is the same for each easing. + { &sine::in, &sine::out, &sine::in_out, &sine::out_in }, + { &quint::in, &quint::out, &quint::in_out, &quint::out_in }, + { &quart::in, &quart::out, &quart::in_out, &quart::out_in }, + { &quad::in, &quad::out, &quad::in_out, &quad::out_in }, + { &expo::in, &expo::out, &expo::in_out, &expo::out_in }, + { &elastic::in, &elastic::out, &elastic::in_out, &elastic::out_in }, + { &cubic::in, &cubic::out, &cubic::in_out, &cubic::out_in }, + { &circ::in, &circ::out, &circ::in_out, &circ::out_in }, + { &bounce::in, &bounce::out, &bounce::in_out, &bounce::out_in }, + { &back::in, &back::out, &back::in_out, &back::out_in }, +}; + void Tweener::set_tween(Ref<Tween> p_tween) { tween = p_tween; } @@ -317,6 +332,16 @@ bool Tween::should_pause() { return pause_mode != TWEEN_PAUSE_PROCESS; } +real_t Tween::run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t p_time, real_t p_initial, real_t p_delta, real_t p_duration) { + if (p_duration == 0) { + // Special case to avoid dividing by 0 in equations. + return p_initial + p_delta; + } + + interpolater func = interpolaters[p_trans_type][p_ease_type]; + return func(p_time, p_initial, p_delta, p_duration); +} + Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, float p_time, float p_duration, TransitionType p_trans, EaseType p_ease) { ERR_FAIL_INDEX_V(p_trans, TransitionType::TRANS_MAX, Variant()); ERR_FAIL_INDEX_V(p_ease, EaseType::EASE_MAX, Variant()); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 5d1106bb41..0476a764a5 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -227,17 +227,17 @@ void CodeEdit::_notification(int p_what) { end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), font_size).x; } - Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent() + font_height * i + yofs); + Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(font_size) + font_height * i + yofs); round_ofs = round_ofs.round(); draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HALIGN_LEFT, -1, font_size, font_color); if (end > 0) { // Draw an underline for the currently edited function parameter. - const Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + line_spacing); + const Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + yofs); draw_line(b, b + Vector2(end - begin, 0), font_color, 2); // Draw a translucent text highlight as well. const Rect2 highlight_rect = Rect2( - hint_ofs + sb->get_offset() + Vector2(begin, 0), + b - Vector2(0, font_height), Vector2(end - begin, font_height)); draw_rect(highlight_rect, font_color * Color(1, 1, 1, 0.2)); } @@ -1146,13 +1146,20 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const { } void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { - if (draw_breakpoints && is_line_breakpointed(p_line)) { - int padding = p_region.size.x / 6; + if (draw_breakpoints) { + bool hovering = p_region.has_point(get_local_mouse_pos()); + bool breakpointed = is_line_breakpointed(p_line); - Rect2 breakpoint_region = p_region; - breakpoint_region.position += Point2(padding, padding); - breakpoint_region.size -= Point2(padding, padding) * 2; - breakpoint_icon->draw_rect(get_canvas_item(), breakpoint_region, false, breakpoint_color); + if (breakpointed || (hovering && !is_dragging_cursor())) { + int padding = p_region.size.x / 6; + Rect2 icon_region = p_region; + icon_region.position += Point2(padding, padding); + icon_region.size -= Point2(padding, padding) * 2; + + // Darken icon when hovering & not yet breakpointed. + Color use_color = hovering && !breakpointed ? breakpoint_color.darkened(0.4) : breakpoint_color; + breakpoint_icon->draw_rect(get_canvas_item(), icon_region, false, use_color); + } } if (draw_bookmarks && is_line_bookmarked(p_line)) { diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 046715e17e..611035fff9 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -827,7 +827,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { real_t dist = center.distance_to(bev->get_position()); if (dist <= center.x) { - real_t rad = Math::atan2(bev->get_position().y - center.y, bev->get_position().x - center.x); + real_t rad = bev->get_position().angle_to_point(center); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; s = CLAMP(dist / center.x, 0, 1); } else { @@ -844,7 +844,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { real_t dist = center.distance_to(bev->get_position()); if (dist >= center.x * 0.84 && dist <= center.x) { - real_t rad = Math::atan2(bev->get_position().y - center.y, bev->get_position().x - center.x); + real_t rad = bev->get_position().angle_to_point(center); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; spinning = true; } else { @@ -889,12 +889,12 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { Vector2 center = c->get_size() / 2.0; if (picker_type == SHAPE_VHS_CIRCLE) { real_t dist = center.distance_to(mev->get_position()); - real_t rad = Math::atan2(mev->get_position().y - center.y, mev->get_position().x - center.x); + real_t rad = mev->get_position().angle_to_point(center); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; s = CLAMP(dist / center.x, 0, 1); } else { if (spinning) { - real_t rad = Math::atan2(mev->get_position().y - center.y, mev->get_position().x - center.x); + real_t rad = mev->get_position().angle_to_point(center); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; } else { real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index bca3b2059d..d9c08ec272 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -1071,10 +1071,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (mm.is_valid() && box_selecting) { box_selecting_to = mm->get_position(); - box_selecting_rect = Rect2(MIN(box_selecting_from.x, box_selecting_to.x), - MIN(box_selecting_from.y, box_selecting_to.y), - ABS(box_selecting_from.x - box_selecting_to.x), - ABS(box_selecting_from.y - box_selecting_to.y)); + box_selecting_rect = Rect2(box_selecting_from.min(box_selecting_to), (box_selecting_from - box_selecting_to).abs()); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index cbdf2a498a..ecf735d7f5 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -31,6 +31,9 @@ #include "graph_node.h" #include "core/string/translation.h" +#ifdef TOOLS_ENABLED +#include "graph_edit.h" +#endif struct _MinSizeCache { int min_size; @@ -458,6 +461,27 @@ void GraphNode::_shape() { title_buf->add_string(title, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); } +#ifdef TOOLS_ENABLED +void GraphNode::_edit_set_position(const Point2 &p_position) { + GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent()); + if (graph) { + Point2 offset = (p_position + graph->get_scroll_ofs()) * graph->get_zoom(); + set_position_offset(offset); + } + set_position(p_position); +} + +void GraphNode::_validate_property(PropertyInfo &property) const { + Control::_validate_property(property); + GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent()); + if (graph) { + if (property.name == "rect_position") { + property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } +} +#endif + void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right) { ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx)); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index c7c7006bfc..2238cfdb56 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -98,6 +98,11 @@ private: Overlay overlay = OVERLAY_DISABLED; +#ifdef TOOLS_ENABLED + void _edit_set_position(const Point2 &p_position) override; + void _validate_property(PropertyInfo &property) const override; +#endif + protected: virtual void gui_input(const Ref<InputEvent> &p_ev) override; void _notification(int p_what); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index d9acbeb828..89fd9bfc88 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -260,24 +260,29 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } else { if (selecting_enabled) { - if (!b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - selection.last_dblclk) < 600) { + const int triple_click_timeout = 600; + const int triple_click_tolerance = 5; + const bool is_triple_click = !b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && b->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance; + + if (is_triple_click && text.length()) { // Triple-click select all. selection.enabled = true; selection.begin = 0; selection.end = text.length(); selection.double_click = true; - selection.last_dblclk = 0; + last_dblclk = 0; caret_column = selection.begin; } else if (b->is_double_click()) { // Double-click select word. + last_dblclk = OS::get_singleton()->get_ticks_msec(); + last_dblclk_pos = b->get_position(); Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid); for (int i = 0; i < words.size(); i++) { - if (words[i].x < caret_column && words[i].y > caret_column) { + if ((words[i].x < caret_column && words[i].y > caret_column) || (i == words.size() - 1 && caret_column == words[i].y)) { selection.enabled = true; selection.begin = words[i].x; selection.end = words[i].y; selection.double_click = true; - selection.last_dblclk = OS::get_singleton()->get_ticks_msec(); caret_column = selection.end; break; } @@ -1555,6 +1560,20 @@ void LineEdit::deselect() { update(); } +bool LineEdit::has_selection() const { + return selection.enabled; +} + +int LineEdit::get_selection_from_column() const { + ERR_FAIL_COND_V(!selection.enabled, -1); + return selection.begin; +} + +int LineEdit::get_selection_to_column() const { + ERR_FAIL_COND_V(!selection.enabled, -1); + return selection.end; +} + void LineEdit::selection_delete() { if (selection.enabled) { delete_text(selection.begin, selection.end); @@ -2089,6 +2108,9 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("select_all"), &LineEdit::select_all); ClassDB::bind_method(D_METHOD("deselect"), &LineEdit::deselect); + ClassDB::bind_method(D_METHOD("has_selection"), &LineEdit::has_selection); + ClassDB::bind_method(D_METHOD("get_selection_from_column"), &LineEdit::get_selection_from_column); + ClassDB::bind_method(D_METHOD("get_selection_to_column"), &LineEdit::get_selection_to_column); ClassDB::bind_method(D_METHOD("set_text", "text"), &LineEdit::set_text); ClassDB::bind_method(D_METHOD("get_text"), &LineEdit::get_text); ClassDB::bind_method(D_METHOD("get_draw_control_chars"), &LineEdit::get_draw_control_chars); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index e364a79c83..923024dd56 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -136,7 +136,6 @@ private: bool creating = false; bool double_click = false; bool drag_attempt = false; - uint64_t last_dblclk = 0; } selection; struct TextOperation { @@ -153,6 +152,9 @@ private: bool pressing_inside = false; } clear_button_status; + uint64_t last_dblclk = 0; + Vector2 last_dblclk_pos; + bool caret_blink_enabled = false; bool caret_force_displayed = false; bool draw_caret = true; @@ -229,6 +231,9 @@ public: void select_all(); void selection_delete(); void deselect(); + bool has_selection() const; + int get_selection_from_column() const; + int get_selection_to_column() const; void delete_char(); void delete_text(int p_from_column, int p_to_column); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 3eaae5181d..277026cc28 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -2378,8 +2378,7 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, item->size.width = p_image->get_width() * p_height / p_image->get_height(); } else { // keep original width and height - item->size.height = p_image->get_height(); - item->size.width = p_image->get_width(); + item->size = p_image->get_size(); } } diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index a423dc0173..c8a0501d8a 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -79,7 +79,7 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { Popup *popup = get_popup(); if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - Point2 pos(mb->get_position().x, mb->get_position().y); + Point2 pos = mb->get_position(); Size2 size = get_size(); // Click must be on tabs in the tab header area. @@ -190,7 +190,7 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - Point2 pos(mm->get_position().x, mm->get_position().y); + Point2 pos = mm->get_position(); Size2 size = get_size(); // Mouse must be on tabs in the tab header area. @@ -1210,6 +1210,8 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled); ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabContainer::get_tab_disabled); + ClassDB::bind_method(D_METHOD("set_tab_hidden", "tab_idx", "hidden"), &TabContainer::set_tab_hidden); + ClassDB::bind_method(D_METHOD("get_tab_hidden", "tab_idx"), &TabContainer::get_tab_hidden); ClassDB::bind_method(D_METHOD("get_tab_idx_at_point", "point"), &TabContainer::get_tab_idx_at_point); ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup); ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup); diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index 3ca2d1c1e9..ef34bec347 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -98,29 +98,45 @@ void Tabs::gui_input(const Ref<InputEvent> &p_event) { if (mm.is_valid()) { Point2 pos = mm->get_position(); - highlight_arrow = -1; if (buttons_visible) { Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); if (is_layout_rtl()) { if (pos.x < decr->get_width()) { - highlight_arrow = 1; + if (highlight_arrow != 1) { + highlight_arrow = 1; + update(); + } } else if (pos.x < incr->get_width() + decr->get_width()) { - highlight_arrow = 0; + if (highlight_arrow != 0) { + highlight_arrow = 0; + update(); + } + } else if (highlight_arrow != -1) { + highlight_arrow = -1; + update(); } } else { int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); if (pos.x > limit_minus_buttons + decr->get_width()) { - highlight_arrow = 1; + if (highlight_arrow != 1) { + highlight_arrow = 1; + update(); + } } else if (pos.x > limit_minus_buttons) { - highlight_arrow = 0; + if (highlight_arrow != 0) { + highlight_arrow = 0; + update(); + } + } else if (highlight_arrow != -1) { + highlight_arrow = -1; + update(); } } } _update_hover(); - update(); return; } @@ -167,7 +183,7 @@ void Tabs::gui_input(const Ref<InputEvent> &p_event) { if (mb->is_pressed() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (select_with_rmb && mb->get_button_index() == MOUSE_BUTTON_RIGHT))) { // clicks - Point2 pos(mb->get_position().x, mb->get_position().y); + Point2 pos = mb->get_position(); if (buttons_visible) { Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); @@ -241,6 +257,7 @@ void Tabs::_shape(int p_tab) { tabs.write[p_tab].xl_text = atr(tabs[p_tab].text); tabs.write[p_tab].text_buf->clear(); + tabs.write[p_tab].text_buf->set_width(-1); if (tabs[p_tab].text_direction == Control::TEXT_DIRECTION_INHERITED) { tabs.write[p_tab].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); } else { @@ -529,7 +546,6 @@ bool Tabs::get_offset_buttons_visible() const { void Tabs::set_tab_title(int p_tab, const String &p_title) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].text = p_title; - tabs.write[p_tab].xl_text = atr(p_title); _shape(p_tab); update(); minimum_size_changed(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 09899413f2..5e165be15e 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1636,6 +1636,32 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } } + + // Check if user is hovering a different gutter, and update if yes. + Vector2i current_hovered_gutter = Vector2i(-1, -1); + + int left_margin = style_normal->get_margin(SIDE_LEFT); + if (mpos.x <= left_margin + gutters_width + gutter_padding) { + int hovered_row = get_line_column_at_pos(mpos).y; + for (int i = 0; i < gutters.size(); i++) { + if (!gutters[i].draw || gutters[i].width <= 0) { + continue; + } + + if (mpos.x > left_margin && mpos.x <= (left_margin + gutters[i].width) - 3) { + // We are in this gutter i's horizontal area. + current_hovered_gutter = Vector2i(i, hovered_row); + break; + } + + left_margin += gutters[i].width; + } + } + + if (current_hovered_gutter != hovered_gutter) { + hovered_gutter = current_hovered_gutter; + update(); + } } if (draw_minimap && !dragging_selection) { @@ -2731,7 +2757,10 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) { } void TextEdit::insert_text_at_caret(const String &p_text) { - begin_complex_operation(); + bool had_selection = has_selection(); + if (had_selection) { + begin_complex_operation(); + } delete_selection(); @@ -2743,7 +2772,9 @@ void TextEdit::insert_text_at_caret(const String &p_text) { set_caret_column(new_column); update(); - end_complex_operation(); + if (had_selection) { + end_complex_operation(); + } } void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { @@ -3689,7 +3720,7 @@ void TextEdit::select_word_under_caret() { int end = 0; const Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); for (int i = 0; i < words.size(); i++) { - if (words[i].x <= caret.column && words[i].y >= caret.column) { + if ((words[i].x < caret.column && words[i].y > caret.column) || (i == words.size() - 1 && caret.column == words[i].y)) { begin = words[i].x; end = words[i].y; break; @@ -5411,7 +5442,7 @@ void TextEdit::_update_selection_mode_word() { int end = beg; Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); for (int i = 0; i < words.size(); i++) { - if (words[i].x < caret_pos && words[i].y > caret_pos) { + if ((words[i].x < caret_pos && words[i].y > caret_pos) || (i == words.size() - 1 && caret_pos == words[i].y)) { beg = words[i].x; end = words[i].y; break; diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index b1226f2aff..db16d09c58 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -477,6 +477,7 @@ private: Vector<GutterInfo> gutters; int gutters_width = 0; int gutter_padding = 0; + Vector2i hovered_gutter = Vector2i(-1, -1); // X = gutter index, Y = row. void _update_gutter_width(); diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 286f01ee33..35a098c6fd 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -471,7 +471,7 @@ void TextureProgressBar::_notification(int p_what) { Vector<Point2> uvs; Vector<Point2> points; uvs.push_back(get_relative_center()); - points.push_back(progress_offset + Point2(s.x * get_relative_center().x, s.y * get_relative_center().y)); + points.push_back(progress_offset + s * get_relative_center()); for (int i = 0; i < pts.size(); i++) { Point2 uv = unit_val_to_uv(pts[i]); if (uvs.find(uv) >= 0) { @@ -493,8 +493,7 @@ void TextureProgressBar::_notification(int p_what) { p = progress->get_size(); } - p.x *= get_relative_center().x; - p.y *= get_relative_center().y; + p *= get_relative_center(); p = p.floor(); draw_line(p - Point2(8, 0), p + Point2(8, 0), Color(0.9, 0.5, 0.5), 2); draw_line(p - Point2(0, 8), p + Point2(0, 8), Color(0.9, 0.5, 0.5), 2); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index f62c09925d..7d7596635c 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -144,6 +144,7 @@ void TreeItem::_change_tree(Tree *p_tree) { /* cell mode */ void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) { ERR_FAIL_INDEX(p_column, cells.size()); + Cell &c = cells.write[p_column]; c.mode = p_mode; c.min = 0; @@ -155,8 +156,9 @@ void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) { c.text = ""; c.dirty = true; c.icon_max_w = 0; + c.cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { @@ -167,22 +169,27 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { /* check mode */ void TreeItem::set_checked(int p_column, bool p_checked) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].checked = p_checked; cells.write[p_column].indeterminate = false; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } void TreeItem::set_indeterminate(int p_column, bool p_indeterminate) { ERR_FAIL_INDEX(p_column, cells.size()); + // Prevent uncheck if indeterminate set to false twice if (p_indeterminate == cells[p_column].indeterminate) { return; } + cells.write[p_column].indeterminate = p_indeterminate; cells.write[p_column].checked = false; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } bool TreeItem::is_checked(int p_column) const { @@ -214,8 +221,10 @@ void TreeItem::set_text(int p_column, String p_text) { } cells.write[p_column].step = 0; } + + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } String TreeItem::get_text(int p_column) const { @@ -231,7 +240,7 @@ void TreeItem::set_text_direction(int p_column, Control::TextDirection p_text_di cells.write[p_column].dirty = true; _changed_notify(p_column); } - cached_minimum_size_dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; } Control::TextDirection TreeItem::get_text_direction(int p_column) const { @@ -241,10 +250,12 @@ Control::TextDirection TreeItem::get_text_direction(int p_column) const { void TreeItem::clear_opentype_features(int p_column) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].opentype_features.clear(); cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_value) { @@ -253,8 +264,9 @@ void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_va if (!cells[p_column].opentype_features.has(tag) || (int)cells[p_column].opentype_features[tag] != p_value) { cells.write[p_column].opentype_features[tag] = p_value; cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } } @@ -269,11 +281,13 @@ int TreeItem::get_opentype_feature(int p_column, const String &p_name) const { void TreeItem::set_structured_text_bidi_override(int p_column, Control::StructuredTextParser p_parser) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].st_parser != p_parser) { cells.write[p_column].st_parser = p_parser; cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } } @@ -284,10 +298,12 @@ Control::StructuredTextParser TreeItem::get_structured_text_bidi_override(int p_ void TreeItem::set_structured_text_bidi_override_options(int p_column, Array p_args) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].st_args = p_args; cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } Array TreeItem::get_structured_text_bidi_override_options(int p_column) const { @@ -297,11 +313,13 @@ Array TreeItem::get_structured_text_bidi_override_options(int p_column) const { void TreeItem::set_language(int p_column, const String &p_language) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].language != p_language) { cells.write[p_column].language = p_language; cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } } @@ -312,10 +330,11 @@ String TreeItem::get_language(int p_column) const { void TreeItem::set_suffix(int p_column, String p_suffix) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].suffix = p_suffix; + cells.write[p_column].cached_minimum_size_dirty = true; _changed_notify(p_column); - cached_minimum_size_dirty = true; } String TreeItem::get_suffix(int p_column) const { @@ -325,9 +344,11 @@ String TreeItem::get_suffix(int p_column) const { void TreeItem::set_icon(int p_column, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].icon = p_icon; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } Ref<Texture2D> TreeItem::get_icon(int p_column) const { @@ -337,9 +358,11 @@ Ref<Texture2D> TreeItem::get_icon(int p_column) const { void TreeItem::set_icon_region(int p_column, const Rect2 &p_icon_region) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].icon_region = p_icon_region; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } Rect2 TreeItem::get_icon_region(int p_column) const { @@ -360,9 +383,11 @@ Color TreeItem::get_icon_modulate(int p_column) const { void TreeItem::set_icon_max_width(int p_column, int p_max) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].icon_max_w = p_max; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } int TreeItem::get_icon_max_width(int p_column) const { @@ -474,8 +499,11 @@ void TreeItem::uncollapse_tree() { void TreeItem::set_custom_minimum_height(int p_height) { custom_min_height = p_height; + + for (Cell &c : cells) + c.cached_minimum_size_dirty = true; + _changed_notify(); - cached_minimum_size_dirty = true; } int TreeItem::get_custom_minimum_height() const { @@ -799,8 +827,9 @@ void TreeItem::add_button(int p_column, const Ref<Texture2D> &p_button, int p_id button.disabled = p_disabled; button.tooltip = p_tooltip; cells.write[p_column].buttons.push_back(button); + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } int TreeItem::get_button_count(int p_column) const { @@ -843,8 +872,9 @@ void TreeItem::set_button(int p_column, int p_idx, const Ref<Texture2D> &p_butto ERR_FAIL_INDEX(p_column, cells.size()); ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); cells.write[p_column].buttons.write[p_idx].texture = p_button; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } void TreeItem::set_button_color(int p_column, int p_idx, const Color &p_color) { @@ -859,8 +889,9 @@ void TreeItem::set_button_disabled(int p_column, int p_idx, bool p_disabled) { ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); cells.write[p_column].buttons.write[p_idx].disabled = p_disabled; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } bool TreeItem::is_button_disabled(int p_column, int p_idx) const { @@ -872,9 +903,11 @@ bool TreeItem::is_button_disabled(int p_column, int p_idx) const { void TreeItem::set_editable(int p_column, bool p_editable) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].editable = p_editable; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } bool TreeItem::is_editable(int p_column) { @@ -906,8 +939,9 @@ void TreeItem::clear_custom_color(int p_column) { void TreeItem::set_custom_font(int p_column, const Ref<Font> &p_font) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].custom_font = p_font; - cached_minimum_size_dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; } Ref<Font> TreeItem::get_custom_font(int p_column) const { @@ -917,8 +951,9 @@ Ref<Font> TreeItem::get_custom_font(int p_column) const { void TreeItem::set_custom_font_size(int p_column, int p_font_size) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].custom_font_size = p_font_size; - cached_minimum_size_dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; } int TreeItem::get_custom_font_size(int p_column) const { @@ -961,8 +996,9 @@ Color TreeItem::get_custom_bg_color(int p_column) const { void TreeItem::set_custom_as_button(int p_column, bool p_button) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].custom_button = p_button; - cached_minimum_size_dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; } bool TreeItem::is_custom_set_as_button(int p_column) const { @@ -972,9 +1008,11 @@ bool TreeItem::is_custom_set_as_button(int p_column) const { void TreeItem::set_text_align(int p_column, TextAlign p_align) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].text_align = p_align; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } TreeItem::TextAlign TreeItem::get_text_align(int p_column) const { @@ -984,9 +1022,11 @@ TreeItem::TextAlign TreeItem::get_text_align(int p_column) const { void TreeItem::set_expand_right(int p_column, bool p_enable) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].expand_right = p_enable; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } bool TreeItem::get_expand_right(int p_column) const { @@ -996,8 +1036,11 @@ bool TreeItem::get_expand_right(int p_column) const { void TreeItem::set_disable_folding(bool p_disable) { disable_folding = p_disable; + + for (Cell &c : cells) + c.cached_minimum_size_dirty = true; + _changed_notify(0); - cached_minimum_size_dirty = true; } bool TreeItem::is_folding_disabled() const { @@ -1009,14 +1052,12 @@ Size2 TreeItem::get_minimum_size(int p_column) { Tree *tree = get_tree(); ERR_FAIL_COND_V(!tree, Size2()); - if (cached_minimum_size_dirty) { - Size2 size; + const TreeItem::Cell &cell = cells[p_column]; - // Default offset? - //size.width += (disable_folding || tree->hide_folding) ? tree->cache.hseparation : tree->cache.item_margin; + if (cell.cached_minimum_size_dirty) { + Size2 size; // Text. - const TreeItem::Cell &cell = cells[p_column]; if (!cell.text.is_empty()) { if (cell.dirty) { tree->update_item_cell(this, p_column); @@ -1052,11 +1093,11 @@ Size2 TreeItem::get_minimum_size(int p_column) { size.width += (cell.buttons.size() - 1) * tree->cache.button_margin; } - cached_minimum_size = size; - cached_minimum_size_dirty = false; + cells.write[p_column].cached_minimum_size = size; + cells.write[p_column].cached_minimum_size_dirty = false; } - return cached_minimum_size; + return cell.cached_minimum_size; } Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { @@ -1337,10 +1378,6 @@ void Tree::update_cache() { cache.title_button_color = get_theme_color(SNAME("title_button_color")); v_scroll->set_custom_step(cache.font->get_height(cache.font_size)); - - for (TreeItem *item = get_root(); item; item = item->get_next()) { - item->cached_minimum_size_dirty = true; - } } int Tree::compute_item_height(TreeItem *p_item) const { @@ -1712,7 +1749,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } if ((select_mode == SELECT_ROW && selected_item == p_item) || p_item->cells[i].selected || !p_item->has_meta("__focus_rect")) { - Rect2i r(cell_rect.position, cell_rect.size); + Rect2i r = cell_rect; p_item->set_meta("__focus_rect", Rect2(r.position, r.size)); @@ -1968,7 +2005,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 arrow = cache.arrow; } - Point2 apos = p_pos + p_draw_ofs + Point2i(0, (label_h - arrow->get_height()) / 2) - cache.offset; + Point2 apos = p_pos + Point2i(0, (label_h - arrow->get_height()) / 2) - cache.offset + p_draw_ofs; + apos.x += cache.item_margin - arrow->get_width(); if (rtl) { apos.x = get_size().width - apos.x - arrow->get_width(); @@ -4000,10 +4038,12 @@ TreeItem *Tree::get_next_selected(TreeItem *p_item) { int Tree::get_column_minimum_width(int p_column) const { ERR_FAIL_INDEX_V(p_column, columns.size(), -1); + // Use the custom minimum width. int min_width = columns[p_column].custom_min_width; + // Check if the visible title of the column is wider. if (show_column_titles) { - min_width = MAX(cache.font->get_string_size(columns[p_column].title).width, min_width); + min_width = MAX(cache.font->get_string_size(columns[p_column].title).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width); } if (!columns[p_column].clip_content) { @@ -4028,7 +4068,11 @@ int Tree::get_column_minimum_width(int p_column) const { Size2 item_size = item->get_minimum_size(p_column); if (p_column == 0) { item_size.width += cache.item_margin * depth; + } else { + item_size.width += cache.hseparation; } + + // Check if the item is wider. min_width = MAX(min_width, item_size.width); } } @@ -4068,9 +4112,6 @@ int Tree::get_column_width(int p_column) const { } } - if (p_column < columns.size() - 1) { - column_width += cache.hseparation; - } return column_width; } diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 85fed941dc..c4a6b6b058 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -95,6 +95,9 @@ private: bool expand_right = false; Color icon_color = Color(1, 1, 1); + Size2i cached_minimum_size; + bool cached_minimum_size_dirty = true; + TextAlign text_align = ALIGN_LEFT; Variant meta; @@ -130,9 +133,6 @@ private: bool disable_folding = false; int custom_min_height = 0; - Size2i cached_minimum_size; - bool cached_minimum_size_dirty = true; - TreeItem *parent = nullptr; // parent item TreeItem *prev = nullptr; // previous in list TreeItem *next = nullptr; // next in list diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index f24d880045..14fd14dd18 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -104,9 +104,11 @@ Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_h CharString charstr = p_request_data.utf8(); size_t len = charstr.length(); - raw_data.resize(len); - uint8_t *w = raw_data.ptrw(); - memcpy(w, charstr.ptr(), len); + if (len > 0) { + raw_data.resize(len); + uint8_t *w = raw_data.ptrw(); + memcpy(w, charstr.ptr(), len); + } return request_raw(p_url, p_custom_headers, p_ssl_validate_domain, p_method, raw_data); } diff --git a/scene/main/node.cpp b/scene/main/node.cpp index f5d2d2f2a2..db5f714b99 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -114,8 +114,8 @@ void Node::_notification(int p_notification) { memdelete(data.path_cache); data.path_cache = nullptr; } - if (data.filename.length()) { - get_multiplayer()->scene_enter_exit_notify(data.filename, this, false); + if (data.scene_file_path.length()) { + get_multiplayer()->scene_enter_exit_notify(data.scene_file_path, this, false); } } break; case NOTIFICATION_PATH_CHANGED: { @@ -146,9 +146,9 @@ void Node::_notification(int p_notification) { GDVIRTUAL_CALL(_ready); - if (data.filename.length()) { + if (data.scene_file_path.length()) { ERR_FAIL_COND(!is_inside_tree()); - get_multiplayer()->scene_enter_exit_notify(data.filename, this, true); + get_multiplayer()->scene_enter_exit_notify(data.scene_file_path, this, true); } } break; @@ -235,7 +235,7 @@ void Node::_propagate_enter_tree() { data.blocked--; #ifdef DEBUG_ENABLED - SceneDebugger::add_to_cache(data.filename, this); + SceneDebugger::add_to_cache(data.scene_file_path, this); #endif // enter groups } @@ -253,7 +253,7 @@ void Node::_propagate_exit_tree() { //block while removing children #ifdef DEBUG_ENABLED - SceneDebugger::remove_from_cache(data.filename, this); + SceneDebugger::remove_from_cache(data.scene_file_path, this); #endif data.blocked++; @@ -1846,12 +1846,12 @@ void Node::remove_and_skip() { data.parent->remove_child(this); } -void Node::set_filename(const String &p_filename) { - data.filename = p_filename; +void Node::set_scene_file_path(const String &p_scene_file_path) { + data.scene_file_path = p_scene_file_path; } -String Node::get_filename() const { - return data.filename; +String Node::get_scene_file_path() const { + return data.scene_file_path; } void Node::set_editor_description(const String &p_editor_description) { @@ -1948,8 +1948,8 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const nip->set_instance_path(ip->get_instance_path()); node = nip; - } else if ((p_flags & DUPLICATE_USE_INSTANCING) && get_filename() != String()) { - Ref<PackedScene> res = ResourceLoader::load(get_filename()); + } else if ((p_flags & DUPLICATE_USE_INSTANCING) && get_scene_file_path() != String()) { + Ref<PackedScene> res = ResourceLoader::load(get_scene_file_path()); ERR_FAIL_COND_V(res.is_null(), nullptr); PackedScene::GenEditState ges = PackedScene::GEN_EDIT_STATE_DISABLED; #ifdef TOOLS_ENABLED @@ -1972,8 +1972,8 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const ERR_FAIL_COND_V(!node, nullptr); } - if (get_filename() != "") { //an instance - node->set_filename(get_filename()); + if (get_scene_file_path() != "") { //an instance + node->set_scene_file_path(get_scene_file_path()); node->data.editable_instance = data.editable_instance; } @@ -2004,7 +2004,7 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const node_tree.push_back(descendant); - if (descendant->get_filename() != "" && instance_roots.has(descendant->get_owner())) { + if (descendant->get_scene_file_path() != "" && instance_roots.has(descendant->get_owner())) { instance_roots.push_back(descendant); } } @@ -2313,7 +2313,7 @@ void Node::replace_by(Node *p_node, bool p_keep_groups) { owned_by_owner[i]->set_owner(owner); } - p_node->set_filename(get_filename()); + p_node->set_scene_file_path(get_scene_file_path()); } void Node::_replace_connections_target(Node *p_new_target) { @@ -2693,8 +2693,8 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("get_index", "include_internal"), &Node::get_index, DEFVAL(false)); ClassDB::bind_method(D_METHOD("print_tree"), &Node::print_tree); ClassDB::bind_method(D_METHOD("print_tree_pretty"), &Node::print_tree_pretty); - ClassDB::bind_method(D_METHOD("set_filename", "filename"), &Node::set_filename); - ClassDB::bind_method(D_METHOD("get_filename"), &Node::get_filename); + ClassDB::bind_method(D_METHOD("set_scene_file_path", "scene_file_path"), &Node::set_scene_file_path); + ClassDB::bind_method(D_METHOD("get_scene_file_path"), &Node::get_scene_file_path); ClassDB::bind_method(D_METHOD("propagate_notification", "what"), &Node::propagate_notification); ClassDB::bind_method(D_METHOD("propagate_call", "method", "args", "parent_first"), &Node::propagate_call, DEFVAL(Array()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_physics_process", "enable"), &Node::set_physics_process); @@ -2839,7 +2839,7 @@ void Node::_bind_methods() { ADD_SIGNAL(MethodInfo("tree_exited")); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_name", "get_name"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "filename", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_filename", "get_filename"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_file_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_file_path", "get_scene_file_path"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_owner", "get_owner"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "", "get_multiplayer"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "set_custom_multiplayer", "get_custom_multiplayer"); diff --git a/scene/main/node.h b/scene/main/node.h index 198501eeac..7d4c79cfba 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -96,7 +96,7 @@ private: }; struct Data { - String filename; + String scene_file_path; Ref<SceneState> instance_state; Ref<SceneState> inherited_state; @@ -353,8 +353,8 @@ public: void print_tree(); void print_tree_pretty(); - void set_filename(const String &p_filename); - String get_filename() const; + void set_scene_file_path(const String &p_scene_file_path); + String get_scene_file_path() const; void set_editor_description(const String &p_editor_description); String get_editor_description() const; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 6e78193717..3d07e4473d 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -1105,7 +1105,7 @@ Error SceneTree::change_scene_to(const Ref<PackedScene> &p_scene) { Error SceneTree::reload_current_scene() { ERR_FAIL_COND_V(!current_scene, ERR_UNCONFIGURED); - String fname = current_scene->get_filename(); + String fname = current_scene->get_scene_file_path(); return change_scene(fname); } diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 59faa50114..9722570060 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -384,7 +384,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // save the child instantiated scenes that are chosen as editable, so they can be restored // upon load back - if (p_node != p_owner && p_node->get_filename() != String() && p_owner->is_editable_instance(p_node)) { + if (p_node != p_owner && p_node->get_scene_file_path() != String() && p_owner->is_editable_instance(p_node)) { editable_instances.push_back(p_owner->get_path_to(p_node)); // Node is the root of an editable instance. is_editable_instance = true; @@ -437,14 +437,14 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map } } - if (p_node->get_filename() != String() && p_node->get_owner() == p_owner && instantiated_by_owner) { + if (p_node->get_scene_file_path() != String() && p_node->get_owner() == p_owner && instantiated_by_owner) { if (p_node->get_scene_instance_load_placeholder()) { //it's a placeholder, use the placeholder path - nd.instance = _vm_get_variant(p_node->get_filename(), variant_map); + nd.instance = _vm_get_variant(p_node->get_scene_file_path(), variant_map); nd.instance |= FLAG_INSTANCE_IS_PLACEHOLDER; } else { //must instance ourselves - Ref<PackedScene> instance = ResourceLoader::load(p_node->get_filename()); + Ref<PackedScene> instance = ResourceLoader::load(p_node->get_scene_file_path()); if (!instance.is_valid()) { return ERR_CANT_OPEN; } @@ -454,7 +454,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map } n = nullptr; } else { - if (n->get_filename() != String()) { + if (n->get_scene_file_path() != String()) { //is an instance Ref<SceneState> state = n->get_scene_instance_state(); if (state.is_valid()) { @@ -714,7 +714,7 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName ERR_CONTINUE(!common_parent); - if (common_parent != p_owner && common_parent->get_filename() == String()) { + if (common_parent != p_owner && common_parent->get_scene_file_path() == String()) { common_parent = common_parent->get_owner(); } @@ -774,7 +774,7 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName nl = nullptr; } else { - if (nl->get_filename() != String()) { + if (nl->get_scene_file_path() != String()) { //is an instance Ref<SceneState> state = nl->get_scene_instance_state(); if (state.is_valid()) { @@ -1652,7 +1652,7 @@ Node *PackedScene::instantiate(GenEditState p_edit_state) const { } if (get_path() != "" && get_path().find("::") == -1) { - s->set_filename(get_path()); + s->set_scene_file_path(get_path()); } s->notification(Node::NOTIFICATION_INSTANCED); diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index e7da41db9d..f8be00f5fb 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -1390,6 +1390,12 @@ void QuadMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset"), "set_center_offset", "get_center_offset"); } +uint32_t QuadMesh::surface_get_format(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, 1, 0); + + return RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL | RS::ARRAY_FORMAT_TANGENT | RS::ARRAY_FORMAT_TEX_UV; +} + QuadMesh::QuadMesh() { primitive_type = PRIMITIVE_TRIANGLES; } @@ -1460,7 +1466,7 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { } else { Vector3 p = Vector3(x * radius * w, y, z * radius * w); points.push_back(p); - Vector3 normal = Vector3(x * radius * w * scale, y / scale, z * radius * w * scale); + Vector3 normal = Vector3(x * w * scale, radius * (y / scale), z * w * scale); normals.push_back(normal.normalized()); }; ADD_TANGENT(z, 0.0, -x, 1.0) diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index 7915cb0028..d447dad97a 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -285,6 +285,8 @@ protected: virtual void _create_mesh_array(Array &p_arr) const override; public: + virtual uint32_t surface_get_format(int p_idx) const override; + QuadMesh(); void set_size(const Size2 &p_size); diff --git a/scene/resources/skeleton_modification_2d.cpp b/scene/resources/skeleton_modification_2d.cpp index e533fb054a..7ac40b497d 100644 --- a/scene/resources/skeleton_modification_2d.cpp +++ b/scene/resources/skeleton_modification_2d.cpp @@ -96,37 +96,25 @@ float SkeletonModification2D::clamp_angle(float p_angle, float p_min_bound, floa p_max_bound = Math_TAU + p_max_bound; } if (p_min_bound > p_max_bound) { - float tmp = p_min_bound; - p_min_bound = p_max_bound; - p_max_bound = tmp; + SWAP(p_min_bound, p_max_bound); } + bool is_beyond_bounds = (p_angle < p_min_bound || p_angle > p_max_bound); + bool is_within_bounds = (p_angle > p_min_bound && p_angle < p_max_bound); + // Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle. - if (p_invert == false) { - if (p_angle < p_min_bound || p_angle > p_max_bound) { - Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); - Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); - Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); - - if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { - p_angle = p_min_bound; - } else { - p_angle = p_max_bound; - } - } - } else { - if (p_angle > p_min_bound && p_angle < p_max_bound) { - Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); - Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); - Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); + if ((!p_invert && is_beyond_bounds) || (p_invert && is_within_bounds)) { + Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); + Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); + Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); - if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { - p_angle = p_min_bound; - } else { - p_angle = p_max_bound; - } + if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { + p_angle = p_min_bound; + } else { + p_angle = p_max_bound; } } + return p_angle; } @@ -152,9 +140,7 @@ void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_b arc_angle_max = (Math_PI * 2) + arc_angle_max; } if (arc_angle_min > arc_angle_max) { - float tmp = arc_angle_min; - arc_angle_min = arc_angle_max; - arc_angle_max = tmp; + SWAP(arc_angle_min, arc_angle_max); } arc_angle_min += p_operation_bone->get_bone_angle(); arc_angle_max += p_operation_bone->get_bone_angle(); diff --git a/scene/resources/skeleton_modification_2d_fabrik.cpp b/scene/resources/skeleton_modification_2d_fabrik.cpp index 6e9429034f..3b5c555f89 100644 --- a/scene/resources/skeleton_modification_2d_fabrik.cpp +++ b/scene/resources/skeleton_modification_2d_fabrik.cpp @@ -247,7 +247,7 @@ void SkeletonModification2DFABRIK::chain_backwards() { } float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); - float length = current_bone2d_node_length / (previous_pose.get_origin() - current_pose.get_origin()).length(); + float length = current_bone2d_node_length / (current_pose.get_origin().distance_to(previous_pose.get_origin())); Vector2 finish_position = previous_pose.get_origin().lerp(current_pose.get_origin(), length); current_pose.set_origin(finish_position); @@ -268,7 +268,7 @@ void SkeletonModification2DFABRIK::chain_forwards() { Transform2D next_pose = fabrik_transform_chain[i + 1]; float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); - float length = current_bone2d_node_length / (current_pose.get_origin() - next_pose.get_origin()).length(); + float length = current_bone2d_node_length / (next_pose.get_origin().distance_to(current_pose.get_origin())); Vector2 finish_position = current_pose.get_origin().lerp(next_pose.get_origin(), length); current_pose.set_origin(finish_position); diff --git a/scene/resources/skeleton_modification_2d_twoboneik.cpp b/scene/resources/skeleton_modification_2d_twoboneik.cpp index 88d80a501f..4f752896a9 100644 --- a/scene/resources/skeleton_modification_2d_twoboneik.cpp +++ b/scene/resources/skeleton_modification_2d_twoboneik.cpp @@ -144,7 +144,7 @@ void SkeletonModification2DTwoBoneIK::_execute(float p_delta) { // With modifications by TwistedTwigleg Vector2 target_difference = target->get_global_position() - joint_one_bone->get_global_position(); float joint_one_to_target = target_difference.length(); - float angle_atan = Math::atan2(target_difference.y, target_difference.x); + float angle_atan = target_difference.angle(); float bone_one_length = joint_one_bone->get_length() * MIN(joint_one_bone->get_global_scale().x, joint_one_bone->get_global_scale().y); float bone_two_length = joint_two_bone->get_length() * MIN(joint_two_bone->get_global_scale().x, joint_two_bone->get_global_scale().y); diff --git a/scene/resources/skeleton_modification_3d.cpp b/scene/resources/skeleton_modification_3d.cpp index ee02ede2d5..b476952d86 100644 --- a/scene/resources/skeleton_modification_3d.cpp +++ b/scene/resources/skeleton_modification_3d.cpp @@ -72,37 +72,25 @@ real_t SkeletonModification3D::clamp_angle(real_t p_angle, real_t p_min_bound, r p_max_bound = Math_TAU + p_max_bound; } if (p_min_bound > p_max_bound) { - real_t tmp = p_min_bound; - p_min_bound = p_max_bound; - p_max_bound = tmp; + SWAP(p_min_bound, p_max_bound); } + bool is_beyond_bounds = (p_angle < p_min_bound || p_angle > p_max_bound); + bool is_within_bounds = (p_angle > p_min_bound && p_angle < p_max_bound); + // Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle. - if (p_invert == false) { - if (p_angle < p_min_bound || p_angle > p_max_bound) { - Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); - Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); - Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); - - if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { - p_angle = p_min_bound; - } else { - p_angle = p_max_bound; - } - } - } else { - if (p_angle > p_min_bound && p_angle < p_max_bound) { - Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); - Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); - Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); - - if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { - p_angle = p_min_bound; - } else { - p_angle = p_max_bound; - } + if ((!p_invert && is_beyond_bounds) || (p_invert && is_within_bounds)) { + Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); + Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); + Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); + + if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { + p_angle = p_min_bound; + } else { + p_angle = p_max_bound; } } + return p_angle; } diff --git a/scene/resources/skeleton_modification_3d_fabrik.cpp b/scene/resources/skeleton_modification_3d_fabrik.cpp index 69f75eb7b5..e615615924 100644 --- a/scene/resources/skeleton_modification_3d_fabrik.cpp +++ b/scene/resources/skeleton_modification_3d_fabrik.cpp @@ -232,7 +232,7 @@ void SkeletonModification3DFABRIK::chain_backwards() { int current_bone_idx = fabrik_data_chain[i].bone_idx; Transform3D current_trans = stack->skeleton->local_pose_to_global_pose(current_bone_idx, stack->skeleton->get_bone_local_pose_override(current_bone_idx)); - real_t length = fabrik_data_chain[i].length / (next_bone_trans.origin - current_trans.origin).length(); + real_t length = fabrik_data_chain[i].length / (current_trans.origin.distance_to(next_bone_trans.origin)); current_trans.origin = next_bone_trans.origin.lerp(current_trans.origin, length); // Apply it back to the skeleton @@ -253,7 +253,7 @@ void SkeletonModification3DFABRIK::chain_forwards() { int next_bone_idx = fabrik_data_chain[i + 1].bone_idx; Transform3D next_bone_trans = stack->skeleton->local_pose_to_global_pose(next_bone_idx, stack->skeleton->get_bone_local_pose_override(next_bone_idx)); - real_t length = fabrik_data_chain[i].length / (current_trans.origin - next_bone_trans.origin).length(); + real_t length = fabrik_data_chain[i].length / (next_bone_trans.origin.distance_to(current_trans.origin)); next_bone_trans.origin = current_trans.origin.lerp(next_bone_trans.origin, length); // Apply it back to the skeleton diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index d5e370568d..a8cd872408 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -1176,6 +1176,7 @@ Vector<int> SurfaceTool::generate_lod(float p_threshold, int p_target_index_coun ERR_FAIL_COND_V(simplify_func == nullptr, lod); ERR_FAIL_COND_V(vertex_array.size() == 0, lod); ERR_FAIL_COND_V(index_array.size() == 0, lod); + ERR_FAIL_COND_V(index_array.size() % 3 != 0, lod); lod.resize(index_array.size()); LocalVector<float> vertices; //uses floats diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 3dc32632cc..1b3a7a5f2a 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -1248,6 +1248,14 @@ bool AtlasTexture::is_pixel_opaque(int p_x, int p_y) const { return atlas->is_pixel_opaque(x, y); } +Ref<Image> AtlasTexture::get_image() const { + if (!atlas.is_valid() || !atlas->get_image().is_valid()) { + return Ref<Image>(); + } + + return atlas->get_image()->get_rect(region); +} + AtlasTexture::AtlasTexture() {} ///////////////////////////////////////// diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 93f4e2de5a..862b9a47a6 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -250,6 +250,8 @@ public: bool is_pixel_opaque(int p_x, int p_y) const override; + virtual Ref<Image> get_image() const override; + AtlasTexture(); }; diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 67cbd0e094..15e622c9d6 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -1019,20 +1019,25 @@ Vector<Vector2> TileSet::get_tile_shape_polygon() { void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled, Ref<Texture2D> p_texture) { if (tile_meshes_dirty) { - Vector<Vector2> uvs = get_tile_shape_polygon(); + Vector<Vector2> shape = get_tile_shape_polygon(); + Vector<Vector2> uvs; + uvs.resize(shape.size()); + for (int i = 0; i < shape.size(); i++) { + uvs.write[i] = shape[i] + Vector2(0.5, 0.5); + } Vector<Color> colors; - colors.resize(uvs.size()); + colors.resize(shape.size()); colors.fill(Color(1.0, 1.0, 1.0, 1.0)); // Filled mesh. tile_filled_mesh->clear_surfaces(); Array a; a.resize(Mesh::ARRAY_MAX); - a[Mesh::ARRAY_VERTEX] = uvs; + a[Mesh::ARRAY_VERTEX] = shape; a[Mesh::ARRAY_TEX_UV] = uvs; a[Mesh::ARRAY_COLOR] = colors; - a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(uvs); + a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(shape); tile_filled_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); // Lines mesh. @@ -1040,9 +1045,9 @@ void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform a.clear(); a.resize(Mesh::ARRAY_MAX); // Add the first point again when drawing lines. - uvs.push_back(uvs[0]); + shape.push_back(shape[0]); colors.push_back(colors[0]); - a[Mesh::ARRAY_VERTEX] = uvs; + a[Mesh::ARRAY_VERTEX] = shape; a[Mesh::ARRAY_COLOR] = colors; tile_lines_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINE_STRIP, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); @@ -3119,7 +3124,7 @@ Vector2i TileSetAtlasSource::get_atlas_grid_size() const { } bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 3); + Vector<String> components = String(p_name).split("/", true, 2); // Compute the vector2i if we have coordinates. Vector<String> coords_split = components[0].split(":"); @@ -3520,6 +3525,13 @@ Vector2i TileSetAtlasSource::get_tile_id(int p_index) const { } bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile) const { + if (p_atlas_coords.x < 0 || p_atlas_coords.y < 0) { + return false; + } + if (p_size.x <= 0 || p_size.y <= 0) { + return false; + } + Size2i atlas_grid_size = get_atlas_grid_size(); for (int frame = 0; frame < p_frames_count; frame++) { Vector2i frame_coords = p_atlas_coords + (p_size + p_animation_separation) * ((p_animation_columns > 0) ? Vector2i(frame % p_animation_columns, frame / p_animation_columns) : Vector2i(frame, 0)); for (int x = 0; x < p_size.x; x++) { @@ -3528,6 +3540,11 @@ bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_s if (_coords_mapping_cache.has(coords) && _coords_mapping_cache[coords] != p_ignored_tile) { return false; } + if (coords.x >= atlas_grid_size.x || coords.y >= atlas_grid_size.y) { + if (!(_coords_mapping_cache.has(coords) && _coords_mapping_cache[coords] == p_ignored_tile)) { + return false; // Only accept tiles outside the atlas if they are part of the ignored tile. + } + } } } } @@ -3558,8 +3575,7 @@ Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_ margin = Vector2i(MAX(0, margin.x), MAX(0, margin.y)); Vector2i effective_texture_offset = Object::cast_to<TileData>(get_tile_data(p_atlas_coords, p_alternative_tile))->get_texture_offset(); if (ABS(effective_texture_offset.x) > margin.x || ABS(effective_texture_offset.y) > margin.y) { - effective_texture_offset.x = CLAMP(effective_texture_offset.x, -margin.x, margin.x); - effective_texture_offset.y = CLAMP(effective_texture_offset.y, -margin.y, margin.y); + effective_texture_offset = effective_texture_offset.clamp(-margin, margin); } return effective_texture_offset; @@ -4300,9 +4316,26 @@ Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id) const { } // Physics -int TileData::get_collision_polygons_count(int p_layer_id) const { - ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); - return physics[p_layer_id].polygons.size(); +void TileData::set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + physics.write[p_layer_id].linear_velocity = p_velocity; + emit_signal(SNAME("changed")); +} + +Vector2 TileData::get_constant_linear_velocity(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Vector2()); + return physics[p_layer_id].linear_velocity; +} + +void TileData::set_constant_angular_velocity(int p_layer_id, real_t p_velocity) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + physics.write[p_layer_id].angular_velocity = p_velocity; + emit_signal(SNAME("changed")); +} + +real_t TileData::get_constant_angular_velocity(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0.0); + return physics[p_layer_id].angular_velocity; } void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count) { @@ -4313,6 +4346,11 @@ void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count emit_signal(SNAME("changed")); } +int TileData::get_collision_polygons_count(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); + return physics[p_layer_id].polygons.size(); +} + void TileData::add_collision_polygon(int p_layer_id) { ERR_FAIL_INDEX(p_layer_id, physics.size()); physics.write[p_layer_id].polygons.push_back(PhysicsLayerTileData::PolygonShapeTileData()); @@ -4514,11 +4552,7 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { // Physics layers. int layer_index = components[0].trim_prefix("physics_layer_").to_int(); ERR_FAIL_COND_V(layer_index < 0, false); - if (components.size() == 2 && components[1] == "polygons_count") { - if (p_value.get_type() != Variant::INT) { - return false; - } - + if (components.size() == 2) { if (layer_index >= physics.size()) { if (tile_set) { return false; @@ -4526,8 +4560,19 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { physics.resize(layer_index + 1); } } - set_collision_polygons_count(layer_index, p_value); - return true; + if (components[1] == "linear_velocity") { + set_constant_linear_velocity(layer_index, p_value); + return true; + } else if (components[1] == "angular_velocity") { + set_constant_angular_velocity(layer_index, p_value); + return true; + } else if (components[1] == "polygons_count") { + if (p_value.get_type() != Variant::INT) { + return false; + } + set_collision_polygons_count(layer_index, p_value); + return true; + } } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { int polygon_index = components[1].trim_prefix("polygon_").to_int(); ERR_FAIL_COND_V(polygon_index < 0, false); @@ -4629,9 +4674,18 @@ bool TileData::_get(const StringName &p_name, Variant &r_ret) const { if (layer_index >= physics.size()) { return false; } - if (components.size() == 2 && components[1] == "polygons_count") { - r_ret = get_collision_polygons_count(layer_index); - return true; + + if (components.size() == 2) { + if (components[1] == "linear_velocity") { + r_ret = get_constant_linear_velocity(layer_index); + return true; + } else if (components[1] == "angular_velocity") { + r_ret = get_constant_angular_velocity(layer_index); + return true; + } else if (components[1] == "polygons_count") { + r_ret = get_collision_polygons_count(layer_index); + return true; + } } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { int polygon_index = components[1].trim_prefix("polygon_").to_int(); ERR_FAIL_COND_V(polygon_index < 0, false); @@ -4702,6 +4756,8 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const { // Physics layers. p_list->push_back(PropertyInfo(Variant::NIL, "Physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); for (int i = 0; i < physics.size(); i++) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, vformat("physics_layer_%d/linear_velocity", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/angular_velocity", i), PROPERTY_HINT_NONE)); p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/polygons_count", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); for (int j = 0; j < physics[i].polygons.size(); j++) { @@ -4791,8 +4847,12 @@ void TileData::_bind_methods() { ClassDB::bind_method(D_METHOD("get_occluder", "layer_id"), &TileData::get_occluder); // Physics. - ClassDB::bind_method(D_METHOD("get_collision_polygons_count", "layer_id"), &TileData::get_collision_polygons_count); + ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "layer_id", "velocity"), &TileData::set_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("get_constant_linear_velocity", "layer_id"), &TileData::get_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "layer_id", "velocity"), &TileData::set_constant_angular_velocity); + ClassDB::bind_method(D_METHOD("get_constant_angular_velocity", "layer_id"), &TileData::get_constant_angular_velocity); ClassDB::bind_method(D_METHOD("set_collision_polygons_count", "layer_id", "polygons_count"), &TileData::set_collision_polygons_count); + ClassDB::bind_method(D_METHOD("get_collision_polygons_count", "layer_id"), &TileData::get_collision_polygons_count); ClassDB::bind_method(D_METHOD("add_collision_polygon", "layer_id"), &TileData::add_collision_polygon); ClassDB::bind_method(D_METHOD("remove_collision_polygon", "layer_id", "polygon_index"), &TileData::remove_collision_polygon); ClassDB::bind_method(D_METHOD("set_collision_polygon_points", "layer_id", "polygon_index", "polygon"), &TileData::set_collision_polygon_points); diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 46cb1fb36d..42b82957fb 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -645,6 +645,8 @@ private: float one_way_margin = 1.0; }; + Vector2 linear_velocity; + float angular_velocity; Vector<PolygonShapeTileData> polygons; }; Vector<PhysicsLayerTileData> physics; @@ -718,8 +720,12 @@ public: Ref<OccluderPolygon2D> get_occluder(int p_layer_id) const; // Physics - int get_collision_polygons_count(int p_layer_id) const; + void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity); + Vector2 get_constant_linear_velocity(int p_layer_id) const; + void set_constant_angular_velocity(int p_layer_id, real_t p_velocity); + real_t get_constant_angular_velocity(int p_layer_id) const; void set_collision_polygons_count(int p_layer_id, int p_shapes_count); + int get_collision_polygons_count(int p_layer_id) const; void add_collision_polygon(int p_layer_id); void remove_collision_polygon(int p_layer_id, int p_polygon_index); void set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector<Vector2> p_polygon); |