diff options
Diffstat (limited to 'scene')
94 files changed, 6630 insertions, 1417 deletions
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 13b37aa2b2..bf91ce8e65 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -261,6 +261,7 @@ void Camera2D::_notification(int p_what) { if (viewport && !(custom_viewport && !ObjectDB::get_instance(custom_viewport_id))) { viewport->set_canvas_transform(Transform2D()); clear_current(); + current = true; } } remove_from_group(group_name); diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 559bd2fd16..b836497627 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -248,7 +248,7 @@ TypedArray<String> CPUParticles2D::get_configuration_warnings() const { CanvasItemMaterial *mat = Object::cast_to<CanvasItemMaterial>(get_material().ptr()); if (get_material().is_null() || (mat && !mat->get_particles_animation())) { - if (get_param(PARAM_ANIM_SPEED) != 0.0 || get_param(PARAM_ANIM_OFFSET) != 0.0 || + if (get_param_max(PARAM_ANIM_SPEED) != 0.0 || get_param_max(PARAM_ANIM_OFFSET) != 0.0 || get_param_curve(PARAM_ANIM_SPEED).is_valid() || get_param_curve(PARAM_ANIM_OFFSET).is_valid()) { warnings.push_back(TTR("CPUParticles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled.")); } @@ -292,28 +292,34 @@ real_t CPUParticles2D::get_spread() const { return spread; } -void CPUParticles2D::set_param(Parameter p_param, real_t p_value) { +void CPUParticles2D::set_param_min(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - parameters[p_param] = p_value; + parameters_min[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_max(p_param, p_value); + } } -real_t CPUParticles2D::get_param(Parameter p_param) const { +real_t CPUParticles2D::get_param_min(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return parameters[p_param]; + return parameters_min[p_param]; } -void CPUParticles2D::set_param_randomness(Parameter p_param, real_t p_value) { +void CPUParticles2D::set_param_max(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - randomness[p_param] = p_value; + parameters_max[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_min(p_param, p_value); + } } -real_t CPUParticles2D::get_param_randomness(Parameter p_param) const { +real_t CPUParticles2D::get_param_max(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return randomness[p_param]; + return parameters_max[p_param]; } static void _adjust_curve_range(const Ref<Curve> &p_curve, real_t p_min, real_t p_max) { @@ -460,6 +466,31 @@ Vector2 CPUParticles2D::get_gravity() const { return gravity; } +void CPUParticles2D::set_scale_curve_x(Ref<Curve> p_scale_curve) { + scale_curve_x = p_scale_curve; +} + +void CPUParticles2D::set_scale_curve_y(Ref<Curve> p_scale_curve) { + scale_curve_y = p_scale_curve; +} + +void CPUParticles2D::set_split_scale(bool p_split_scale) { + split_scale = p_split_scale; + notify_property_list_changed(); +} + +Ref<Curve> CPUParticles2D::get_scale_curve_x() const { + return scale_curve_x; +} + +Ref<Curve> CPUParticles2D::get_scale_curve_y() const { + return scale_curve_y; +} + +bool CPUParticles2D::get_split_scale() { + return split_scale; +} + void CPUParticles2D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { property.usage = PROPERTY_USAGE_NONE; @@ -484,6 +515,9 @@ void CPUParticles2D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_colors" && emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { property.usage = PROPERTY_USAGE_NONE; } + if (property.name.begins_with("scale_curve_") && !split_scale) { + property.usage = PROPERTY_USAGE_NONE; + } } static uint32_t idhash(uint32_t x) { @@ -695,14 +729,14 @@ void CPUParticles2D::_particles_process(double p_delta) { real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); Vector2 rot = Vector2(Math::cos(angle1_rad), Math::sin(angle1_rad)); - p.velocity = rot * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp((real_t)1.0, real_t(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], Math::randf()); - real_t base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp((real_t)1.0, p.angle_rand, randomness[PARAM_ANGLE]); + real_t base_angle = tex_angle * Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); p.rotation = Math::deg2rad(base_angle); p.custom[0] = 0.0; // unused p.custom[1] = 0.0; // phase [0..1] - p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp((real_t)1.0, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]); //animation phase [0..1] + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); p.custom[3] = 0.0; p.transform = Transform2D(); p.time = 0; @@ -766,51 +800,51 @@ void CPUParticles2D::_particles_process(double p_delta) { p.custom[1] = p.time / lifetime; tv = p.time / p.lifetime; - real_t tex_linear_velocity = 0.0; + real_t tex_linear_velocity = 1.0; if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(tv); } - real_t tex_orbit_velocity = 0.0; + real_t tex_orbit_velocity = 1.0; if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(tv); } - real_t tex_angular_velocity = 0.0; + real_t tex_angular_velocity = 1.0; if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(tv); } - real_t tex_linear_accel = 0.0; + real_t tex_linear_accel = 1.0; if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) { tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(tv); } - real_t tex_tangential_accel = 0.0; + real_t tex_tangential_accel = 1.0; if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(tv); } - real_t tex_radial_accel = 0.0; + real_t tex_radial_accel = 1.0; if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) { tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(tv); } - real_t tex_damping = 0.0; + real_t tex_damping = 1.0; if (curve_parameters[PARAM_DAMPING].is_valid()) { tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(tv); } - real_t tex_angle = 0.0; + real_t tex_angle = 1.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv); } - real_t tex_anim_speed = 0.0; + real_t tex_anim_speed = 1.0; if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) { tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(tv); } - real_t tex_anim_offset = 0.0; + real_t tex_anim_offset = 1.0; if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) { tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(tv); } @@ -819,18 +853,18 @@ void CPUParticles2D::_particles_process(double p_delta) { Vector2 pos = p.transform[2]; //apply linear acceleration - force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector2(); + force += p.velocity.length() > 0.0 ? p.velocity.normalized() * tex_linear_accel * Math::lerp(parameters_min[PARAM_LINEAR_ACCEL], parameters_max[PARAM_LINEAR_ACCEL], rand_from_seed(alt_seed)) : Vector2(); //apply radial acceleration Vector2 org = emission_xform[2]; Vector2 diff = pos - org; - force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector2(); + force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector2(); //apply tangential acceleration; Vector2 yx = Vector2(diff.y, diff.x); - force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector2(); + force += yx.length() > 0.0 ? yx.normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector2(); //apply attractor forces p.velocity += force * local_delta; //orbit velocity - real_t orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); + real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(alt_seed)); if (orbit_amount != 0.0) { real_t ang = orbit_amount * local_delta * Math_TAU; // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, @@ -843,9 +877,9 @@ void CPUParticles2D::_particles_process(double p_delta) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } - if (parameters[PARAM_DAMPING] + tex_damping > 0.0) { + if (parameters_max[PARAM_DAMPING] + tex_damping > 0.0) { real_t v = p.velocity.length(); - real_t damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]); + real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(alt_seed)); v -= damp * local_delta; if (v < 0.0) { p.velocity = Vector2(); @@ -853,18 +887,32 @@ void CPUParticles2D::_particles_process(double p_delta) { p.velocity = p.velocity.normalized() * v; } } - real_t base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp((real_t)1.0, p.angle_rand, randomness[PARAM_ANGLE]); - base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]); + real_t base_angle = (tex_angle)*Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); + base_angle += p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(alt_seed)); p.rotation = Math::deg2rad(base_angle); //angle - real_t animation_phase = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp((real_t)1.0, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + p.custom[1] * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); - p.custom[2] = animation_phase; + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand) + p.custom[1] * tex_anim_speed * Math::lerp(parameters_min[PARAM_ANIM_SPEED], parameters_max[PARAM_ANIM_SPEED], rand_from_seed(alt_seed)); } //apply color //apply hue rotation - real_t tex_scale = 1.0; - if (curve_parameters[PARAM_SCALE].is_valid()) { - tex_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + Vector2 tex_scale = Vector2(1.0, 1.0); + if (split_scale) { + if (scale_curve_x.is_valid()) { + tex_scale.x = scale_curve_x->interpolate(tv); + } else { + tex_scale.x = 1.0; + } + if (scale_curve_y.is_valid()) { + tex_scale.y = scale_curve_y->interpolate(tv); + } else { + tex_scale.y = 1.0; + } + } else { + if (curve_parameters[PARAM_SCALE].is_valid()) { + real_t tmp_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + tex_scale.x = tmp_scale; + tex_scale.y = tmp_scale; + } } real_t tex_hue_variation = 0.0; @@ -872,7 +920,7 @@ void CPUParticles2D::_particles_process(double p_delta) { tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(tv); } - real_t hue_rot_angle = (parameters[PARAM_HUE_VARIATION] + tex_hue_variation) * Math_TAU * Math::lerp(1, p.hue_rot_rand * 2.0f - 1.0f, randomness[PARAM_HUE_VARIATION]); + real_t hue_rot_angle = (tex_hue_variation)*Math_TAU * Math::lerp(parameters_min[PARAM_HUE_VARIATION], parameters_max[PARAM_HUE_VARIATION], p.hue_rot_rand); real_t hue_rot_c = Math::cos(hue_rot_angle); real_t hue_rot_s = Math::sin(hue_rot_angle); @@ -912,13 +960,15 @@ void CPUParticles2D::_particles_process(double p_delta) { } //scale by scale - real_t base_scale = tex_scale * Math::lerp(parameters[PARAM_SCALE], (real_t)1.0, p.scale_rand * randomness[PARAM_SCALE]); - if (base_scale < 0.000001) { - base_scale = 0.000001; + Vector2 base_scale = tex_scale * Math::lerp(parameters_min[PARAM_SCALE], parameters_max[PARAM_SCALE], p.scale_rand); + if (base_scale.x < 0.00001) { + base_scale.x = 0.00001; } - - p.transform.elements[0] *= base_scale; - p.transform.elements[1] *= base_scale; + if (base_scale.y < 0.00001) { + base_scale.y = 0.00001; + } + p.transform.elements[0] *= base_scale.x; + p.transform.elements[1] *= base_scale.y; p.transform[2] += p.velocity * local_delta; } @@ -1130,18 +1180,24 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) { Vector2 rect_extents = Vector2(material->get_emission_box_extents().x, material->get_emission_box_extents().y); set_emission_rect_extents(rect_extents); + Ref<CurveXYZTexture> scale3D = material->get_param_texture(ParticlesMaterial::PARAM_SCALE); + if (scale3D.is_valid()) { + split_scale = true; + scale_curve_x = scale3D->get_curve_x(); + scale_curve_y = scale3D->get_curve_y(); + } Vector2 gravity = Vector2(material->get_gravity().x, material->get_gravity().y); set_gravity(gravity); set_lifetime_randomness(material->get_lifetime_randomness()); #define CONVERT_PARAM(m_param) \ - set_param(m_param, material->get_param(ParticlesMaterial::m_param)); \ + set_param_min(m_param, material->get_param_min(ParticlesMaterial::m_param)); \ { \ Ref<CurveTexture> ctex = material->get_param_texture(ParticlesMaterial::m_param); \ if (ctex.is_valid()) \ set_param_curve(m_param, ctex->get_curve()); \ } \ - set_param_randomness(m_param, material->get_param_randomness(ParticlesMaterial::m_param)); + set_param_max(m_param, material->get_param_max(ParticlesMaterial::m_param)); CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); @@ -1224,11 +1280,11 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_spread", "degrees"), &CPUParticles2D::set_spread); ClassDB::bind_method(D_METHOD("get_spread"), &CPUParticles2D::get_spread); - ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &CPUParticles2D::set_param); - ClassDB::bind_method(D_METHOD("get_param", "param"), &CPUParticles2D::get_param); + ClassDB::bind_method(D_METHOD("set_param_min", "param", "value"), &CPUParticles2D::set_param_min); + ClassDB::bind_method(D_METHOD("get_param_min", "param"), &CPUParticles2D::get_param_min); - ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &CPUParticles2D::set_param_randomness); - ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &CPUParticles2D::get_param_randomness); + ClassDB::bind_method(D_METHOD("set_param_max", "param", "value"), &CPUParticles2D::set_param_max); + ClassDB::bind_method(D_METHOD("get_param_max", "param"), &CPUParticles2D::get_param_max); ClassDB::bind_method(D_METHOD("set_param_curve", "param", "curve"), &CPUParticles2D::set_param_curve); ClassDB::bind_method(D_METHOD("get_param_curve", "param"), &CPUParticles2D::get_param_curve); @@ -1263,6 +1319,15 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles2D::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles2D::set_gravity); + ClassDB::bind_method(D_METHOD("get_split_scale"), &CPUParticles2D::get_split_scale); + ClassDB::bind_method(D_METHOD("set_split_scale", "split_scale"), &CPUParticles2D::set_split_scale); + + ClassDB::bind_method(D_METHOD("get_scale_curve_x"), &CPUParticles2D::get_scale_curve_x); + ClassDB::bind_method(D_METHOD("set_scale_curve_x", "scale_curve"), &CPUParticles2D::set_scale_curve_x); + + ClassDB::bind_method(D_METHOD("get_scale_curve_y"), &CPUParticles2D::get_scale_curve_y); + ClassDB::bind_method(D_METHOD("set_scale_curve_y", "scale_curve"), &CPUParticles2D::set_scale_curve_y); + ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles2D::convert_from_particles); ADD_GROUP("Emission Shape", "emission_"); @@ -1280,54 +1345,58 @@ void CPUParticles2D::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY); ADD_GROUP("Angular Velocity", "angular_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_min", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_min", "get_param_min", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_max", "get_param_max", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param", "get_param", PARAM_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGLE); ADD_GROUP("Scale", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_amount_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_SCALE); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "split_scale"), "set_split_scale", "get_split_scale"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_x", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_x", "get_scale_curve_x"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_y", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_y", "get_scale_curve_y"); + ADD_GROUP("Color", ""); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_color_ramp", "get_color_ramp"); ADD_GROUP("Hue Variation", "hue_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param", "get_param", PARAM_HUE_VARIATION); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_HUE_VARIATION); ADD_GROUP("Animation", "anim_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_OFFSET); BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); @@ -1366,22 +1435,31 @@ CPUParticles2D::CPUParticles2D() { set_amount(8); set_use_local_coordinates(true); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); - - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } + set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_min(PARAM_ANGULAR_VELOCITY, 0); + set_param_min(PARAM_ORBIT_VELOCITY, 0); + set_param_min(PARAM_LINEAR_ACCEL, 0); + set_param_min(PARAM_RADIAL_ACCEL, 0); + set_param_min(PARAM_TANGENTIAL_ACCEL, 0); + set_param_min(PARAM_DAMPING, 0); + set_param_min(PARAM_ANGLE, 0); + set_param_min(PARAM_SCALE, 1); + set_param_min(PARAM_HUE_VARIATION, 0); + set_param_min(PARAM_ANIM_SPEED, 0); + set_param_min(PARAM_ANIM_OFFSET, 0); + + set_param_max(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_max(PARAM_ANGULAR_VELOCITY, 0); + set_param_max(PARAM_ORBIT_VELOCITY, 0); + set_param_max(PARAM_LINEAR_ACCEL, 0); + set_param_max(PARAM_RADIAL_ACCEL, 0); + set_param_max(PARAM_TANGENTIAL_ACCEL, 0); + set_param_max(PARAM_DAMPING, 0); + set_param_max(PARAM_ANGLE, 0); + set_param_max(PARAM_SCALE, 1); + set_param_max(PARAM_HUE_VARIATION, 0); + set_param_max(PARAM_ANIM_SPEED, 0); + set_param_max(PARAM_ANIM_OFFSET, 0); for (int i = 0; i < PARTICLE_FLAG_MAX; i++) { particle_flags[i] = false; diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 0f8950375f..4990d443e3 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -150,8 +150,8 @@ private: Vector2 direction = Vector2(1, 0); real_t spread = 45.0; - real_t parameters[PARAM_MAX]; - real_t randomness[PARAM_MAX]; + real_t parameters_min[PARAM_MAX]; + real_t parameters_max[PARAM_MAX]; Ref<Curve> curve_parameters[PARAM_MAX]; Color color; @@ -167,6 +167,10 @@ private: Vector<Color> emission_colors; int emission_point_count = 0; + Ref<Curve> scale_curve_x; + Ref<Curve> scale_curve_y; + bool split_scale = false; + Vector2 gravity = Vector2(0, 980); void _update_internal(); @@ -236,11 +240,11 @@ public: void set_spread(real_t p_spread); real_t get_spread() const; - void set_param(Parameter p_param, real_t p_value); - real_t get_param(Parameter p_param) const; + void set_param_min(Parameter p_param, real_t p_value); + real_t get_param_min(Parameter p_param) const; - void set_param_randomness(Parameter p_param, real_t p_value); - real_t get_param_randomness(Parameter p_param) const; + void set_param_max(Parameter p_param, real_t p_value); + real_t get_param_max(Parameter p_param) const; void set_param_curve(Parameter p_param, const Ref<Curve> &p_curve); Ref<Curve> get_param_curve(Parameter p_param) const; @@ -261,6 +265,9 @@ public: void set_emission_normals(const Vector<Vector2> &p_normals); void set_emission_colors(const Vector<Color> &p_colors); void set_emission_point_count(int p_count); + void set_scale_curve_x(Ref<Curve> p_scale_curve); + void set_scale_curve_y(Ref<Curve> p_scale_curve); + void set_split_scale(bool p_split_scale); EmissionShape get_emission_shape() const; real_t get_emission_sphere_radius() const; @@ -269,6 +276,9 @@ public: Vector<Vector2> get_emission_normals() const; Vector<Color> get_emission_colors() const; int get_emission_point_count() const; + Ref<Curve> get_scale_curve_x() const; + Ref<Curve> get_scale_curve_y() const; + bool get_split_scale(); void set_gravity(const Vector2 &p_gravity); Vector2 get_gravity() const; diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index 47bf1bc77c..5bce705dd5 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -295,7 +295,7 @@ TypedArray<String> GPUParticles2D::get_configuration_warnings() const { if (get_material().is_null() || (mat && !mat->get_particles_animation())) { const ParticlesMaterial *process = Object::cast_to<ParticlesMaterial>(process_material.ptr()); if (process && - (process->get_param(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || + (process->get_param_max(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param_max(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_SPEED).is_valid() || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_OFFSET).is_valid())) { warnings.push_back(TTR("Particles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled.")); } diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index d52c293763..a9d4877cbb 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -102,21 +102,21 @@ bool PhysicsBody2D::move_and_collide(const Vector2 &p_motion, PhysicsServer2D::M } // Check depth of recovery. - real_t projected_length = r_result.motion.dot(motion_normal); - Vector2 recovery = r_result.motion - motion_normal * projected_length; + real_t projected_length = r_result.travel.dot(motion_normal); + Vector2 recovery = r_result.travel - motion_normal * projected_length; real_t recovery_length = recovery.length(); // Fixes cases where canceling slide causes the motion to go too deep into the ground, // because we're only taking rest information into account and not general recovery. if (recovery_length < (real_t)p_margin + precision) { // Apply adjustment to motion. - r_result.motion = motion_normal * projected_length; - r_result.remainder = p_motion - r_result.motion; + r_result.travel = motion_normal * projected_length; + r_result.remainder = p_motion - r_result.travel; } } } if (!p_test_only) { - gt.elements[2] += r_result.motion; + gt.elements[2] += r_result.travel; set_global_transform(gt); } @@ -529,9 +529,9 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { sleeping = state->is_sleeping(); emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); } - if (get_script_instance()) { - get_script_instance()->call("_integrate_forces", state); - } + + GDVIRTUAL_CALL(_integrate_forces, state); + set_block_transform_notify(false); // want it back if (contact_monitor) { @@ -978,7 +978,7 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_colliding_bodies"), &RigidBody2D::get_colliding_bodies); - BIND_VMETHOD(MethodInfo("_integrate_forces", PropertyInfo(Variant::OBJECT, "state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectBodyState2D"))); + 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,65535,0.01,exp"), "set_mass", "get_mass"); @@ -1043,14 +1043,14 @@ void RigidBody2D::_reload_physics_characteristics() { // So, if you pass 45 as limit, avoid numerical precision errors when angle is 45. #define FLOOR_ANGLE_THRESHOLD 0.01 -void CharacterBody2D::move_and_slide() { +bool CharacterBody2D::move_and_slide() { // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky. float delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); Vector2 current_platform_velocity = platform_velocity; if ((on_floor || on_wall) && platform_rid.is_valid()) { - bool excluded = (exclude_body_layers & platform_layer) != 0; + bool excluded = (moving_platform_ignore_layers & platform_layer) != 0; if (!excluded) { // 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); @@ -1081,10 +1081,24 @@ void CharacterBody2D::move_and_slide() { } } - Vector2 motion = linear_velocity * delta; + if (motion_mode == MOTION_MODE_GROUNDED) { + _move_and_slide_grounded(delta, was_on_floor, current_platform_velocity); + } else { + _move_and_slide_free(delta); + } + + if (!on_floor && !on_wall) { + // Add last platform velocity when just left a moving platform. + linear_velocity += current_platform_velocity; + } + + return motion_results.size() > 0; +} + +void CharacterBody2D::_move_and_slide_grounded(real_t p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity) { + Vector2 motion = linear_velocity * p_delta; Vector2 motion_slide_up = motion.slide(up_direction); - Vector2 prev_platform_velocity = current_platform_velocity; Vector2 prev_floor_normal = floor_normal; RID prev_platform_rid = platform_rid; int prev_platform_layer = platform_layer; @@ -1095,7 +1109,7 @@ void CharacterBody2D::move_and_slide() { // No sliding on first attempt to keep floor motion stable when possible, // When stop on slope is enabled or when there is no up direction. - bool sliding_enabled = !stop_on_slope || up_direction == Vector2(); + bool sliding_enabled = !floor_stop_on_slope; // Constant speed can be applied only the first time sliding is enabled. bool can_apply_constant_speed = sliding_enabled; bool first_slide = true; @@ -1104,23 +1118,21 @@ void CharacterBody2D::move_and_slide() { for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer2D::MotionResult result; - bool found_collision = false; Vector2 prev_position = get_global_transform().elements[2]; bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled); if (collided) { - found_collision = true; motion_results.push_back(result); _set_collision_direction(result); - if (on_floor && stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { + if (on_floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { Transform2D gt = get_global_transform(); - if (result.motion.length() > margin) { - gt.elements[2] -= result.motion.slide(up_direction); + if (result.travel.length() > margin) { + gt.elements[2] -= result.travel.slide(up_direction); } else { - gt.elements[2] -= result.motion; + gt.elements[2] -= result.travel; } set_global_transform(gt); linear_velocity = Vector2(); @@ -1134,28 +1146,27 @@ void CharacterBody2D::move_and_slide() { } // Move on floor only checks. - if (move_on_floor_only && on_wall && motion_slide_up.dot(result.collision_normal) <= 0) { - // Avoid to move forward on a wall if move_on_floor_only is true. - if (was_on_floor && !is_on_floor_only() && !vel_dir_facing_up) { + if (floor_block_on_wall && on_wall && motion_slide_up.dot(result.collision_normal) <= 0) { + // 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.motion.length() <= margin) { + if (result.travel.length() <= margin) { // Cancels the motion. Transform2D gt = get_global_transform(); - gt.elements[2] -= result.motion; + gt.elements[2] -= result.travel; set_global_transform(gt); } on_floor = true; platform_rid = prev_platform_rid; platform_layer = prev_platform_layer; - - platform_velocity = prev_platform_velocity; + platform_velocity = p_prev_platform_velocity; floor_normal = prev_floor_normal; linear_velocity = Vector2(); motion = Vector2(); break; } // Prevents the body from being able to climb a slope when it moves forward against the wall. - else if (!is_on_floor_only()) { + else if (!on_floor) { motion = up_direction * up_direction.dot(result.remainder); motion = motion.slide(result.collision_normal); } else { @@ -1163,12 +1174,10 @@ void CharacterBody2D::move_and_slide() { } } // Constant Speed when the slope is upward. - else if (constant_speed_on_floor && is_on_floor_only() && can_apply_constant_speed && was_on_floor && motion.dot(result.collision_normal) < 0) { + else if (floor_constant_speed && is_on_floor_only() && can_apply_constant_speed && p_was_on_floor && motion.dot(result.collision_normal) < 0) { can_apply_constant_speed = false; Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized(); - if (!motion_slide_norm.is_equal_approx(Vector2())) { - motion = motion_slide_norm * (motion_slide_up.length() - result.motion.slide(up_direction).length() - last_travel.slide(up_direction).length()); - } + motion = motion_slide_norm * (motion_slide_up.length() - result.travel.slide(up_direction).length() - last_travel.slide(up_direction).length()); } // 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)) { @@ -1197,11 +1206,11 @@ void CharacterBody2D::move_and_slide() { } } - last_travel = result.motion; + last_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. - else if (constant_speed_on_floor && first_slide && _on_floor_if_snapped(was_on_floor, vel_dir_facing_up)) { + else if (floor_constant_speed && first_slide && _on_floor_if_snapped(p_was_on_floor, vel_dir_facing_up)) { can_apply_constant_speed = false; sliding_enabled = true; Transform2D gt = get_global_transform(); @@ -1209,27 +1218,20 @@ void CharacterBody2D::move_and_slide() { set_global_transform(gt); Vector2 motion_slide_norm = motion.slide(prev_floor_normal).normalized(); - if (!motion_slide_norm.is_equal_approx(Vector2())) { - motion = motion_slide_norm * (motion_slide_up.length()); - found_collision = true; - } + 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 (!found_collision || motion.is_equal_approx(Vector2())) { + if (!collided || motion.is_equal_approx(Vector2())) { break; } } - _snap_on_floor(was_on_floor, vel_dir_facing_up); - - if (!on_floor && !on_wall) { - // Add last platform velocity when just left a moving platform. - linear_velocity += current_platform_velocity; - } + _snap_on_floor(p_was_on_floor, vel_dir_facing_up); // Reset the gravity accumulation when touching the ground. if (on_floor && !vel_dir_facing_up) { @@ -1237,8 +1239,47 @@ void CharacterBody2D::move_and_slide() { } } +void CharacterBody2D::_move_and_slide_free(real_t p_delta) { + Vector2 motion = linear_velocity * p_delta; + + platform_rid = RID(); + floor_normal = Vector2(); + platform_velocity = Vector2(); + + bool first_slide = true; + for (int iteration = 0; iteration < max_slides; ++iteration) { + PhysicsServer2D::MotionResult result; + + bool collided = move_and_collide(motion, result, margin, false, false); + + 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) { + motion = Vector2(); + } else if (first_slide) { + Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized(); + motion = motion_slide_norm * (motion.length() - result.travel.length()); + } else { + motion = result.remainder.slide(result.collision_normal); + } + + if (motion.dot(linear_velocity) <= 0.0) { + motion = Vector2(); + } + } + + first_slide = false; + + if (!collided || motion.is_equal_approx(Vector2())) { + break; + } + } +} + void CharacterBody2D::_snap_on_floor(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 (Math::is_equal_approx(floor_snap_length, 0) || on_floor || !was_on_floor || vel_dir_facing_up) { return; } @@ -1246,20 +1287,19 @@ void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) PhysicsServer2D::MotionResult result; if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false)) { bool apply = true; - float collision_angle = Math::acos(result.collision_normal.dot(up_direction)); - if (collision_angle <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { on_floor = true; floor_normal = result.collision_normal; platform_velocity = result.collider_velocity; _set_platform_data(result); - if (stop_on_slope) { + if (floor_stop_on_slope) { // move and collide may stray the object a bit because of pre un-stucking, // so only ensure that motion happens on floor direction in this case. - if (result.motion.length() > margin) { - result.motion = up_direction * up_direction.dot(result.motion); + if (result.travel.length() > margin) { + result.travel = up_direction * up_direction.dot(result.travel); } else { - result.motion = Vector2(); + result.travel = Vector2(); } } } else { @@ -1267,7 +1307,7 @@ void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) } if (apply) { - gt.elements[2] += result.motion; + gt.elements[2] += result.travel; set_global_transform(gt); } } @@ -1280,7 +1320,7 @@ bool CharacterBody2D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facin PhysicsServer2D::MotionResult result; if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false)) { - if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { return true; } } @@ -1289,16 +1329,12 @@ bool CharacterBody2D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facin } void CharacterBody2D::_set_collision_direction(const PhysicsServer2D::MotionResult &p_result) { - if (up_direction == Vector2()) { - return; - } - - if (Math::acos(p_result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor + if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor on_floor = true; floor_normal = p_result.collision_normal; platform_velocity = p_result.collider_velocity; _set_platform_data(p_result); - } else if (Math::acos(p_result.collision_normal.dot(-up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling + } else if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling on_ceiling = true; } else { on_wall = true; @@ -1352,11 +1388,16 @@ Vector2 CharacterBody2D::get_floor_normal() const { return floor_normal; } +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 { return platform_velocity; } -int CharacterBody2D::get_slide_count() const { +int CharacterBody2D::get_slide_collision_count() const { return motion_results.size(); } @@ -1380,6 +1421,13 @@ Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) { return slide_colliders[p_bounce]; } +Ref<KinematicCollision2D> CharacterBody2D::_get_last_slide_collision() { + if (motion_results.size() == 0) { + return Ref<KinematicCollision2D>(); + } + return _get_slide_collision(motion_results.size() - 1); +} + void CharacterBody2D::set_safe_margin(real_t p_margin) { margin = p_margin; } @@ -1388,28 +1436,28 @@ real_t CharacterBody2D::get_safe_margin() const { return margin; } -bool CharacterBody2D::is_stop_on_slope_enabled() const { - return stop_on_slope; +bool CharacterBody2D::is_floor_stop_on_slope_enabled() const { + return floor_stop_on_slope; } -void CharacterBody2D::set_stop_on_slope_enabled(bool p_enabled) { - stop_on_slope = p_enabled; +void CharacterBody2D::set_floor_stop_on_slope_enabled(bool p_enabled) { + floor_stop_on_slope = p_enabled; } -bool CharacterBody2D::is_constant_speed_on_floor_enabled() const { - return constant_speed_on_floor; +bool CharacterBody2D::is_floor_constant_speed_enabled() const { + return floor_constant_speed; } -void CharacterBody2D::set_constant_speed_on_floor_enabled(bool p_enabled) { - constant_speed_on_floor = p_enabled; +void CharacterBody2D::set_floor_constant_speed_enabled(bool p_enabled) { + floor_constant_speed = p_enabled; } -bool CharacterBody2D::is_move_on_floor_only_enabled() const { - return move_on_floor_only; +bool CharacterBody2D::is_floor_block_on_wall_enabled() const { + return floor_block_on_wall; } -void CharacterBody2D::set_move_on_floor_only_enabled(bool p_enabled) { - move_on_floor_only = p_enabled; +void CharacterBody2D::set_floor_block_on_wall_enabled(bool p_enabled) { + floor_block_on_wall = p_enabled; } bool CharacterBody2D::is_slide_on_ceiling_enabled() const { @@ -1420,12 +1468,20 @@ void CharacterBody2D::set_slide_on_ceiling_enabled(bool p_enabled) { slide_on_ceiling = p_enabled; } -uint32_t CharacterBody2D::get_exclude_body_layers() const { - return exclude_body_layers; +uint32_t CharacterBody2D::get_moving_platform_ignore_layers() const { + return moving_platform_ignore_layers; } -void CharacterBody2D::set_exclude_body_layers(uint32_t p_exclude_layers) { - exclude_body_layers = p_exclude_layers; +void CharacterBody2D::set_moving_platform_ignore_layers(uint32_t p_exclude_layers) { + moving_platform_ignore_layers = p_exclude_layers; +} + +void CharacterBody2D::set_motion_mode(MotionMode p_mode) { + motion_mode = p_mode; +} + +CharacterBody2D::MotionMode CharacterBody2D::get_motion_mode() const { + return motion_mode; } int CharacterBody2D::get_max_slides() const { @@ -1454,11 +1510,20 @@ void CharacterBody2D::set_floor_snap_length(real_t p_floor_snap_length) { floor_snap_length = p_floor_snap_length; } +real_t CharacterBody2D::get_free_mode_min_slide_angle() const { + return free_mode_min_slide_angle; +} + +void CharacterBody2D::set_free_mode_min_slide_angle(real_t p_radians) { + free_mode_min_slide_angle = p_radians; +} + const Vector2 &CharacterBody2D::get_up_direction() const { return up_direction; } void CharacterBody2D::set_up_direction(const Vector2 &p_up_direction) { + ERR_FAIL_COND_MSG(p_up_direction == Vector2(), "up_direction can't be equal to Vector2.ZERO, consider using Free motion mode instead."); up_direction = p_up_direction.normalized(); } @@ -1484,17 +1549,17 @@ void CharacterBody2D::_bind_methods() { 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); - ClassDB::bind_method(D_METHOD("is_stop_on_slope_enabled"), &CharacterBody2D::is_stop_on_slope_enabled); - ClassDB::bind_method(D_METHOD("set_stop_on_slope_enabled", "enabled"), &CharacterBody2D::set_stop_on_slope_enabled); - ClassDB::bind_method(D_METHOD("set_constant_speed_on_floor_enabled", "enabled"), &CharacterBody2D::set_constant_speed_on_floor_enabled); - ClassDB::bind_method(D_METHOD("is_constant_speed_on_floor_enabled"), &CharacterBody2D::is_constant_speed_on_floor_enabled); - ClassDB::bind_method(D_METHOD("set_move_on_floor_only_enabled", "enabled"), &CharacterBody2D::set_move_on_floor_only_enabled); - ClassDB::bind_method(D_METHOD("is_move_on_floor_only_enabled"), &CharacterBody2D::is_move_on_floor_only_enabled); + ClassDB::bind_method(D_METHOD("is_floor_stop_on_slope_enabled"), &CharacterBody2D::is_floor_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("set_floor_stop_on_slope_enabled", "enabled"), &CharacterBody2D::set_floor_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("set_floor_constant_speed_enabled", "enabled"), &CharacterBody2D::set_floor_constant_speed_enabled); + ClassDB::bind_method(D_METHOD("is_floor_constant_speed_enabled"), &CharacterBody2D::is_floor_constant_speed_enabled); + ClassDB::bind_method(D_METHOD("set_floor_block_on_wall_enabled", "enabled"), &CharacterBody2D::set_floor_block_on_wall_enabled); + ClassDB::bind_method(D_METHOD("is_floor_block_on_wall_enabled"), &CharacterBody2D::is_floor_block_on_wall_enabled); ClassDB::bind_method(D_METHOD("set_slide_on_ceiling_enabled", "enabled"), &CharacterBody2D::set_slide_on_ceiling_enabled); ClassDB::bind_method(D_METHOD("is_slide_on_ceiling_enabled"), &CharacterBody2D::is_slide_on_ceiling_enabled); - ClassDB::bind_method(D_METHOD("set_exclude_body_layers", "exclude_layer"), &CharacterBody2D::set_exclude_body_layers); - ClassDB::bind_method(D_METHOD("get_exclude_body_layers"), &CharacterBody2D::get_exclude_body_layers); + ClassDB::bind_method(D_METHOD("set_moving_platform_ignore_layers", "exclude_layer"), &CharacterBody2D::set_moving_platform_ignore_layers); + ClassDB::bind_method(D_METHOD("get_moving_platform_ignore_layers"), &CharacterBody2D::get_moving_platform_ignore_layers); ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody2D::get_max_slides); ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody2D::set_max_slides); @@ -1502,8 +1567,12 @@ void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_floor_max_angle", "radians"), &CharacterBody2D::set_floor_max_angle); ClassDB::bind_method(D_METHOD("get_floor_snap_length"), &CharacterBody2D::get_floor_snap_length); ClassDB::bind_method(D_METHOD("set_floor_snap_length", "floor_snap_length"), &CharacterBody2D::set_floor_snap_length); + ClassDB::bind_method(D_METHOD("get_free_mode_min_slide_angle"), &CharacterBody2D::get_free_mode_min_slide_angle); + ClassDB::bind_method(D_METHOD("set_free_mode_min_slide_angle", "radians"), &CharacterBody2D::set_free_mode_min_slide_angle); ClassDB::bind_method(D_METHOD("get_up_direction"), &CharacterBody2D::get_up_direction); 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("is_on_floor"), &CharacterBody2D::is_on_floor); ClassDB::bind_method(D_METHOD("is_on_floor_only"), &CharacterBody2D::is_on_floor_only); @@ -1512,22 +1581,43 @@ 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_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_count"), &CharacterBody2D::get_slide_count); + ClassDB::bind_method(D_METHOD("get_slide_collision_count"), &CharacterBody2D::get_slide_collision_count); ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody2D::_get_slide_collision); + 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::BOOL, "stop_on_slope"), "set_stop_on_slope_enabled", "is_stop_on_slope_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constant_speed_on_floor"), "set_constant_speed_on_floor_enabled", "is_constant_speed_on_floor_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "move_on_floor_only"), "set_move_on_floor_only_enabled", "is_move_on_floor_only_enabled"); 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_RANGE, "1,8,1,or_greater"), "set_max_slides", "get_max_slides"); + 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_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_stop_on_slope"), "set_floor_stop_on_slope_enabled", "is_floor_stop_on_slope_enabled"); + 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::VECTOR2, "up_direction"), "set_up_direction", "get_up_direction"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "exclude_body_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_exclude_body_layers", "get_exclude_body_layers"); - + ADD_GROUP("Moving platform", "moving_platform"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_ignore_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_ignore_layers", "get_moving_platform_ignore_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); +} + +void CharacterBody2D::_validate_property(PropertyInfo &property) const { + if (motion_mode == MOTION_MODE_FREE) { + if (property.name.begins_with("floor_") || property.name == "up_direction" || property.name == "slide_on_ceiling") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } else { + if (property.name == "free_mode_min_slide_angle") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } } CharacterBody2D::CharacterBody2D() : @@ -1553,13 +1643,18 @@ Vector2 KinematicCollision2D::get_normal() const { } Vector2 KinematicCollision2D::get_travel() const { - return result.motion; + return result.travel; } Vector2 KinematicCollision2D::get_remainder() const { return result.remainder; } +real_t KinematicCollision2D::get_angle(const Vector2 &p_up_direction) const { + ERR_FAIL_COND_V(p_up_direction == Vector2(), 0); + return result.get_angle(p_up_direction); +} + Object *KinematicCollision2D::get_local_shape() const { if (!owner) { return nullptr; @@ -1614,6 +1709,7 @@ void KinematicCollision2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_normal"), &KinematicCollision2D::get_normal); ClassDB::bind_method(D_METHOD("get_travel"), &KinematicCollision2D::get_travel); ClassDB::bind_method(D_METHOD("get_remainder"), &KinematicCollision2D::get_remainder); + ClassDB::bind_method(D_METHOD("get_angle", "up_direction"), &KinematicCollision2D::get_angle, DEFVAL(Vector2(0.0, -1.0))); ClassDB::bind_method(D_METHOD("get_local_shape"), &KinematicCollision2D::get_local_shape); ClassDB::bind_method(D_METHOD("get_collider"), &KinematicCollision2D::get_collider); ClassDB::bind_method(D_METHOD("get_collider_id"), &KinematicCollision2D::get_collider_id); diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 3d894416f0..a999317953 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -189,6 +189,8 @@ protected: void _notification(int p_what); static void _bind_methods(); + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState2D *) + public: void set_mode(Mode p_mode); Mode get_mode() const; @@ -268,19 +270,47 @@ VARIANT_ENUM_CAST(RigidBody2D::CCDMode); class CharacterBody2D : public PhysicsBody2D { GDCLASS(CharacterBody2D, PhysicsBody2D); +public: + enum MotionMode { + MOTION_MODE_GROUNDED, + MOTION_MODE_FREE, + }; + bool move_and_slide(); + + const Vector2 &get_linear_velocity() const; + void set_linear_velocity(const Vector2 &p_velocity); + + bool is_on_floor() const; + bool is_on_floor_only() const; + bool is_on_wall() const; + bool is_on_wall_only() const; + bool is_on_ceiling() const; + bool is_on_ceiling_only() const; + Vector2 get_floor_normal() const; + real_t get_floor_angle(const Vector2 &p_up_direction = Vector2(0.0, -1.0)) const; + Vector2 get_platform_velocity() const; + + int get_slide_collision_count() const; + PhysicsServer2D::MotionResult get_slide_collision(int p_bounce) const; + + CharacterBody2D(); + ~CharacterBody2D(); + private: real_t margin = 0.08; + MotionMode motion_mode = MOTION_MODE_GROUNDED; - bool stop_on_slope = false; - bool constant_speed_on_floor = false; - bool move_on_floor_only = true; + bool floor_stop_on_slope = false; + bool floor_constant_speed = false; + bool floor_block_on_wall = true; bool slide_on_ceiling = true; int max_slides = 4; int platform_layer; real_t floor_max_angle = Math::deg2rad((real_t)45.0); float floor_snap_length = 0; + real_t free_mode_min_slide_angle = Math::deg2rad((real_t)15.0); Vector2 up_direction = Vector2(0.0, -1.0); - uint32_t exclude_body_layers = 0; + uint32_t moving_platform_ignore_layers = 0; Vector2 linear_velocity; Vector2 floor_normal; @@ -296,14 +326,14 @@ private: void set_safe_margin(real_t p_margin); real_t get_safe_margin() const; - bool is_stop_on_slope_enabled() const; - void set_stop_on_slope_enabled(bool p_enabled); + bool is_floor_stop_on_slope_enabled() const; + void set_floor_stop_on_slope_enabled(bool p_enabled); - bool is_constant_speed_on_floor_enabled() const; - void set_constant_speed_on_floor_enabled(bool p_enabled); + bool is_floor_constant_speed_enabled() const; + void set_floor_constant_speed_enabled(bool p_enabled); - bool is_move_on_floor_only_enabled() const; - void set_move_on_floor_only_enabled(bool p_enabled); + bool is_floor_block_on_wall_enabled() const; + void set_floor_block_on_wall_enabled(bool p_enabled); bool is_slide_on_ceiling_enabled() const; void set_slide_on_ceiling_enabled(bool p_enabled); @@ -317,10 +347,20 @@ private: real_t get_floor_snap_length(); void set_floor_snap_length(real_t p_floor_snap_length); - uint32_t get_exclude_body_layers() const; - void set_exclude_body_layers(const uint32_t p_exclude_layer); + real_t get_free_mode_min_slide_angle() const; + void set_free_mode_min_slide_angle(real_t p_radians); + + uint32_t get_moving_platform_ignore_layers() const; + void set_moving_platform_ignore_layers(const uint32_t p_exclude_layer); + + void set_motion_mode(MotionMode p_mode); + MotionMode get_motion_mode() const; + + void _move_and_slide_free(real_t p_delta); + void _move_and_slide_grounded(real_t p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity); Ref<KinematicCollision2D> _get_slide_collision(int p_bounce); + Ref<KinematicCollision2D> _get_last_slide_collision(); const Vector2 &get_up_direction() const; bool _on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up); void set_up_direction(const Vector2 &p_up_direction); @@ -331,29 +371,11 @@ private: protected: void _notification(int p_what); static void _bind_methods(); - -public: - void move_and_slide(); - - const Vector2 &get_linear_velocity() const; - void set_linear_velocity(const Vector2 &p_velocity); - - bool is_on_floor() const; - bool is_on_floor_only() const; - bool is_on_wall() const; - bool is_on_wall_only() const; - bool is_on_ceiling() const; - bool is_on_ceiling_only() const; - Vector2 get_floor_normal() const; - Vector2 get_platform_velocity() const; - - int get_slide_count() const; - PhysicsServer2D::MotionResult get_slide_collision(int p_bounce) const; - - CharacterBody2D(); - ~CharacterBody2D(); + virtual void _validate_property(PropertyInfo &property) const override; }; +VARIANT_ENUM_CAST(CharacterBody2D::MotionMode); + class KinematicCollision2D : public RefCounted { GDCLASS(KinematicCollision2D, RefCounted); @@ -370,6 +392,7 @@ public: Vector2 get_normal() const; Vector2 get_travel() const; Vector2 get_remainder() const; + real_t get_angle(const Vector2 &p_up_direction = Vector2(0.0, -1.0)) const; Object *get_local_shape() const; Object *get_collider() const; ObjectID get_collider_id() const; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 74eb3f2fc2..12aa1afc45 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -782,7 +782,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List // Get the tile data. TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); Ref<ShaderMaterial> mat = tile_data->tile_get_material(); - int z_index = layers[q.layer].z_index + tile_data->get_z_index(); + int z_index = tile_data->get_z_index(); // Quandrant pos. Vector2 position = map_to_world(q.coords * get_effective_quadrant_size(q.layer)); diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 5315e685a0..70361f4787 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -32,7 +32,15 @@ void BoneAttachment3D::_validate_property(PropertyInfo &property) const { if (property.name == "bone_name") { - Skeleton3D *parent = Object::cast_to<Skeleton3D>(get_parent()); + // Because it is a constant function, we cannot use the _get_skeleton_3d function. + const Skeleton3D *parent = nullptr; + if (use_external_skeleton) { + if (external_skeleton_node_cache.is_valid()) { + parent = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache)); + } + } else { + parent = Object::cast_to<Skeleton3D>(get_parent()); + } if (parent) { String names; @@ -52,55 +60,321 @@ void BoneAttachment3D::_validate_property(PropertyInfo &property) const { } } +bool BoneAttachment3D::_set(const StringName &p_path, const Variant &p_value) { + if (p_path == SNAME("override_pose")) { + set_override_pose(p_value); + } else if (p_path == SNAME("override_mode")) { + set_override_mode(p_value); + } else if (p_path == SNAME("use_external_skeleton")) { + set_use_external_skeleton(p_value); + } else if (p_path == SNAME("external_skeleton")) { + set_external_skeleton(p_value); + } + + return true; +} + +bool BoneAttachment3D::_get(const StringName &p_path, Variant &r_ret) const { + if (p_path == SNAME("override_pose")) { + r_ret = get_override_pose(); + } else if (p_path == SNAME("override_mode")) { + r_ret = get_override_mode(); + } else if (p_path == SNAME("use_external_skeleton")) { + r_ret = get_use_external_skeleton(); + } else if (p_path == SNAME("external_skeleton")) { + r_ret = get_external_skeleton(); + } + + return true; +} + +void BoneAttachment3D::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "override_pose", PROPERTY_HINT_NONE, "")); + if (override_pose) { + p_list->push_back(PropertyInfo(Variant::INT, "override_mode", PROPERTY_HINT_ENUM, "Global Pose Override, Local Pose Override, Custom Pose")); + } + + p_list->push_back(PropertyInfo(Variant::BOOL, "use_external_skeleton", PROPERTY_HINT_NONE, "")); + if (use_external_skeleton) { + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "external_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D")); + } +} + +TypedArray<String> BoneAttachment3D::get_configuration_warnings() const { + TypedArray<String> warnings = Node3D::get_configuration_warnings(); + + if (use_external_skeleton) { + if (external_skeleton_node_cache.is_null()) { + warnings.append(TTR("External Skeleton3D node not set! Please set a path to an external Skeleton3D node.")); + } + } 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.")); + } + } + + if (bone_idx == -1) { + warnings.append(TTR("BoneAttachment3D node is not bound to any bones! Please select a bone to attach this node.")); + } + + return warnings; +} + +void BoneAttachment3D::_update_external_skeleton_cache() { + external_skeleton_node_cache = ObjectID(); + if (has_node(external_skeleton_node)) { + Node *node = get_node(external_skeleton_node); + ERR_FAIL_COND_MSG(!node, "Cannot update external skeleton cache: Node cannot be found!"); + + // Make sure it's a skeleton3D + Skeleton3D *sk = Object::cast_to<Skeleton3D>(node); + ERR_FAIL_COND_MSG(!sk, "Cannot update external skeleton cache: Skeleton3D Nodepath does not point to a Skeleton3D node!"); + + external_skeleton_node_cache = node->get_instance_id(); + } else { + if (external_skeleton_node.is_empty()) { + BoneAttachment3D *parent_attachment = Object::cast_to<BoneAttachment3D>(get_parent()); + if (parent_attachment) { + parent_attachment->_update_external_skeleton_cache(); + if (parent_attachment->has_node(parent_attachment->external_skeleton_node)) { + Node *node = parent_attachment->get_node(parent_attachment->external_skeleton_node); + ERR_FAIL_COND_MSG(!node, "Cannot update external skeleton cache: Parent's Skeleton3D node cannot be found!"); + + // Make sure it's a skeleton3D + Skeleton3D *sk = Object::cast_to<Skeleton3D>(node); + ERR_FAIL_COND_MSG(!sk, "Cannot update external skeleton cache: Parent Skeleton3D Nodepath does not point to a Skeleton3D node!"); + + external_skeleton_node_cache = node->get_instance_id(); + external_skeleton_node = get_path_to(node); + } + } + } + } +} + void BoneAttachment3D::_check_bind() { - Skeleton3D *sk = Object::cast_to<Skeleton3D>(get_parent()); - if (sk) { - int idx = sk->find_bone(bone_name); - if (idx != -1) { - sk->bind_child_node_to_bone(idx, this); - set_transform(sk->get_bone_global_pose(idx)); + Skeleton3D *sk = _get_skeleton3d(); + + if (sk && !bound) { + if (bone_idx <= -1) { + bone_idx = sk->find_bone(bone_name); + } + if (bone_idx != -1) { + sk->call_deferred("connect", "bone_pose_changed", callable_mp(this, &BoneAttachment3D::on_bone_pose_update)); bound = true; + call_deferred(SNAME("on_bone_pose_update"), bone_idx); + } + } +} + +Skeleton3D *BoneAttachment3D::_get_skeleton3d() { + if (use_external_skeleton) { + if (external_skeleton_node_cache.is_valid()) { + return Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache)); + } else { + _update_external_skeleton_cache(); + if (external_skeleton_node_cache.is_valid()) { + return Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache)); + } } + } else { + return Object::cast_to<Skeleton3D>(get_parent()); } + return nullptr; } void BoneAttachment3D::_check_unbind() { if (bound) { - Skeleton3D *sk = Object::cast_to<Skeleton3D>(get_parent()); + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { - int idx = sk->find_bone(bone_name); - if (idx != -1) { - sk->unbind_child_node_from_bone(idx, this); - } + sk->disconnect(SNAME("bone_pose_changed"), callable_mp(this, &BoneAttachment3D::on_bone_pose_update)); } bound = false; } } +void BoneAttachment3D::_transform_changed() { + if (!is_inside_tree()) { + return; + } + + if (override_pose) { + Skeleton3D *sk = _get_skeleton3d(); + + ERR_FAIL_COND_MSG(!sk, "Cannot override pose: Skeleton not found!"); + ERR_FAIL_INDEX_MSG(bone_idx, sk->get_bone_count(), "Cannot override pose: Bone index is out of range!"); + + Transform3D our_trans = get_transform(); + if (use_external_skeleton) { + our_trans = sk->world_transform_to_global_pose(get_global_transform()); + } + + if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) { + sk->set_bone_global_pose_override(bone_idx, our_trans, 1.0, true); + } else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) { + sk->set_bone_local_pose_override(bone_idx, sk->global_pose_to_local_pose(bone_idx, our_trans), 1.0, true); + } else if (override_mode == OVERRIDE_MODES::MODE_CUSTOM_POSE) { + sk->set_bone_custom_pose(bone_idx, sk->global_pose_to_local_pose(bone_idx, our_trans)); + } + } +} + void BoneAttachment3D::set_bone_name(const String &p_name) { + bone_name = p_name; + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { + set_bone_idx(sk->find_bone(bone_name)); + } +} + +String BoneAttachment3D::get_bone_name() const { + return bone_name; +} + +void BoneAttachment3D::set_bone_idx(const int &p_idx) { if (is_inside_tree()) { _check_unbind(); } - bone_name = p_name; + bone_idx = p_idx; + + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { + if (bone_idx <= -1 || bone_idx >= sk->get_bone_count()) { + WARN_PRINT("Bone index out of range! Cannot connect BoneAttachment to node!"); + bone_idx = -1; + } else { + bone_name = sk->get_bone_name(bone_idx); + } + } if (is_inside_tree()) { _check_bind(); } + + notify_property_list_changed(); } -String BoneAttachment3D::get_bone_name() const { - return bone_name; +int BoneAttachment3D::get_bone_idx() const { + return bone_idx; +} + +void BoneAttachment3D::set_override_pose(bool p_override) { + override_pose = p_override; + set_notify_local_transform(override_pose); + set_process_internal(override_pose); + + if (!override_pose) { + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { + if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) { + sk->set_bone_global_pose_override(bone_idx, Transform3D(), 0.0, false); + } else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) { + sk->set_bone_local_pose_override(bone_idx, Transform3D(), 0.0, false); + } else if (override_mode == OVERRIDE_MODES::MODE_CUSTOM_POSE) { + sk->set_bone_custom_pose(bone_idx, Transform3D()); + } + } + _transform_changed(); + } + notify_property_list_changed(); +} + +bool BoneAttachment3D::get_override_pose() const { + return override_pose; +} + +void BoneAttachment3D::set_override_mode(int p_mode) { + if (override_pose) { + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { + if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) { + sk->set_bone_global_pose_override(bone_idx, Transform3D(), 0.0, false); + } else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) { + sk->set_bone_local_pose_override(bone_idx, Transform3D(), 0.0, false); + } else if (override_mode == OVERRIDE_MODES::MODE_CUSTOM_POSE) { + sk->set_bone_custom_pose(bone_idx, Transform3D()); + } + } + override_mode = p_mode; + _transform_changed(); + return; + } + override_mode = p_mode; +} + +int BoneAttachment3D::get_override_mode() const { + return override_mode; +} + +void BoneAttachment3D::set_use_external_skeleton(bool p_use_external) { + use_external_skeleton = p_use_external; + + if (use_external_skeleton) { + _check_unbind(); + _update_external_skeleton_cache(); + _check_bind(); + _transform_changed(); + } + + notify_property_list_changed(); +} + +bool BoneAttachment3D::get_use_external_skeleton() const { + return use_external_skeleton; +} + +void BoneAttachment3D::set_external_skeleton(NodePath p_path) { + external_skeleton_node = p_path; + _update_external_skeleton_cache(); + notify_property_list_changed(); +} + +NodePath BoneAttachment3D::get_external_skeleton() const { + return external_skeleton_node; } void BoneAttachment3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + if (use_external_skeleton) { + _update_external_skeleton_cache(); + } _check_bind(); } break; case NOTIFICATION_EXIT_TREE: { _check_unbind(); } break; + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + _transform_changed(); + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (_override_dirty) { + _override_dirty = false; + } + } + } +} + +void BoneAttachment3D::on_bone_pose_update(int p_bone_index) { + if (bone_idx == p_bone_index) { + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { + if (!override_pose) { + if (use_external_skeleton) { + set_global_transform(sk->global_pose_to_world_transform(sk->get_bone_global_pose(bone_idx))); + } else { + set_transform(sk->get_bone_global_pose(bone_idx)); + } + } else { + if (!_override_dirty) { + _transform_changed(); + _override_dirty = true; + } + } + } } } @@ -111,5 +385,21 @@ void BoneAttachment3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &BoneAttachment3D::set_bone_name); ClassDB::bind_method(D_METHOD("get_bone_name"), &BoneAttachment3D::get_bone_name); + ClassDB::bind_method(D_METHOD("set_bone_idx", "bone_idx"), &BoneAttachment3D::set_bone_idx); + ClassDB::bind_method(D_METHOD("get_bone_idx"), &BoneAttachment3D::get_bone_idx); + + ClassDB::bind_method(D_METHOD("on_bone_pose_update", "bone_index"), &BoneAttachment3D::on_bone_pose_update); + + ClassDB::bind_method(D_METHOD("set_override_pose", "override_pose"), &BoneAttachment3D::set_override_pose); + ClassDB::bind_method(D_METHOD("get_override_pose"), &BoneAttachment3D::get_override_pose); + ClassDB::bind_method(D_METHOD("set_override_mode", "override_mode"), &BoneAttachment3D::set_override_mode); + ClassDB::bind_method(D_METHOD("get_override_mode"), &BoneAttachment3D::get_override_mode); + + ClassDB::bind_method(D_METHOD("set_use_external_skeleton", "use_external_skeleton"), &BoneAttachment3D::set_use_external_skeleton); + ClassDB::bind_method(D_METHOD("get_use_external_skeleton"), &BoneAttachment3D::get_use_external_skeleton); + ClassDB::bind_method(D_METHOD("set_external_skeleton", "external_skeleton"), &BoneAttachment3D::set_external_skeleton); + ClassDB::bind_method(D_METHOD("get_external_skeleton"), &BoneAttachment3D::get_external_skeleton); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_idx"), "set_bone_idx", "get_bone_idx"); } diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h index 0c6d5f12b1..cf681cace8 100644 --- a/scene/3d/bone_attachment_3d.h +++ b/scene/3d/bone_attachment_3d.h @@ -38,20 +38,59 @@ class BoneAttachment3D : public Node3D { bool bound = false; String bone_name; + int bone_idx = -1; + + bool override_pose = false; + int override_mode = 0; + bool _override_dirty = false; + + enum OVERRIDE_MODES { + MODE_GLOBAL_POSE, + MODE_LOCAL_POSE, + MODE_CUSTOM_POSE + }; + + bool use_external_skeleton = false; + NodePath external_skeleton_node; + ObjectID external_skeleton_node_cache; void _check_bind(); void _check_unbind(); + void _transform_changed(); + void _update_external_skeleton_cache(); + Skeleton3D *_get_skeleton3d(); + protected: virtual void _validate_property(PropertyInfo &property) const override; + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; void _notification(int p_what); static void _bind_methods(); public: + virtual TypedArray<String> get_configuration_warnings() const override; + void set_bone_name(const String &p_name); String get_bone_name() const; + void set_bone_idx(const int &p_idx); + int get_bone_idx() const; + + void set_override_pose(bool p_override); + bool get_override_pose() const; + void set_override_mode(int p_mode); + int get_override_mode() const; + + void set_use_external_skeleton(bool p_external_skeleton); + bool get_use_external_skeleton() const; + void set_external_skeleton(NodePath p_skeleton); + NodePath get_external_skeleton() const; + + virtual void on_bone_pose_update(int p_bone_index); + BoneAttachment3D(); }; diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index e2095940ff..48ef41e015 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -212,7 +212,7 @@ TypedArray<String> CPUParticles3D::get_configuration_warnings() const { warnings.push_back(TTR("Nothing is visible because no mesh has been assigned.")); } - if (!anim_material_found && (get_param(PARAM_ANIM_SPEED) != 0.0 || get_param(PARAM_ANIM_OFFSET) != 0.0 || + if (!anim_material_found && (get_param_max(PARAM_ANIM_SPEED) != 0.0 || get_param_max(PARAM_ANIM_OFFSET) != 0.0 || get_param_curve(PARAM_ANIM_SPEED).is_valid() || get_param_curve(PARAM_ANIM_OFFSET).is_valid())) { warnings.push_back(TTR("CPUParticles3D animation requires the usage of a StandardMaterial3D whose Billboard Mode is set to \"Particle Billboard\".")); } @@ -263,28 +263,33 @@ real_t CPUParticles3D::get_flatness() const { return flatness; } -void CPUParticles3D::set_param(Parameter p_param, real_t p_value) { +void CPUParticles3D::set_param_min(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - parameters[p_param] = p_value; + parameters_min[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_max(p_param, p_value); + } } -real_t CPUParticles3D::get_param(Parameter p_param) const { +real_t CPUParticles3D::get_param_min(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return parameters[p_param]; + return parameters_min[p_param]; } -void CPUParticles3D::set_param_randomness(Parameter p_param, real_t p_value) { +void CPUParticles3D::set_param_max(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - - randomness[p_param] = p_value; + parameters_max[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_min(p_param, p_value); + } } -real_t CPUParticles3D::get_param_randomness(Parameter p_param) const { +real_t CPUParticles3D::get_param_max(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return randomness[p_param]; + return parameters_max[p_param]; } static void _adjust_curve_range(const Ref<Curve> &p_curve, real_t p_min, real_t p_max) { @@ -417,6 +422,23 @@ void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) { emission_ring_inner_radius = p_radius; } +void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) { + scale_curve_x = p_scale_curve; +} + +void CPUParticles3D::set_scale_curve_y(Ref<Curve> p_scale_curve) { + scale_curve_y = p_scale_curve; +} + +void CPUParticles3D::set_scale_curve_z(Ref<Curve> p_scale_curve) { + scale_curve_z = p_scale_curve; +} + +void CPUParticles3D::set_split_scale(bool p_split_scale) { + split_scale = p_split_scale; + notify_property_list_changed(); +} + real_t CPUParticles3D::get_emission_sphere_radius() const { return emission_sphere_radius; } @@ -465,6 +487,22 @@ Vector3 CPUParticles3D::get_gravity() const { return gravity; } +Ref<Curve> CPUParticles3D::get_scale_curve_x() const { + return scale_curve_x; +} + +Ref<Curve> CPUParticles3D::get_scale_curve_y() const { + return scale_curve_y; +} + +Ref<Curve> CPUParticles3D::get_scale_curve_z() const { + return scale_curve_z; +} + +bool CPUParticles3D::get_split_scale() { + return split_scale; +} + void CPUParticles3D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { property.usage = PROPERTY_USAGE_NONE; @@ -489,6 +527,10 @@ void CPUParticles3D::_validate_property(PropertyInfo &property) const { if (property.name.begins_with("orbit_") && !particle_flags[PARTICLE_FLAG_DISABLE_Z]) { property.usage = PROPERTY_USAGE_NONE; } + + if (property.name.begins_with("scale_curve_") && !split_scale) { + property.usage = PROPERTY_USAGE_NONE; + } } static uint32_t idhash(uint32_t x) { @@ -707,7 +749,7 @@ void CPUParticles3D::_particles_process(double p_delta) { if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); Vector3 rot = Vector3(Math::cos(angle1_rad), Math::sin(angle1_rad), 0.0); - p.velocity = rot * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp((real_t)1.0, real_t(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], Math::randf()); } else { //initiate velocity spread in 3D real_t angle1_rad = Math::deg2rad((Math::randf() * (real_t)2.0 - (real_t)1.0) * spread); @@ -731,13 +773,13 @@ void CPUParticles3D::_particles_process(double p_delta) { binormal.normalize(); Vector3 normal = binormal.cross(direction_nrm); spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z; - p.velocity = spread_direction * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp((real_t)1.0, real_t(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + p.velocity = spread_direction * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], float(Math::randf())); } - real_t base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp((real_t)1.0, p.angle_rand, randomness[PARAM_ANGLE]); + real_t base_angle = tex_angle * Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); p.custom[0] = Math::deg2rad(base_angle); //angle p.custom[1] = 0.0; //phase - p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp((real_t)1.0, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]); //animation offset (0-1) + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); //animation offset (0-1) p.transform = Transform3D(); p.time = 0; p.lifetime = lifetime * (1.0 - Math::randf() * lifetime_randomness); @@ -894,26 +936,25 @@ void CPUParticles3D::_particles_process(double p_delta) { position.z = 0.0; } //apply linear acceleration - force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector3(); + force += p.velocity.length() > 0.0 ? p.velocity.normalized() * tex_linear_accel * Math::lerp(parameters_min[PARAM_LINEAR_ACCEL], parameters_max[PARAM_LINEAR_ACCEL], rand_from_seed(alt_seed)) : Vector3(); //apply radial acceleration Vector3 org = emission_xform.origin; Vector3 diff = position - org; - force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector3(); - //apply tangential acceleration; + force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector3(); if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { Vector2 yx = Vector2(diff.y, diff.x); Vector2 yx2 = (yx * Vector2(-1.0, 1.0)).normalized(); - force += yx.length() > 0.0 ? Vector3(yx2.x, yx2.y, 0.0) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + force += yx.length() > 0.0 ? Vector3(yx2.x, yx2.y, 0.0) * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector3(); } else { Vector3 crossDiff = diff.normalized().cross(gravity.normalized()); - force += crossDiff.length() > 0.0 ? crossDiff.normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + force += crossDiff.length() > 0.0 ? crossDiff.normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector3(); } //apply attractor forces p.velocity += force * local_delta; //orbit velocity if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - real_t orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); + real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(alt_seed)); if (orbit_amount != 0.0) { real_t ang = orbit_amount * local_delta * Math_TAU; // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, @@ -927,9 +968,10 @@ void CPUParticles3D::_particles_process(double p_delta) { if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } - if (parameters[PARAM_DAMPING] + tex_damping > 0.0) { + + if (parameters_max[PARAM_DAMPING] + tex_damping > 0.0) { real_t v = p.velocity.length(); - real_t damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]); + real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(alt_seed)); v -= damp * local_delta; if (v < 0.0) { p.velocity = Vector3(); @@ -937,17 +979,38 @@ void CPUParticles3D::_particles_process(double p_delta) { p.velocity = p.velocity.normalized() * v; } } - real_t base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp((real_t)1.0, p.angle_rand, randomness[PARAM_ANGLE]); - base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]); + real_t base_angle = (tex_angle)*Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); + base_angle += p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(alt_seed)); p.custom[0] = Math::deg2rad(base_angle); //angle - p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp((real_t)1.0, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + p.custom[1] * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); //angle + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand) + p.custom[1] * tex_anim_speed * Math::lerp(parameters_min[PARAM_ANIM_SPEED], parameters_max[PARAM_ANIM_SPEED], rand_from_seed(alt_seed)); //angle } //apply color //apply hue rotation - real_t tex_scale = 1.0; - if (curve_parameters[PARAM_SCALE].is_valid()) { - tex_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + Vector3 tex_scale = Vector3(1.0, 1.0, 1.0); + if (split_scale) { + if (scale_curve_x.is_valid()) { + tex_scale.x = scale_curve_x->interpolate(tv); + } else { + tex_scale.x = 1.0; + } + if (scale_curve_y.is_valid()) { + tex_scale.y = scale_curve_y->interpolate(tv); + } else { + tex_scale.y = 1.0; + } + if (scale_curve_z.is_valid()) { + tex_scale.z = scale_curve_z->interpolate(tv); + } else { + tex_scale.z = 1.0; + } + } else { + if (curve_parameters[PARAM_SCALE].is_valid()) { + float tmp_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + tex_scale.x = tmp_scale; + tex_scale.y = tmp_scale; + tex_scale.z = tmp_scale; + } } real_t tex_hue_variation = 0.0; @@ -955,7 +1018,7 @@ void CPUParticles3D::_particles_process(double p_delta) { tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(tv); } - real_t hue_rot_angle = (parameters[PARAM_HUE_VARIATION] + tex_hue_variation) * Math_TAU * Math::lerp(1, p.hue_rot_rand * 2.0f - 1.0f, randomness[PARAM_HUE_VARIATION]); + real_t hue_rot_angle = (tex_hue_variation)*Math_TAU * Math::lerp(parameters_min[PARAM_HUE_VARIATION], parameters_max[PARAM_HUE_VARIATION], p.hue_rot_rand); real_t hue_rot_c = Math::cos(hue_rot_angle); real_t hue_rot_s = Math::sin(hue_rot_angle); @@ -1025,13 +1088,21 @@ void CPUParticles3D::_particles_process(double p_delta) { } } + p.transform.basis = p.transform.basis.orthonormalized(); //scale by scale - real_t base_scale = tex_scale * Math::lerp(parameters[PARAM_SCALE], (real_t)1.0, p.scale_rand * randomness[PARAM_SCALE]); - if (base_scale < CMP_EPSILON) { - base_scale = CMP_EPSILON; + + Vector3 base_scale = tex_scale * Math::lerp(parameters_min[PARAM_SCALE], parameters_max[PARAM_SCALE], p.scale_rand); + if (base_scale.x < CMP_EPSILON) { + base_scale.x = CMP_EPSILON; + } + if (base_scale.y < CMP_EPSILON) { + base_scale.y = CMP_EPSILON; + } + if (base_scale.z < CMP_EPSILON) { + base_scale.z = CMP_EPSILON; } - p.transform.basis.scale(Vector3(1, 1, 1) * base_scale); + p.transform.basis.scale(base_scale); if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { p.velocity.z = 0.0; @@ -1267,18 +1338,25 @@ void CPUParticles3D::convert_from_particles(Node *p_particles) { set_emission_shape(EmissionShape(material->get_emission_shape())); set_emission_sphere_radius(material->get_emission_sphere_radius()); set_emission_box_extents(material->get_emission_box_extents()); + Ref<CurveXYZTexture> scale3D = material->get_param_texture(ParticlesMaterial::PARAM_SCALE); + if (scale3D.is_valid()) { + split_scale = true; + scale_curve_x = scale3D->get_curve_x(); + scale_curve_y = scale3D->get_curve_y(); + scale_curve_z = scale3D->get_curve_z(); + } set_gravity(material->get_gravity()); set_lifetime_randomness(material->get_lifetime_randomness()); #define CONVERT_PARAM(m_param) \ - set_param(m_param, material->get_param(ParticlesMaterial::m_param)); \ + set_param_min(m_param, material->get_param_min(ParticlesMaterial::m_param)); \ { \ Ref<CurveTexture> ctex = material->get_param_texture(ParticlesMaterial::m_param); \ if (ctex.is_valid()) \ set_param_curve(m_param, ctex->get_curve()); \ } \ - set_param_randomness(m_param, material->get_param_randomness(ParticlesMaterial::m_param)); + set_param_max(m_param, material->get_param_max(ParticlesMaterial::m_param)); CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); @@ -1364,11 +1442,11 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flatness", "amount"), &CPUParticles3D::set_flatness); ClassDB::bind_method(D_METHOD("get_flatness"), &CPUParticles3D::get_flatness); - ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &CPUParticles3D::set_param); - ClassDB::bind_method(D_METHOD("get_param", "param"), &CPUParticles3D::get_param); + ClassDB::bind_method(D_METHOD("set_param_min", "param", "value"), &CPUParticles3D::set_param_min); + ClassDB::bind_method(D_METHOD("get_param_min", "param"), &CPUParticles3D::get_param_min); - ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &CPUParticles3D::set_param_randomness); - ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &CPUParticles3D::get_param_randomness); + ClassDB::bind_method(D_METHOD("set_param_max", "param", "value"), &CPUParticles3D::set_param_max); + ClassDB::bind_method(D_METHOD("get_param_max", "param"), &CPUParticles3D::get_param_max); ClassDB::bind_method(D_METHOD("set_param_curve", "param", "curve"), &CPUParticles3D::set_param_curve); ClassDB::bind_method(D_METHOD("get_param_curve", "param"), &CPUParticles3D::get_param_curve); @@ -1415,6 +1493,18 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity); + ClassDB::bind_method(D_METHOD("get_split_scale"), &CPUParticles3D::get_split_scale); + ClassDB::bind_method(D_METHOD("set_split_scale", "split_scale"), &CPUParticles3D::set_split_scale); + + ClassDB::bind_method(D_METHOD("get_scale_curve_x"), &CPUParticles3D::get_scale_curve_x); + ClassDB::bind_method(D_METHOD("set_scale_curve_x", "scale_curve"), &CPUParticles3D::set_scale_curve_x); + + ClassDB::bind_method(D_METHOD("get_scale_curve_y"), &CPUParticles3D::get_scale_curve_y); + ClassDB::bind_method(D_METHOD("set_scale_curve_y", "scale_curve"), &CPUParticles3D::set_scale_curve_y); + + ClassDB::bind_method(D_METHOD("get_scale_curve_z"), &CPUParticles3D::get_scale_curve_z); + ClassDB::bind_method(D_METHOD("set_scale_curve_z", "scale_curve"), &CPUParticles3D::set_scale_curve_z); + ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles3D::convert_from_particles); ADD_GROUP("Emission Shape", "emission_"); @@ -1439,54 +1529,58 @@ void CPUParticles3D::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY); ADD_GROUP("Angular Velocity", "angular_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_min", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_min", "get_param_min", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_max", "get_param_max", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param", "get_param", PARAM_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGLE); ADD_GROUP("Scale", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_amount_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_SCALE); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "split_scale"), "set_split_scale", "get_split_scale"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_x", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_x", "get_scale_curve_x"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_y", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_y", "get_scale_curve_y"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_z", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_z", "get_scale_curve_z"); ADD_GROUP("Color", ""); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_color_ramp", "get_color_ramp"); ADD_GROUP("Hue Variation", "hue_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param", "get_param", PARAM_HUE_VARIATION); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_HUE_VARIATION); ADD_GROUP("Animation", "anim_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_OFFSET); BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); @@ -1527,18 +1621,30 @@ CPUParticles3D::CPUParticles3D() { set_emitting(true); set_amount(8); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); + set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_min(PARAM_ANGULAR_VELOCITY, 0); + set_param_min(PARAM_ORBIT_VELOCITY, 0); + set_param_min(PARAM_LINEAR_ACCEL, 0); + set_param_min(PARAM_RADIAL_ACCEL, 0); + set_param_min(PARAM_TANGENTIAL_ACCEL, 0); + set_param_min(PARAM_DAMPING, 0); + set_param_min(PARAM_ANGLE, 0); + set_param_min(PARAM_SCALE, 1); + set_param_min(PARAM_HUE_VARIATION, 0); + set_param_min(PARAM_ANIM_SPEED, 0); + set_param_min(PARAM_ANIM_OFFSET, 0); + set_param_max(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_max(PARAM_ANGULAR_VELOCITY, 0); + set_param_max(PARAM_ORBIT_VELOCITY, 0); + set_param_max(PARAM_LINEAR_ACCEL, 0); + set_param_max(PARAM_RADIAL_ACCEL, 0); + set_param_max(PARAM_TANGENTIAL_ACCEL, 0); + set_param_max(PARAM_DAMPING, 0); + set_param_max(PARAM_ANGLE, 0); + set_param_max(PARAM_SCALE, 1); + set_param_max(PARAM_HUE_VARIATION, 0); + set_param_max(PARAM_ANIM_SPEED, 0); + set_param_max(PARAM_ANIM_OFFSET, 0); set_emission_shape(EMISSION_SHAPE_POINT); set_emission_sphere_radius(1); set_emission_box_extents(Vector3(1, 1, 1)); @@ -1549,10 +1655,6 @@ CPUParticles3D::CPUParticles3D() { set_gravity(Vector3(0, -9.8, 0)); - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } - for (int i = 0; i < PARTICLE_FLAG_MAX; i++) { particle_flags[i] = false; } diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index 5b60322f05..160814ead4 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -154,8 +154,8 @@ private: real_t spread = 45.0; real_t flatness = 0.0; - real_t parameters[PARAM_MAX]; - real_t randomness[PARAM_MAX] = {}; + real_t parameters_min[PARAM_MAX]; + real_t parameters_max[PARAM_MAX] = {}; Ref<Curve> curve_parameters[PARAM_MAX]; Color color = Color(1, 1, 1, 1); @@ -175,6 +175,11 @@ private: real_t emission_ring_radius; real_t emission_ring_inner_radius; + Ref<Curve> scale_curve_x; + Ref<Curve> scale_curve_y; + Ref<Curve> scale_curve_z; + bool split_scale = false; + Vector3 gravity = Vector3(0, -9.8, 0); void _update_internal(); @@ -246,11 +251,11 @@ public: void set_flatness(real_t p_flatness); real_t get_flatness() const; - void set_param(Parameter p_param, real_t p_value); - real_t get_param(Parameter p_param) const; + void set_param_min(Parameter p_param, real_t p_value); + real_t get_param_min(Parameter p_param) const; - void set_param_randomness(Parameter p_param, real_t p_value); - real_t get_param_randomness(Parameter p_param) const; + void set_param_max(Parameter p_param, real_t p_value); + real_t get_param_max(Parameter p_param) const; void set_param_curve(Parameter p_param, const Ref<Curve> &p_curve); Ref<Curve> get_param_curve(Parameter p_param) const; @@ -275,6 +280,10 @@ public: void set_emission_ring_height(real_t p_height); void set_emission_ring_radius(real_t p_radius); void set_emission_ring_inner_radius(real_t p_radius); + void set_scale_curve_x(Ref<Curve> p_scale_curve); + void set_scale_curve_y(Ref<Curve> p_scale_curve); + void set_scale_curve_z(Ref<Curve> p_scale_curve); + void set_split_scale(bool p_split_scale); EmissionShape get_emission_shape() const; real_t get_emission_sphere_radius() const; @@ -287,6 +296,10 @@ public: real_t get_emission_ring_height() const; real_t get_emission_ring_radius() const; real_t get_emission_ring_inner_radius() const; + Ref<Curve> get_scale_curve_x() const; + Ref<Curve> get_scale_curve_y() const; + Ref<Curve> get_scale_curve_z() const; + bool get_split_scale(); void set_gravity(const Vector3 &p_gravity); Vector3 get_gravity() const; diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index d56228df66..baf28ae102 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -312,7 +312,7 @@ TypedArray<String> GPUParticles3D::get_configuration_warnings() const { } else { const ParticlesMaterial *process = Object::cast_to<ParticlesMaterial>(process_material.ptr()); if (!anim_material_found && process && - (process->get_param(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || + (process->get_param_max(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param_max(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_SPEED).is_valid() || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_OFFSET).is_valid())) { warnings.push_back(TTR("Particles animation requires the usage of a BaseMaterial3D whose Billboard Mode is set to \"Particle Billboard\".")); } diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index 508f8a70df..ab417fafdd 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -212,10 +212,6 @@ void Light3D::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } - if (get_light_type() == RS::LIGHT_SPOT && property.name == "shadow_normal_bias") { - property.usage = PROPERTY_USAGE_NONE; - } - if (get_light_type() == RS::LIGHT_DIRECTIONAL && property.name == "light_projector") { property.usage = PROPERTY_USAGE_NONE; } @@ -425,9 +421,7 @@ DirectionalLight3D::DirectionalLight3D() : set_param(PARAM_SHADOW_MAX_DISTANCE, 100); set_param(PARAM_SHADOW_FADE_START, 0.8); // Increase the default shadow bias to better suit most scenes. - // Leave normal bias untouched as it doesn't benefit DirectionalLight3D as much as OmniLight3D. set_param(PARAM_SHADOW_BIAS, 0.1); - set_param(PARAM_SHADOW_NORMAL_BIAS, 1.0); set_shadow_mode(SHADOW_PARALLEL_4_SPLITS); blend_splits = false; } @@ -468,8 +462,7 @@ OmniLight3D::OmniLight3D() : Light3D(RenderingServer::LIGHT_OMNI) { set_shadow_mode(SHADOW_CUBE); // Increase the default shadow biases to better suit most scenes. - set_param(PARAM_SHADOW_BIAS, 0.1); - set_param(PARAM_SHADOW_NORMAL_BIAS, 2.0); + set_param(PARAM_SHADOW_BIAS, 0.2); } TypedArray<String> SpotLight3D::get_configuration_warnings() const { diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 12c91271d4..4c36618c99 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -140,27 +140,27 @@ bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::M } // Check depth of recovery. - real_t projected_length = r_result.motion.dot(motion_normal); - Vector3 recovery = r_result.motion - motion_normal * projected_length; + real_t projected_length = r_result.travel.dot(motion_normal); + Vector3 recovery = r_result.travel - motion_normal * projected_length; real_t recovery_length = recovery.length(); // Fixes cases where canceling slide causes the motion to go too deep into the ground, // because we're only taking rest information into account and not general recovery. if (recovery_length < (real_t)p_margin + precision) { // Apply adjustment to motion. - r_result.motion = motion_normal * projected_length; - r_result.remainder = p_motion - r_result.motion; + r_result.travel = motion_normal * projected_length; + r_result.remainder = p_motion - r_result.travel; } } } for (int i = 0; i < 3; i++) { if (locked_axis & (1 << i)) { - r_result.motion[i] = 0; + r_result.travel[i] = 0; } } if (!p_test_only) { - gt.origin += r_result.motion; + gt.origin += r_result.travel; set_global_transform(gt); } @@ -599,9 +599,9 @@ void RigidBody3D::_direct_state_changed(Object *p_state) { sleeping = state->is_sleeping(); emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); } - if (get_script_instance()) { - get_script_instance()->call("_integrate_forces", state); - } + + GDVIRTUAL_CALL(_integrate_forces, state); + set_ignore_transform_notification(false); _on_transform_changed(); @@ -1022,7 +1022,7 @@ void RigidBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_colliding_bodies"), &RigidBody3D::get_colliding_bodies); - BIND_VMETHOD(MethodInfo("_integrate_forces", PropertyInfo(Variant::OBJECT, "state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectBodyState3D"))); + 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,65535,0.01,exp"), "set_mass", "get_mass"); @@ -1079,7 +1079,7 @@ void RigidBody3D::_reload_physics_characteristics() { //so, if you pass 45 as limit, avoid numerical precision errors when angle is 45. #define FLOOR_ANGLE_THRESHOLD 0.01 -void CharacterBody3D::move_and_slide() { +bool CharacterBody3D::move_and_slide() { Vector3 body_velocity_normal = linear_velocity.normalized(); bool was_on_floor = on_floor; @@ -1126,7 +1126,7 @@ void CharacterBody3D::move_and_slide() { // No sliding on first attempt to keep floor motion stable when possible, // when stop on slope is enabled. - bool sliding_enabled = !stop_on_slope; + bool sliding_enabled = !floor_stop_on_slope; for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionResult result; @@ -1141,17 +1141,17 @@ void CharacterBody3D::move_and_slide() { motion_results.push_back(result); _set_collision_direction(result); - if (on_floor && stop_on_slope) { + if (on_floor && floor_stop_on_slope) { if ((body_velocity_normal + up_direction).length() < 0.01) { Transform3D gt = get_global_transform(); - if (result.motion.length() > margin) { - gt.origin -= result.motion.slide(up_direction); + if (result.travel.length() > margin) { + gt.origin -= result.travel.slide(up_direction); } else { - gt.origin -= result.motion; + gt.origin -= result.travel; } set_global_transform(gt); linear_velocity = Vector3(); - return; + return true; } } @@ -1176,44 +1176,44 @@ void CharacterBody3D::move_and_slide() { } } - if (!on_floor && !on_wall) { - // Add last platform velocity when just left a moving platform. - linear_velocity += current_floor_velocity; - } - - if (!was_on_floor || snap == Vector3()) { - return; - } - - // Apply snap. - Transform3D gt = get_global_transform(); - PhysicsServer3D::MotionResult result; - if (move_and_collide(snap, result, margin, true, false)) { - bool apply = true; - if (up_direction != Vector3()) { - if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { - on_floor = true; - floor_normal = result.collision_normal; - on_floor_body = result.collider; - floor_velocity = result.collider_velocity; - if (stop_on_slope) { - // move and collide may stray the object a bit because of pre un-stucking, - // so only ensure that motion happens on floor direction in this case. - if (result.motion.length() > margin) { - result.motion = result.motion.project(up_direction); - } else { - result.motion = Vector3(); + if (was_on_floor && snap != Vector3()) { + // Apply snap. + Transform3D gt = get_global_transform(); + PhysicsServer3D::MotionResult result; + if (move_and_collide(snap, result, margin, true, false)) { + bool apply = true; + if (up_direction != Vector3()) { + if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + on_floor = true; + floor_normal = result.collision_normal; + on_floor_body = result.collider; + floor_velocity = result.collider_velocity; + if (floor_stop_on_slope) { + // move and collide may stray the object a bit because of pre un-stucking, + // so only ensure that motion happens on floor direction in this case. + if (result.travel.length() > margin) { + result.travel = result.travel.project(up_direction); + } else { + result.travel = Vector3(); + } } + } else { + apply = false; //snapped with floor direction, but did not snap to a floor, do not snap. } - } else { - apply = false; //snapped with floor direction, but did not snap to a floor, do not snap. } - } - if (apply) { - gt.origin += result.motion; - set_global_transform(gt); + if (apply) { + gt.origin += result.travel; + set_global_transform(gt); + } } } + + if (!on_floor && !on_wall) { + // Add last platform velocity when just left a moving platform. + linear_velocity += current_floor_velocity; + } + + return motion_results.size() > 0; } void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResult &p_result) { @@ -1221,12 +1221,12 @@ void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResu //all is a wall on_wall = true; } else { - if (Math::acos(p_result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor + if (p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor on_floor = true; floor_normal = p_result.collision_normal; on_floor_body = p_result.collider; floor_velocity = p_result.collider_velocity; - } else if (Math::acos(p_result.collision_normal.dot(-up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling + } else if (p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling on_ceiling = true; } else { on_wall = true; @@ -1256,23 +1256,40 @@ bool CharacterBody3D::is_on_floor() const { return on_floor; } +bool CharacterBody3D::is_on_floor_only() const { + return on_floor && !on_wall && !on_ceiling; +} + bool CharacterBody3D::is_on_wall() const { return on_wall; } +bool CharacterBody3D::is_on_wall_only() const { + return on_wall && !on_floor && !on_ceiling; +} + bool CharacterBody3D::is_on_ceiling() const { return on_ceiling; } +bool CharacterBody3D::is_on_ceiling_only() const { + return on_ceiling && !on_floor && !on_wall; +} + Vector3 CharacterBody3D::get_floor_normal() const { return floor_normal; } -Vector3 CharacterBody3D::get_floor_velocity() const { +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 { return floor_velocity; } -int CharacterBody3D::get_slide_count() const { +int CharacterBody3D::get_slide_collision_count() const { return motion_results.size(); } @@ -1296,12 +1313,19 @@ Ref<KinematicCollision3D> CharacterBody3D::_get_slide_collision(int p_bounce) { return slide_colliders[p_bounce]; } -bool CharacterBody3D::is_stop_on_slope_enabled() const { - return stop_on_slope; +Ref<KinematicCollision3D> CharacterBody3D::_get_last_slide_collision() { + if (motion_results.size() == 0) { + return Ref<KinematicCollision3D>(); + } + return _get_slide_collision(motion_results.size() - 1); +} + +bool CharacterBody3D::is_floor_stop_on_slope_enabled() const { + return floor_stop_on_slope; } -void CharacterBody3D::set_stop_on_slope_enabled(bool p_enabled) { - stop_on_slope = p_enabled; +void CharacterBody3D::set_floor_stop_on_slope_enabled(bool p_enabled) { + floor_stop_on_slope = p_enabled; } int CharacterBody3D::get_max_slides() const { @@ -1359,8 +1383,8 @@ void CharacterBody3D::_bind_methods() { 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); - ClassDB::bind_method(D_METHOD("is_stop_on_slope_enabled"), &CharacterBody3D::is_stop_on_slope_enabled); - ClassDB::bind_method(D_METHOD("set_stop_on_slope_enabled", "enabled"), &CharacterBody3D::set_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("is_floor_stop_on_slope_enabled"), &CharacterBody3D::is_floor_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("set_floor_stop_on_slope_enabled", "enabled"), &CharacterBody3D::set_floor_stop_on_slope_enabled); ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody3D::get_max_slides); ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody3D::set_max_slides); ClassDB::bind_method(D_METHOD("get_floor_max_angle"), &CharacterBody3D::get_floor_max_angle); @@ -1371,20 +1395,26 @@ void CharacterBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody3D::set_up_direction); ClassDB::bind_method(D_METHOD("is_on_floor"), &CharacterBody3D::is_on_floor); + ClassDB::bind_method(D_METHOD("is_on_floor_only"), &CharacterBody3D::is_on_floor_only); ClassDB::bind_method(D_METHOD("is_on_ceiling"), &CharacterBody3D::is_on_ceiling); + ClassDB::bind_method(D_METHOD("is_on_ceiling_only"), &CharacterBody3D::is_on_ceiling_only); ClassDB::bind_method(D_METHOD("is_on_wall"), &CharacterBody3D::is_on_wall); + ClassDB::bind_method(D_METHOD("is_on_wall_only"), &CharacterBody3D::is_on_wall_only); ClassDB::bind_method(D_METHOD("get_floor_normal"), &CharacterBody3D::get_floor_normal); - ClassDB::bind_method(D_METHOD("get_floor_velocity"), &CharacterBody3D::get_floor_velocity); + ClassDB::bind_method(D_METHOD("get_floor_angle", "up_direction"), &CharacterBody3D::get_floor_angle, DEFVAL(Vector3(0.0, 1.0, 0.0))); + ClassDB::bind_method(D_METHOD("get_platform_velocity"), &CharacterBody3D::get_platform_velocity); - ClassDB::bind_method(D_METHOD("get_slide_count"), &CharacterBody3D::get_slide_count); + ClassDB::bind_method(D_METHOD("get_slide_collision_count"), &CharacterBody3D::get_slide_collision_count); ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody3D::_get_slide_collision); + ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody3D::_get_last_slide_collision); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stop_on_slope"), "set_stop_on_slope_enabled", "is_stop_on_slope_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_RANGE, "1,8,1,or_greater"), "set_max_slides", "get_max_slides"); - 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::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "snap"), "set_snap", "get_snap"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_direction"), "set_up_direction", "get_up_direction"); + ADD_GROUP("Floor", "floor_"); + 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::BOOL, "floor_stop_on_slope"), "set_floor_stop_on_slope_enabled", "is_floor_stop_on_slope_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); } @@ -1411,13 +1441,18 @@ Vector3 KinematicCollision3D::get_normal() const { } Vector3 KinematicCollision3D::get_travel() const { - return result.motion; + return result.travel; } Vector3 KinematicCollision3D::get_remainder() const { return result.remainder; } +real_t KinematicCollision3D::get_angle(const Vector3 &p_up_direction) const { + ERR_FAIL_COND_V(p_up_direction == Vector3(), 0); + return result.get_angle(p_up_direction); +} + Object *KinematicCollision3D::get_local_shape() const { if (!owner) { return nullptr; @@ -1472,6 +1507,7 @@ void KinematicCollision3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_normal"), &KinematicCollision3D::get_normal); ClassDB::bind_method(D_METHOD("get_travel"), &KinematicCollision3D::get_travel); ClassDB::bind_method(D_METHOD("get_remainder"), &KinematicCollision3D::get_remainder); + ClassDB::bind_method(D_METHOD("get_angle", "up_direction"), &KinematicCollision3D::get_angle, DEFVAL(Vector3(0.0, 1.0, 0.0))); ClassDB::bind_method(D_METHOD("get_local_shape"), &KinematicCollision3D::get_local_shape); ClassDB::bind_method(D_METHOD("get_collider"), &KinematicCollision3D::get_collider); ClassDB::bind_method(D_METHOD("get_collider_id"), &KinematicCollision3D::get_collider_id); @@ -2199,7 +2235,6 @@ void PhysicalBone3D::_notification(int p_what) { if (parent_skeleton) { if (-1 != bone_id) { parent_skeleton->unbind_physical_bone_from_bone(bone_id); - parent_skeleton->unbind_child_node_from_bone(bone_id, this); bone_id = -1; } } @@ -2656,7 +2691,6 @@ void PhysicalBone3D::update_bone_id() { if (-1 != bone_id) { // Assert the unbind from old node parent_skeleton->unbind_physical_bone_from_bone(bone_id); - parent_skeleton->unbind_child_node_from_bone(bone_id, this); } bone_id = new_bone_id; diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index b076560ead..26b9a39047 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -132,6 +132,8 @@ public: MODE_KINEMATIC, }; + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState3D *) + protected: bool can_sleep = true; PhysicsDirectBodyState3D *state = nullptr; @@ -278,7 +280,7 @@ class CharacterBody3D : public PhysicsBody3D { private: real_t margin = 0.001; - bool stop_on_slope = false; + bool floor_stop_on_slope = false; int max_slides = 4; real_t floor_max_angle = Math::deg2rad((real_t)45.0); Vector3 snap; @@ -296,14 +298,15 @@ private: Vector<Ref<KinematicCollision3D>> slide_colliders; Ref<KinematicCollision3D> _get_slide_collision(int p_bounce); + Ref<KinematicCollision3D> _get_last_slide_collision(); void _set_collision_direction(const PhysicsServer3D::MotionResult &p_result); void set_safe_margin(real_t p_margin); real_t get_safe_margin() const; - bool is_stop_on_slope_enabled() const; - void set_stop_on_slope_enabled(bool p_enabled); + bool is_floor_stop_on_slope_enabled() const; + void set_floor_stop_on_slope_enabled(bool p_enabled); int get_max_slides() const; void set_max_slides(int p_max_slides); @@ -322,18 +325,22 @@ protected: static void _bind_methods(); public: - void move_and_slide(); + bool move_and_slide(); virtual Vector3 get_linear_velocity() const override; void set_linear_velocity(const Vector3 &p_velocity); bool is_on_floor() const; + bool is_on_floor_only() const; bool is_on_wall() const; + bool is_on_wall_only() const; bool is_on_ceiling() const; + bool is_on_ceiling_only() const; Vector3 get_floor_normal() const; - Vector3 get_floor_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; - int get_slide_count() const; + int get_slide_collision_count() const; PhysicsServer3D::MotionResult get_slide_collision(int p_bounce) const; CharacterBody3D(); @@ -356,6 +363,7 @@ public: Vector3 get_normal() const; Vector3 get_travel() const; Vector3 get_remainder() const; + real_t get_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; Object *get_local_shape() const; Object *get_collider() const; ObjectID get_collider_id() const; diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index c996735761..94fb49ae81 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -31,7 +31,10 @@ #include "skeleton_3d.h" #include "core/object/message_queue.h" +#include "core/variant/type_info.h" #include "scene/3d/physics_body_3d.h" +#include "scene/resources/skeleton_modification_3d.h" +#include "scene/resources/surface_tool.h" #include "scene/scene_string_names.h" void SkinReference::_skin_changed() { @@ -68,6 +71,13 @@ SkinReference::~SkinReference() { bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; +#ifndef _3D_DISABLED + if (path.begins_with("modification_stack")) { + set_modification_stack(p_value); + return true; + } +#endif //_3D_DISABLED + if (!path.begins_with("bones/")) { return false; } @@ -100,6 +110,13 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; +#ifndef _3D_DISABLED + if (path.begins_with("modification_stack")) { + r_ret = modification_stack; + return true; + } +#endif //_3D_DISABLED + if (!path.begins_with("bones/")) { return false; } @@ -135,6 +152,14 @@ void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + "pose", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); } + +#ifndef _3D_DISABLED + p_list->push_back( + PropertyInfo(Variant::OBJECT, "modification_stack", + PROPERTY_HINT_RESOURCE_TYPE, + "SkeletonModificationStack3D", + PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); +#endif //_3D_DISABLED } void Skeleton3D::_update_process_order() { @@ -145,47 +170,32 @@ void Skeleton3D::_update_process_order() { Bone *bonesptr = bones.ptrw(); int len = bones.size(); - process_order.resize(len); - int *order = process_order.ptrw(); + parentless_bones.clear(); + + for (int i = 0; i < len; i++) { + bonesptr[i].child_bones.clear(); + } + for (int i = 0; i < len; i++) { if (bonesptr[i].parent >= len) { //validate this just in case ERR_PRINT("Bone " + itos(i) + " has invalid parent: " + itos(bonesptr[i].parent)); bonesptr[i].parent = -1; } - order[i] = i; - bonesptr[i].sort_index = i; - } - //now check process order - int pass_count = 0; - while (pass_count < len * len) { - //using bubblesort because of simplicity, it won't run every frame though. - //bublesort worst case is O(n^2), and this may be an infinite loop if cyclic - bool swapped = false; - for (int i = 0; i < len; i++) { - int parent_idx = bonesptr[order[i]].parent; - if (parent_idx < 0) { - continue; //do nothing because it has no parent - } - //swap indices - int parent_order = bonesptr[parent_idx].sort_index; - if (parent_order > i) { - bonesptr[order[i]].sort_index = parent_order; - bonesptr[parent_idx].sort_index = i; - //swap order - SWAP(order[i], order[parent_order]); - swapped = true; - } - } - if (!swapped) { - break; - } - pass_count++; - } + if (bonesptr[i].parent != -1) { + int parent_bone_idx = bonesptr[i].parent; - if (pass_count == len * len) { - ERR_PRINT("Skeleton3D parenthood graph is cyclic"); + // Check to see if this node is already added to the parent: + if (bonesptr[parent_bone_idx].child_bones.find(i) < 0) { + // Add the child node + bonesptr[parent_bone_idx].child_bones.push_back(i); + } else { + ERR_PRINT("Skeleton3D parenthood graph is cyclic"); + } + } else { + parentless_bones.push_back(i); + } } process_order_dirty = false; @@ -196,78 +206,12 @@ void Skeleton3D::_notification(int p_what) { case NOTIFICATION_UPDATE_SKELETON: { RenderingServer *rs = RenderingServer::get_singleton(); Bone *bonesptr = bones.ptrw(); - int len = bones.size(); - _update_process_order(); - - const int *order = process_order.ptr(); - - for (int i = 0; i < len; i++) { - Bone &b = bonesptr[order[i]]; - - if (b.disable_rest) { - if (b.enabled) { - Transform3D pose = b.pose; - if (b.custom_pose_enable) { - pose = b.custom_pose * pose; - } - if (b.parent >= 0) { - b.pose_global = bonesptr[b.parent].pose_global * pose; - b.pose_global_no_override = bonesptr[b.parent].pose_global * pose; - } else { - b.pose_global = pose; - b.pose_global_no_override = pose; - } - } else { - if (b.parent >= 0) { - b.pose_global = bonesptr[b.parent].pose_global; - b.pose_global_no_override = bonesptr[b.parent].pose_global; - } else { - b.pose_global = Transform3D(); - b.pose_global_no_override = Transform3D(); - } - } - - } else { - if (b.enabled) { - Transform3D pose = b.pose; - if (b.custom_pose_enable) { - pose = b.custom_pose * pose; - } - if (b.parent >= 0) { - b.pose_global = bonesptr[b.parent].pose_global * (b.rest * pose); - b.pose_global_no_override = bonesptr[b.parent].pose_global * (b.rest * pose); - } else { - b.pose_global = b.rest * pose; - b.pose_global_no_override = b.rest * pose; - } - } else { - if (b.parent >= 0) { - b.pose_global = bonesptr[b.parent].pose_global * b.rest; - b.pose_global_no_override = bonesptr[b.parent].pose_global * b.rest; - } else { - b.pose_global = b.rest; - b.pose_global_no_override = b.rest; - } - } - } - - if (b.global_pose_override_amount >= CMP_EPSILON) { - b.pose_global = b.pose_global.interpolate_with(b.global_pose_override, b.global_pose_override_amount); - } - - if (b.global_pose_override_reset) { - b.global_pose_override_amount = 0.0; - } + int len = bones.size(); + dirty = false; - for (const ObjectID &E : b.nodes_bound) { - Object *obj = ObjectDB::get_instance(E); - ERR_CONTINUE(!obj); - Node3D *node_3d = Object::cast_to<Node3D>(obj); - ERR_CONTINUE(!node_3d); - node_3d->set_transform(b.pose_global); - } - } + // Update bone transforms + force_update_all_bone_transforms(); //update skins for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) { @@ -325,32 +269,53 @@ void Skeleton3D::_notification(int p_what) { } } - dirty = false; - #ifdef TOOLS_ENABLED emit_signal(SceneStringNames::get_singleton()->pose_updated); #endif // TOOLS_ENABLED } break; +#ifndef _3D_DISABLED case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { // This is active only if the skeleton animates the physical bones // and the state of the bone is not active. - if (animate_physical_bones) { - for (int i = 0; i < bones.size(); i += 1) { - if (bones[i].physical_bone) { - if (bones[i].physical_bone->is_simulating_physics() == false) { - bones[i].physical_bone->reset_to_rest_position(); + if (Engine::get_singleton()->is_editor_hint()) { + if (animate_physical_bones) { + for (int i = 0; i < bones.size(); i += 1) { + if (bones[i].physical_bone) { + if (bones[i].physical_bone->is_simulating_physics() == false) { + bones[i].physical_bone->reset_to_rest_position(); + } } } } } + + if (modification_stack.is_valid()) { + execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack3D::EXECUTION_MODE::execution_mode_physics_process); + } + } break; +#endif // _3D_DISABLED + +#ifndef _3D_DISABLED + case NOTIFICATION_INTERNAL_PROCESS: { + if (modification_stack.is_valid()) { + execute_modifications(get_process_delta_time(), SkeletonModificationStack3D::EXECUTION_MODE::execution_mode_process); + } + } break; +#endif // _3D_DISABLED + +#ifndef _3D_DISABLED case NOTIFICATION_READY: { - if (Engine::get_singleton()->is_editor_hint()) { - set_physics_process_internal(true); + set_physics_process_internal(true); + set_process_internal(true); + + if (modification_stack.is_valid()) { + set_modification_stack(modification_stack); } } break; +#endif // _3D_DISABLED } } @@ -363,15 +328,23 @@ void Skeleton3D::clear_bones_global_pose_override() { } void Skeleton3D::set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].global_pose_override_amount = p_amount; bones.write[p_bone].global_pose_override = p_pose; bones.write[p_bone].global_pose_override_reset = !p_persistent; _make_dirty(); } +Transform3D Skeleton3D::get_bone_global_pose_override(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); + return bones[p_bone].global_pose_override; +} + Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform3D()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); if (dirty) { const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON); } @@ -379,13 +352,107 @@ Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const { } Transform3D Skeleton3D::get_bone_global_pose_no_override(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform3D()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); if (dirty) { const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON); } return bones[p_bone].pose_global_no_override; } +void Skeleton3D::clear_bones_local_pose_override() { + for (int i = 0; i < bones.size(); i += 1) { + bones.write[i].local_pose_override_amount = 0; + } + _make_dirty(); +} + +void Skeleton3D::set_bone_local_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + bones.write[p_bone].local_pose_override_amount = p_amount; + bones.write[p_bone].local_pose_override = p_pose; + bones.write[p_bone].local_pose_override_reset = !p_persistent; + _make_dirty(); +} + +Transform3D Skeleton3D::get_bone_local_pose_override(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); + return bones[p_bone].local_pose_override; +} + +void Skeleton3D::update_bone_rest_forward_vector(int p_bone, bool p_force_update) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + + if (bones[p_bone].rest_bone_forward_vector.length_squared() > 0 && p_force_update == false) { + update_bone_rest_forward_axis(p_bone, p_force_update); + } + + // If it is a child/leaf bone... + if (get_bone_parent(p_bone) > 0) { + bones.write[p_bone].rest_bone_forward_vector = bones[p_bone].rest.origin.normalized(); + } else { + // If it has children... + Vector<int> child_bones = get_bone_children(p_bone); + if (child_bones.size() > 0) { + Vector3 combined_child_dir = Vector3(0, 0, 0); + for (int i = 0; i < child_bones.size(); i++) { + combined_child_dir += bones[child_bones[i]].rest.origin.normalized(); + } + combined_child_dir = combined_child_dir / child_bones.size(); + bones.write[p_bone].rest_bone_forward_vector = combined_child_dir.normalized(); + } else { + WARN_PRINT_ONCE("Cannot calculate forward direction for bone " + itos(p_bone)); + WARN_PRINT_ONCE("Assuming direction of (0, 1, 0) for bone"); + bones.write[p_bone].rest_bone_forward_vector = Vector3(0, 1, 0); + } + } + update_bone_rest_forward_axis(p_bone, p_force_update); +} + +void Skeleton3D::update_bone_rest_forward_axis(int p_bone, bool p_force_update) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + if (bones[p_bone].rest_bone_forward_axis > -1 && p_force_update == false) { + return; + } + + Vector3 forward_axis_absolute = bones[p_bone].rest_bone_forward_vector.abs(); + if (forward_axis_absolute.x > forward_axis_absolute.y && forward_axis_absolute.x > forward_axis_absolute.z) { + if (bones[p_bone].rest_bone_forward_vector.x > 0) { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_X_FORWARD; + } else { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_NEGATIVE_X_FORWARD; + } + } else if (forward_axis_absolute.y > forward_axis_absolute.x && forward_axis_absolute.y > forward_axis_absolute.z) { + if (bones[p_bone].rest_bone_forward_vector.y > 0) { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_Y_FORWARD; + } else { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_NEGATIVE_Y_FORWARD; + } + } else { + if (bones[p_bone].rest_bone_forward_vector.z > 0) { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_Z_FORWARD; + } else { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_NEGATIVE_Z_FORWARD; + } + } +} + +Vector3 Skeleton3D::get_bone_axis_forward_vector(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Vector3(0, 0, 0)); + return bones[p_bone].rest_bone_forward_vector; +} + +int Skeleton3D::get_bone_axis_forward_enum(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, -1); + return bones[p_bone].rest_bone_forward_axis; +} + // skeleton creation api void Skeleton3D::add_bone(const String &p_name) { ERR_FAIL_COND(p_name == "" || p_name.find(":") != -1 || p_name.find("/") != -1); @@ -414,14 +481,15 @@ int Skeleton3D::find_bone(const String &p_name) const { } String Skeleton3D::get_bone_name(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), ""); - + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, ""); return bones[p_bone].name; } void Skeleton3D::set_bone_name(int p_bone, const String &p_name) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); - for (int i = 0; i < bones.size(); i++) { + for (int i = 0; i < bone_size; i++) { if (i != p_bone) { ERR_FAIL_COND(bones[i].name == p_name); } @@ -449,7 +517,8 @@ int Skeleton3D::get_bone_count() const { } void Skeleton3D::set_bone_parent(int p_bone, int p_parent) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); ERR_FAIL_COND(p_parent != -1 && (p_parent < 0)); bones.write[p_bone].parent = p_parent; @@ -458,7 +527,8 @@ void Skeleton3D::set_bone_parent(int p_bone, int p_parent) { } void Skeleton3D::unparent_bone_and_rest(int p_bone) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); _update_process_order(); @@ -475,76 +545,93 @@ void Skeleton3D::unparent_bone_and_rest(int p_bone) { } void Skeleton3D::set_bone_disable_rest(int p_bone, bool p_disable) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].disable_rest = p_disable; } bool Skeleton3D::is_bone_rest_disabled(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), false); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, false); return bones[p_bone].disable_rest; } int Skeleton3D::get_bone_parent(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), -1); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, -1); return bones[p_bone].parent; } -void Skeleton3D::set_bone_rest(int p_bone, const Transform3D &p_rest) { - ERR_FAIL_INDEX(p_bone, bones.size()); +Vector<int> Skeleton3D::get_bone_children(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Vector<int>()); + return bones[p_bone].child_bones; +} - bones.write[p_bone].rest = p_rest; +void Skeleton3D::set_bone_children(int p_bone, Vector<int> p_children) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + bones.write[p_bone].child_bones = p_children; + + process_order_dirty = true; _make_dirty(); } -Transform3D Skeleton3D::get_bone_rest(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform3D()); - return bones[p_bone].rest; +void Skeleton3D::add_bone_child(int p_bone, int p_child) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + bones.write[p_bone].child_bones.push_back(p_child); + + process_order_dirty = true; + _make_dirty(); } -void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) { - ERR_FAIL_INDEX(p_bone, bones.size()); +void Skeleton3D::remove_bone_child(int p_bone, int p_child) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].enabled = p_enabled; + int child_idx = bones[p_bone].child_bones.find(p_child); + if (child_idx >= 0) { + bones.write[p_bone].child_bones.remove(child_idx); + } else { + WARN_PRINT("Cannot remove child bone: Child bone not found."); + } + + process_order_dirty = true; _make_dirty(); } -bool Skeleton3D::is_bone_enabled(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), false); - return bones[p_bone].enabled; +Vector<int> Skeleton3D::get_parentless_bones() { + return parentless_bones; } -void Skeleton3D::bind_child_node_to_bone(int p_bone, Node *p_node) { - ERR_FAIL_NULL(p_node); - ERR_FAIL_INDEX(p_bone, bones.size()); - - ObjectID id = p_node->get_instance_id(); +void Skeleton3D::set_bone_rest(int p_bone, const Transform3D &p_rest) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); - for (const ObjectID &E : bones[p_bone].nodes_bound) { - if (E == id) { - return; // already here - } - } + bones.write[p_bone].rest = p_rest; + _make_dirty(); +} +Transform3D Skeleton3D::get_bone_rest(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); - bones.write[p_bone].nodes_bound.push_back(id); + return bones[p_bone].rest; } -void Skeleton3D::unbind_child_node_from_bone(int p_bone, Node *p_node) { - ERR_FAIL_NULL(p_node); - ERR_FAIL_INDEX(p_bone, bones.size()); +void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); - ObjectID id = p_node->get_instance_id(); - bones.write[p_bone].nodes_bound.erase(id); + bones.write[p_bone].enabled = p_enabled; + _make_dirty(); } -void Skeleton3D::get_bound_child_nodes_to_bone(int p_bone, List<Node *> *p_bound) const { - ERR_FAIL_INDEX(p_bone, bones.size()); - - for (const ObjectID &E : bones[p_bone].nodes_bound) { - Object *obj = ObjectDB::get_instance(E); - ERR_CONTINUE(!obj); - p_bound->push_back(Object::cast_to<Node>(obj)); - } +bool Skeleton3D::is_bone_enabled(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, false); + return bones[p_bone].enabled; } void Skeleton3D::clear_bones() { @@ -557,7 +644,8 @@ void Skeleton3D::clear_bones() { // posing api void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].pose = p_pose; if (is_inside_tree()) { @@ -565,12 +653,14 @@ void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) { } } Transform3D Skeleton3D::get_bone_pose(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform3D()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); return bones[p_bone].pose; } void Skeleton3D::set_bone_custom_pose(int p_bone, const Transform3D &p_custom_pose) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); //ERR_FAIL_COND( !is_inside_scene() ); bones.write[p_bone].custom_pose_enable = (p_custom_pose != Transform3D()); @@ -580,7 +670,8 @@ void Skeleton3D::set_bone_custom_pose(int p_bone, const Transform3D &p_custom_po } Transform3D Skeleton3D::get_bone_custom_pose(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform3D()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); return bones[p_bone].custom_pose; } @@ -593,24 +684,22 @@ void Skeleton3D::_make_dirty() { dirty = true; } -int Skeleton3D::get_process_order(int p_idx) { - ERR_FAIL_INDEX_V(p_idx, bones.size(), -1); +void Skeleton3D::localize_rests() { _update_process_order(); - return process_order[p_idx]; -} -Vector<int> Skeleton3D::get_bone_process_orders() { - _update_process_order(); - return process_order; -} + Vector<int> bones_to_process = get_parentless_bones(); + while (bones_to_process.size() > 0) { + int current_bone_idx = bones_to_process[0]; + bones_to_process.erase(current_bone_idx); -void Skeleton3D::localize_rests() { - _update_process_order(); + if (bones[current_bone_idx].parent >= 0) { + set_bone_rest(current_bone_idx, bones[bones[current_bone_idx].parent].rest.affine_inverse() * bones[current_bone_idx].rest); + } - for (int i = bones.size() - 1; i >= 0; i--) { - int idx = process_order[i]; - if (bones[idx].parent >= 0) { - set_bone_rest(idx, bones[bones[idx].parent].rest.affine_inverse() * bones[idx].rest); + // Add the bone's children to the list of bones to be processed + int child_bone_size = bones[current_bone_idx].child_bones.size(); + for (int i = 0; i < child_bone_size; i++) { + bones_to_process.push_back(bones[current_bone_idx].child_bones[i]); } } } @@ -637,7 +726,8 @@ bool Skeleton3D::get_animate_physical_bones() const { } void Skeleton3D::bind_physical_bone_to_bone(int p_bone, PhysicalBone3D *p_physical_bone) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); ERR_FAIL_COND(bones[p_bone].physical_bone); ERR_FAIL_COND(!p_physical_bone); bones.write[p_bone].physical_bone = p_physical_bone; @@ -646,20 +736,23 @@ void Skeleton3D::bind_physical_bone_to_bone(int p_bone, PhysicalBone3D *p_physic } void Skeleton3D::unbind_physical_bone_from_bone(int p_bone) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].physical_bone = nullptr; _rebuild_physical_bones_cache(); } PhysicalBone3D *Skeleton3D::get_physical_bone(int p_bone) { - ERR_FAIL_INDEX_V(p_bone, bones.size(), nullptr); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); return bones[p_bone].physical_bone; } PhysicalBone3D *Skeleton3D::get_physical_bone_parent(int p_bone) { - ERR_FAIL_INDEX_V(p_bone, bones.size(), nullptr); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); if (bones[p_bone].cache_parent_physical_bone) { return bones[p_bone].cache_parent_physical_bone; @@ -669,7 +762,8 @@ PhysicalBone3D *Skeleton3D::get_physical_bone_parent(int p_bone) { } PhysicalBone3D *Skeleton3D::_get_physical_bone_parent(int p_bone) { - ERR_FAIL_INDEX_V(p_bone, bones.size(), nullptr); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); const int parent_bone = bones[p_bone].parent; if (0 > parent_bone) { @@ -800,15 +894,20 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { // pose changed, rebuild cache of inverses const Bone *bonesptr = bones.ptr(); int len = bones.size(); - const int *order = process_order.ptr(); // calculate global rests and invert them - for (int i = 0; i < len; i++) { - const Bone &b = bonesptr[order[i]]; + Vector<int> bones_to_process = get_parentless_bones(); + while (bones_to_process.size() > 0) { + int current_bone_idx = bones_to_process[0]; + bones_to_process.erase(current_bone_idx); + const Bone &b = bonesptr[current_bone_idx]; + + // Note: the code below may not work by default. May need to track an integer for the bone pose index order + // in the while loop, instead of using current_bone_idx. if (b.parent >= 0) { - skin->set_bind_pose(order[i], skin->get_bind_pose(b.parent) * b.rest); + skin->set_bind_pose(current_bone_idx, skin->get_bind_pose(b.parent) * b.rest); } else { - skin->set_bind_pose(order[i], b.rest); + skin->set_bind_pose(current_bone_idx, b.rest); } } @@ -839,17 +938,202 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { return skin_ref; } +void Skeleton3D::force_update_all_bone_transforms() { + _update_process_order(); + + for (int i = 0; i < parentless_bones.size(); i++) { + force_update_bone_children_transforms(parentless_bones[i]); + } +} + +void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone_idx, bone_size); + + Bone *bonesptr = bones.ptrw(); + List<int> bones_to_process = List<int>(); + bones_to_process.push_back(p_bone_idx); + + while (bones_to_process.size() > 0) { + int current_bone_idx = bones_to_process[0]; + bones_to_process.erase(current_bone_idx); + + Bone &b = bonesptr[current_bone_idx]; + + if (b.disable_rest) { + if (b.enabled) { + Transform3D pose = b.pose; + if (b.custom_pose_enable) { + pose = b.custom_pose * pose; + } + if (b.parent >= 0) { + b.pose_global = bonesptr[b.parent].pose_global * pose; + b.pose_global_no_override = b.pose_global; + } else { + b.pose_global = pose; + b.pose_global_no_override = b.pose_global; + } + } else { + if (b.parent >= 0) { + b.pose_global = bonesptr[b.parent].pose_global; + b.pose_global_no_override = b.pose_global; + } else { + b.pose_global = Transform3D(); + b.pose_global_no_override = b.pose_global; + } + } + + } else { + if (b.enabled) { + Transform3D pose = b.pose; + if (b.custom_pose_enable) { + pose = b.custom_pose * pose; + } + if (b.parent >= 0) { + b.pose_global = bonesptr[b.parent].pose_global * (b.rest * pose); + b.pose_global_no_override = b.pose_global; + } else { + b.pose_global = b.rest * pose; + b.pose_global_no_override = b.pose_global; + } + } else { + if (b.parent >= 0) { + b.pose_global = bonesptr[b.parent].pose_global * b.rest; + b.pose_global_no_override = b.pose_global; + } else { + b.pose_global = b.rest; + b.pose_global_no_override = b.pose_global; + } + } + } + + if (b.local_pose_override_amount >= CMP_EPSILON) { + Transform3D override_local_pose; + if (b.parent >= 0) { + override_local_pose = bonesptr[b.parent].pose_global * (b.rest * b.local_pose_override); + } else { + override_local_pose = (b.rest * b.local_pose_override); + } + b.pose_global = b.pose_global.interpolate_with(override_local_pose, b.local_pose_override_amount); + } + + if (b.global_pose_override_amount >= CMP_EPSILON) { + b.pose_global = b.pose_global.interpolate_with(b.global_pose_override, b.global_pose_override_amount); + } + + if (b.local_pose_override_reset) { + b.local_pose_override_amount = 0.0; + } + if (b.global_pose_override_reset) { + b.global_pose_override_amount = 0.0; + } + + // Add the bone's children to the list of bones to be processed + int child_bone_size = b.child_bones.size(); + for (int i = 0; i < child_bone_size; i++) { + bones_to_process.push_back(b.child_bones[i]); + } + + emit_signal(SceneStringNames::get_singleton()->bone_pose_changed, current_bone_idx); + } +} + // helper functions -Transform3D Skeleton3D::bone_transform_to_world_transform(Transform3D p_bone_transform) { - return get_global_transform() * p_bone_transform; + +Transform3D Skeleton3D::global_pose_to_world_transform(Transform3D p_global_pose) { + return get_global_transform() * p_global_pose; } -Transform3D Skeleton3D::world_transform_to_bone_transform(Transform3D p_world_transform) { +Transform3D Skeleton3D::world_transform_to_global_pose(Transform3D p_world_transform) { return get_global_transform().affine_inverse() * p_world_transform; } +Transform3D Skeleton3D::global_pose_to_local_pose(int p_bone_idx, Transform3D p_global_pose) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Transform3D()); + if (bones[p_bone_idx].parent >= 0) { + int parent_bone_idx = bones[p_bone_idx].parent; + Transform3D conversion_transform = (bones[parent_bone_idx].pose_global * bones[p_bone_idx].rest); + return conversion_transform.affine_inverse() * p_global_pose; + } else { + return p_global_pose; + } +} + +Transform3D Skeleton3D::local_pose_to_global_pose(int p_bone_idx, Transform3D p_local_pose) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Transform3D()); + if (bones[p_bone_idx].parent >= 0) { + int parent_bone_idx = bones[p_bone_idx].parent; + Transform3D conversion_transform = (bones[parent_bone_idx].pose_global * bones[p_bone_idx].rest); + return conversion_transform * p_local_pose; + } else { + return p_local_pose; + } +} + +Basis Skeleton3D::global_pose_z_forward_to_bone_forward(int p_bone_idx, Basis p_basis) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Basis()); + Basis return_basis = p_basis; + + if (bones[p_bone_idx].rest_bone_forward_axis < 0) { + update_bone_rest_forward_vector(p_bone_idx, true); + } + + if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_X_FORWARD) { + return_basis.rotate_local(Vector3(0, 1, 0), (Math_PI / 2.0)); + } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_NEGATIVE_X_FORWARD) { + return_basis.rotate_local(Vector3(0, 1, 0), -(Math_PI / 2.0)); + } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_Y_FORWARD) { + return_basis.rotate_local(Vector3(1, 0, 0), -(Math_PI / 2.0)); + } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_NEGATIVE_Y_FORWARD) { + return_basis.rotate_local(Vector3(1, 0, 0), (Math_PI / 2.0)); + } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_Z_FORWARD) { + // Do nothing! + } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_NEGATIVE_Z_FORWARD) { + return_basis.rotate_local(Vector3(0, 0, 1), Math_PI); + } + + return return_basis; +} + +// Modifications + +#ifndef _3D_DISABLED + +void Skeleton3D::set_modification_stack(Ref<SkeletonModificationStack3D> p_stack) { + if (modification_stack.is_valid()) { + modification_stack->is_setup = false; + modification_stack->set_skeleton(nullptr); + } + + modification_stack = p_stack; + if (modification_stack.is_valid()) { + modification_stack->set_skeleton(this); + modification_stack->setup(); + } +} +Ref<SkeletonModificationStack3D> Skeleton3D::get_modification_stack() { + return modification_stack; +} + +void Skeleton3D::execute_modifications(real_t p_delta, int p_execution_mode) { + if (!modification_stack.is_valid()) { + return; + } + + // Needed to avoid the issue where the stack looses reference to the skeleton when the scene is saved. + if (modification_stack->skeleton != this) { + modification_stack->set_skeleton(this); + } + + modification_stack->execute(p_delta, p_execution_mode); +} + +#endif // _3D_DISABLED + void Skeleton3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_bone_process_orders"), &Skeleton3D::get_bone_process_orders); ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton3D::add_bone); ClassDB::bind_method(D_METHOD("find_bone", "name"), &Skeleton3D::find_bone); ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &Skeleton3D::get_bone_name); @@ -862,6 +1146,13 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("unparent_bone_and_rest", "bone_idx"), &Skeleton3D::unparent_bone_and_rest); + ClassDB::bind_method(D_METHOD("get_bone_children", "bone_idx"), &Skeleton3D::get_bone_children); + ClassDB::bind_method(D_METHOD("set_bone_children", "bone_idx", "bone_children"), &Skeleton3D::set_bone_children); + ClassDB::bind_method(D_METHOD("add_bone_child", "bone_idx", "child_bone_idx"), &Skeleton3D::add_bone_child); + ClassDB::bind_method(D_METHOD("remove_bone_child", "bone_idx", "child_bone_idx"), &Skeleton3D::remove_bone_child); + + ClassDB::bind_method(D_METHOD("get_parentless_bones"), &Skeleton3D::get_parentless_bones); + ClassDB::bind_method(D_METHOD("get_bone_rest", "bone_idx"), &Skeleton3D::get_bone_rest); ClassDB::bind_method(D_METHOD("set_bone_rest", "bone_idx", "rest"), &Skeleton3D::set_bone_rest); @@ -879,14 +1170,26 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_bones_global_pose_override"), &Skeleton3D::clear_bones_global_pose_override); ClassDB::bind_method(D_METHOD("set_bone_global_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_global_pose_override, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_bone_global_pose_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_override); ClassDB::bind_method(D_METHOD("get_bone_global_pose", "bone_idx"), &Skeleton3D::get_bone_global_pose); ClassDB::bind_method(D_METHOD("get_bone_global_pose_no_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_no_override); + ClassDB::bind_method(D_METHOD("clear_bones_local_pose_override"), &Skeleton3D::clear_bones_local_pose_override); + ClassDB::bind_method(D_METHOD("set_bone_local_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_local_pose_override, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_bone_local_pose_override", "bone_idx"), &Skeleton3D::get_bone_local_pose_override); + ClassDB::bind_method(D_METHOD("get_bone_custom_pose", "bone_idx"), &Skeleton3D::get_bone_custom_pose); ClassDB::bind_method(D_METHOD("set_bone_custom_pose", "bone_idx", "custom_pose"), &Skeleton3D::set_bone_custom_pose); - ClassDB::bind_method(D_METHOD("bone_transform_to_world_transform", "bone_transform"), &Skeleton3D::bone_transform_to_world_transform); - ClassDB::bind_method(D_METHOD("world_transform_to_bone_transform", "world_transform"), &Skeleton3D::world_transform_to_bone_transform); + ClassDB::bind_method(D_METHOD("force_update_all_bone_transforms"), &Skeleton3D::force_update_all_bone_transforms); + ClassDB::bind_method(D_METHOD("force_update_bone_child_transform", "bone_idx"), &Skeleton3D::force_update_bone_children_transforms); + + // Helper functions + ClassDB::bind_method(D_METHOD("global_pose_to_world_transform", "global_pose"), &Skeleton3D::global_pose_to_world_transform); + ClassDB::bind_method(D_METHOD("world_transform_to_global_pose", "world_transform"), &Skeleton3D::world_transform_to_global_pose); + ClassDB::bind_method(D_METHOD("global_pose_to_local_pose", "bone_idx", "global_pose"), &Skeleton3D::global_pose_to_local_pose); + ClassDB::bind_method(D_METHOD("local_pose_to_global_pose", "bone_idx", "local_pose"), &Skeleton3D::local_pose_to_global_pose); + ClassDB::bind_method(D_METHOD("global_pose_z_forward_to_bone_forward", "bone_idx", "basis"), &Skeleton3D::global_pose_z_forward_to_bone_forward); ClassDB::bind_method(D_METHOD("set_animate_physical_bones"), &Skeleton3D::set_animate_physical_bones); ClassDB::bind_method(D_METHOD("get_animate_physical_bones"), &Skeleton3D::get_animate_physical_bones); @@ -896,12 +1199,21 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &Skeleton3D::physical_bones_add_collision_exception); ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &Skeleton3D::physical_bones_remove_collision_exception); + // Modifications + ClassDB::bind_method(D_METHOD("set_modification_stack", "modification_stack"), &Skeleton3D::set_modification_stack); + ClassDB::bind_method(D_METHOD("get_modification_stack"), &Skeleton3D::get_modification_stack); + ClassDB::bind_method(D_METHOD("execute_modifications", "delta", "execution_mode"), &Skeleton3D::execute_modifications); + +#ifndef _3D_DISABLED ADD_PROPERTY(PropertyInfo(Variant::BOOL, "animate_physical_bones"), "set_animate_physical_bones", "get_animate_physical_bones"); +#endif // _3D_DISABLED #ifdef TOOLS_ENABLED ADD_SIGNAL(MethodInfo("pose_updated")); #endif // TOOLS_ENABLED + ADD_SIGNAL(MethodInfo("bone_pose_changed", PropertyInfo(Variant::INT, "bone_idx"))); + BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON); } diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index bccad97d5f..c8a19db813 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -32,6 +32,7 @@ #define SKELETON_3D_H #include "scene/3d/node_3d.h" +#include "scene/resources/skeleton_modification_3d.h" #include "scene/resources/skin.h" typedef int BoneId; @@ -61,6 +62,8 @@ public: ~SkinReference(); }; +class SkeletonModificationStack3D; + class Skeleton3D : public Node3D { GDCLASS(Skeleton3D, Node3D); @@ -70,9 +73,8 @@ private: struct Bone { String name; - bool enabled = true; - int parent = -1; - int sort_index = 0; //used for re-sorting process order + bool enabled; + int parent; bool disable_rest = false; Transform3D rest; @@ -91,7 +93,35 @@ private: PhysicalBone3D *physical_bone = nullptr; PhysicalBone3D *cache_parent_physical_bone = nullptr; - List<ObjectID> nodes_bound; + real_t local_pose_override_amount; + bool local_pose_override_reset; + Transform3D local_pose_override; + + Vector<int> child_bones; + + // The forward direction vector and rest bone forward axis are cached because they do not change + // 99% of the time, but recalculating them can be expensive on models with many bones. + Vector3 rest_bone_forward_vector; + int rest_bone_forward_axis = -1; + + Bone() { + parent = -1; + enabled = true; + disable_rest = false; + custom_pose_enable = false; + global_pose_override_amount = 0; + global_pose_override_reset = false; +#ifndef _3D_DISABLED + physical_bone = nullptr; + cache_parent_physical_bone = nullptr; +#endif // _3D_DISABLED + local_pose_override_amount = 0; + local_pose_override_reset = false; + child_bones = Vector<int>(); + + rest_bone_forward_vector = Vector3(0, 0, 0); + rest_bone_forward_axis = -1; + } }; Set<SkinReference *> skin_bindings; @@ -100,8 +130,9 @@ private: bool animate_physical_bones = true; Vector<Bone> bones; - Vector<int> process_order; - bool process_order_dirty = true; + bool process_order_dirty; + + Vector<int> parentless_bones; void _make_dirty(); bool dirty = false; @@ -117,7 +148,20 @@ protected: void _notification(int p_what); static void _bind_methods(); +#ifndef _3D_DISABLED + Ref<SkeletonModificationStack3D> modification_stack; +#endif // _3D_DISABLED + public: + enum Bone_Forward_Axis { + BONE_AXIS_X_FORWARD = 0, + BONE_AXIS_Y_FORWARD = 1, + BONE_AXIS_Z_FORWARD = 2, + BONE_AXIS_NEGATIVE_X_FORWARD = 3, + BONE_AXIS_NEGATIVE_Y_FORWARD = 4, + BONE_AXIS_NEGATIVE_Z_FORWARD = 5, + }; + enum { NOTIFICATION_UPDATE_SKELETON = 50 }; @@ -135,6 +179,12 @@ public: void unparent_bone_and_rest(int p_bone); + Vector<int> get_bone_children(int p_bone); + void set_bone_children(int p_bone, Vector<int> p_children); + void add_bone_child(int p_bone, int p_child); + void remove_bone_child(int p_bone, int p_child); + Vector<int> get_parentless_bones(); + void set_bone_disable_rest(int p_bone, bool p_disable); bool is_bone_rest_disabled(int p_bone) const; @@ -145,16 +195,8 @@ public: Transform3D get_bone_global_pose(int p_bone) const; Transform3D get_bone_global_pose_no_override(int p_bone) const; - void clear_bones_global_pose_override(); - void set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent = false); - void set_bone_enabled(int p_bone, bool p_enabled); bool is_bone_enabled(int p_bone) const; - - void bind_child_node_to_bone(int p_bone, Node *p_node); - void unbind_child_node_from_bone(int p_bone, Node *p_node); - void get_bound_child_nodes_to_bone(int p_bone, List<Node *> *p_bound) const; - void clear_bones(); // posing api @@ -165,15 +207,40 @@ public: void set_bone_custom_pose(int p_bone, const Transform3D &p_custom_pose); Transform3D get_bone_custom_pose(int p_bone) const; + void clear_bones_global_pose_override(); + Transform3D get_bone_global_pose_override(int p_bone) const; + void set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent = false); + + void clear_bones_local_pose_override(); + Transform3D get_bone_local_pose_override(int p_bone) const; + void set_bone_local_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent = false); + void localize_rests(); // used for loaders and tools - int get_process_order(int p_idx); - Vector<int> get_bone_process_orders(); Ref<SkinReference> register_skin(const Ref<Skin> &p_skin); + void force_update_all_bone_transforms(); + void force_update_bone_children_transforms(int bone_idx); + + void update_bone_rest_forward_vector(int p_bone, bool p_force_update = false); + void update_bone_rest_forward_axis(int p_bone, bool p_force_update = false); + Vector3 get_bone_axis_forward_vector(int p_bone); + int get_bone_axis_forward_enum(int p_bone); + // Helper functions - Transform3D bone_transform_to_world_transform(Transform3D p_transform); - Transform3D world_transform_to_bone_transform(Transform3D p_transform); + Transform3D global_pose_to_world_transform(Transform3D p_global_pose); + Transform3D world_transform_to_global_pose(Transform3D p_transform); + Transform3D global_pose_to_local_pose(int p_bone_idx, Transform3D p_global_pose); + Transform3D local_pose_to_global_pose(int p_bone_idx, Transform3D p_local_pose); + + Basis global_pose_z_forward_to_bone_forward(int p_bone_idx, Basis p_basis); + + // Modifications +#ifndef _3D_DISABLED + Ref<SkeletonModificationStack3D> get_modification_stack(); + void set_modification_stack(Ref<SkeletonModificationStack3D> p_stack); + void execute_modifications(real_t p_delta, int p_execution_mode); +#endif // _3D_DISABLED // Physical bone API diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 543545b90f..88fb960164 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -36,8 +36,9 @@ #include "servers/audio/audio_stream.h" void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { - if (get_script_instance()) { - Array parameters = get_script_instance()->call("_get_parameter_list"); + Array parameters; + + if (GDVIRTUAL_CALL(_get_parameter_list, parameters)) { for (int i = 0; i < parameters.size(); i++) { Dictionary d = parameters[i]; ERR_CONTINUE(d.is_empty()); @@ -47,8 +48,9 @@ void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { } Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const { - if (get_script_instance()) { - return get_script_instance()->call("_get_parameter_default_value", p_parameter); + Variant ret; + if (GDVIRTUAL_CALL(_get_parameter_default_value, p_parameter, ret)) { + return ret; } return Variant(); } @@ -72,8 +74,8 @@ Variant AnimationNode::get_parameter(const StringName &p_name) const { } void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) { - if (get_script_instance()) { - Dictionary cn = get_script_instance()->call("_get_child_nodes"); + Dictionary cn; + if (GDVIRTUAL_CALL(_get_child_nodes, cn)) { List<Variant> keys; cn.get_key_list(&keys); for (const Variant &E : keys) { @@ -298,8 +300,9 @@ String AnimationNode::get_input_name(int p_input) { } String AnimationNode::get_caption() const { - if (get_script_instance()) { - return get_script_instance()->call("_get_caption"); + String ret; + if (GDVIRTUAL_CALL(_get_caption, ret)) { + return ret; } return "Node"; @@ -329,8 +332,9 @@ void AnimationNode::remove_input(int p_index) { } double AnimationNode::process(double p_time, bool p_seek) { - if (get_script_instance()) { - return get_script_instance()->call("_process", p_time, p_seek); + double ret; + if (GDVIRTUAL_CALL(_process, p_time, p_seek, ret)) { + return ret; } return 0; @@ -357,8 +361,9 @@ bool AnimationNode::is_path_filtered(const NodePath &p_path) const { } bool AnimationNode::has_filter() const { - if (get_script_instance()) { - return get_script_instance()->call("_has_filter"); + bool ret; + if (GDVIRTUAL_CALL(_has_filter, ret)) { + return ret; } return false; @@ -390,8 +395,9 @@ void AnimationNode::_validate_property(PropertyInfo &property) const { } Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) { - if (get_script_instance()) { - return get_script_instance()->call("_get_child_by_name", p_name); + Ref<AnimationNode> ret; + if (GDVIRTUAL_CALL(_get_child_by_name, p_name, ret)) { + return ret; } return Ref<AnimationNode>(); } @@ -422,17 +428,13 @@ void AnimationNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_filter_enabled", "is_filter_enabled"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters"); - BIND_VMETHOD(MethodInfo(Variant::DICTIONARY, "_get_child_nodes")); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_parameter_list")); - BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_get_child_by_name", PropertyInfo(Variant::STRING, "name"))); - { - MethodInfo mi = MethodInfo(Variant::NIL, "_get_parameter_default_value", PropertyInfo(Variant::STRING_NAME, "name")); - mi.return_val.usage = PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(mi); - } - BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::FLOAT, "time"), PropertyInfo(Variant::BOOL, "seek"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_caption")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_filter")); + GDVIRTUAL_BIND(_get_child_nodes); + GDVIRTUAL_BIND(_get_parameter_list); + GDVIRTUAL_BIND(_get_child_by_name, "name"); + GDVIRTUAL_BIND(_get_parameter_default_value, "parameter"); + GDVIRTUAL_BIND(_process, "time", "seek"); + GDVIRTUAL_BIND(_get_caption); + GDVIRTUAL_BIND(_has_filter); ADD_SIGNAL(MethodInfo("removed_from_graph")); diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 59bbc5b4da..1e0267682e 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -112,6 +112,14 @@ protected: void _set_parent(Object *p_parent); + GDVIRTUAL0RC(Dictionary, _get_child_nodes) + GDVIRTUAL0RC(Array, _get_parameter_list) + GDVIRTUAL1RC(Ref<AnimationNode>, _get_child_by_name, StringName) + GDVIRTUAL1RC(Variant, _get_parameter_default_value, StringName) + GDVIRTUAL2RC(double, _process, double, bool) + GDVIRTUAL0RC(String, _get_caption) + GDVIRTUAL0RC(bool, _has_filter) + public: virtual void get_parameter_list(List<PropertyInfo> *r_list) const; virtual Variant get_parameter_default_value(const StringName &p_parameter) const; diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index df1aece80a..f4e477b613 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -88,7 +88,7 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra } else if (p_msg == "override_camera_2D:transform") { ERR_FAIL_COND_V(p_args.size() < 1, ERR_INVALID_DATA); - Transform2D transform = p_args[1]; + Transform2D transform = p_args[0]; scene_tree->get_root()->set_canvas_transform_override(transform); #ifndef _3D_DISABLED } else if (p_msg == "override_camera_3D:set") { diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index d252a4507c..03c75b25f4 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -121,17 +121,13 @@ void BaseButton::_notification(int p_what) { } void BaseButton::_pressed() { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_pressed); - } + GDVIRTUAL_CALL(_pressed); pressed(); emit_signal(SNAME("pressed")); } void BaseButton::_toggled(bool p_pressed) { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, p_pressed); - } + GDVIRTUAL_CALL(_toggled, p_pressed); toggled(p_pressed); emit_signal(SNAME("toggled"), p_pressed); } @@ -440,8 +436,8 @@ void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &BaseButton::set_shortcut_context); ClassDB::bind_method(D_METHOD("get_shortcut_context"), &BaseButton::get_shortcut_context); - BIND_VMETHOD(MethodInfo("_pressed")); - BIND_VMETHOD(MethodInfo("_toggled", PropertyInfo(Variant::BOOL, "button_pressed"))); + GDVIRTUAL_BIND(_pressed); + GDVIRTUAL_BIND(_toggled, "button_pressed"); ADD_SIGNAL(MethodInfo("pressed")); ADD_SIGNAL(MethodInfo("button_up")); diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index d86b35daf0..cf1904344b 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -81,6 +81,9 @@ protected: bool _is_focus_owner_in_shorcut_context() const; + GDVIRTUAL0(_pressed) + GDVIRTUAL1(_toggled, bool) + public: enum DrawMode { DRAW_NORMAL, diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 08dd7f28eb..7cc2352353 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -450,7 +450,7 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } if (k->is_action("ui_text_backspace", true)) { backspace(); - _filter_code_completion_candidates(); + _filter_code_completion_candidates_impl(); accept_event(); return; } @@ -522,7 +522,7 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { TextEdit::_gui_input(p_gui_input); if (update_code_completion) { - _filter_code_completion_candidates(); + _filter_code_completion_candidates_impl(); } } @@ -557,7 +557,7 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { /* Text manipulation */ // Overridable actions -void CodeEdit::_handle_unicode_input(const uint32_t p_unicode) { +void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { bool had_selection = has_selection(); if (had_selection) { begin_complex_operation(); @@ -609,7 +609,7 @@ void CodeEdit::_handle_unicode_input(const uint32_t p_unicode) { } } -void CodeEdit::_backspace() { +void CodeEdit::_backspace_internal() { if (!is_editable()) { return; } @@ -1739,9 +1739,7 @@ String CodeEdit::get_text_for_code_completion() const { } void CodeEdit::request_code_completion(bool p_force) { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_request_code_completion")) { - si->call("_request_code_completion", p_force); + if (GDVIRTUAL_CALL(_request_code_completion, p_force)) { return; } @@ -1798,7 +1796,7 @@ void CodeEdit::update_code_completion_options(bool p_forced) { code_completion_forced = p_forced; code_completion_option_sources = code_completion_option_submitted; code_completion_option_submitted.clear(); - _filter_code_completion_candidates(); + _filter_code_completion_candidates_impl(); } TypedArray<Dictionary> CodeEdit::get_code_completion_options() const { @@ -1855,11 +1853,10 @@ void CodeEdit::confirm_code_completion(bool p_replace) { return; } - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_confirm_code_completion")) { - si->call("_confirm_code_completion", p_replace); + if (GDVIRTUAL_CALL(_confirm_code_completion, p_replace)) { return; } + begin_complex_operation(); int caret_line = get_caret_line(); @@ -2179,9 +2176,10 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_code_comletion_prefixes"), &CodeEdit::get_code_completion_prefixes); // Overridable - BIND_VMETHOD(MethodInfo("_confirm_code_completion", PropertyInfo(Variant::BOOL, "replace"))); - BIND_VMETHOD(MethodInfo("_request_code_completion", PropertyInfo(Variant::BOOL, "force"))); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_filter_code_completion_candidates", PropertyInfo(Variant::ARRAY, "candidates"))); + + GDVIRTUAL_BIND(_confirm_code_completion, "replace") + GDVIRTUAL_BIND(_request_code_completion, "force") + GDVIRTUAL_BIND(_filter_code_completion_candidates, "candidates") /* Line length guidelines */ ClassDB::bind_method(D_METHOD("set_line_length_guidelines", "guideline_columns"), &CodeEdit::set_line_length_guidelines); @@ -2650,11 +2648,10 @@ TypedArray<String> CodeEdit::_get_delimiters(DelimiterType p_type) const { } /* Code Completion */ -void CodeEdit::_filter_code_completion_candidates() { - ScriptInstance *si = get_script_instance(); +void CodeEdit::_filter_code_completion_candidates_impl() { int line_height = get_line_height(); - if (si && si->has_method("_filter_code_completion_candidates")) { + if (GDVIRTUAL_IS_OVERRIDEN(_filter_code_completion_candidates)) { code_completion_options.clear(); code_completion_base = ""; @@ -2674,7 +2671,9 @@ void CodeEdit::_filter_code_completion_candidates() { i++; } - TypedArray<Dictionary> completion_options = si->call("_filter_code_completion_candidates", completion_options_sources); + Array completion_options; + + GDVIRTUAL_CALL(_filter_code_completion_candidates, completion_options_sources, completion_options); /* No options to complete, cancel. */ if (completion_options.size() == 0) { diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 76ac15f553..aa62cbdf3c 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -219,7 +219,7 @@ private: List<ScriptCodeCompletionOption> code_completion_option_sources; String code_completion_base; - void _filter_code_completion_candidates(); + void _filter_code_completion_candidates_impl(); /* Line length guidelines */ TypedArray<int> line_length_guideline_columns; @@ -256,8 +256,12 @@ protected: /* Text manipulation */ // Overridable actions - virtual void _handle_unicode_input(const uint32_t p_unicode) override; - virtual void _backspace() override; + virtual void _handle_unicode_input_internal(const uint32_t p_unicode) override; + virtual void _backspace_internal() override; + + GDVIRTUAL1(_confirm_code_completion, bool) + GDVIRTUAL1(_request_code_completion, bool) + GDVIRTUAL1RC(Array, _filter_code_completion_candidates, TypedArray<Dictionary>) public: /* General overrides */ diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 261480bcdd..661e0dc648 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -46,13 +46,13 @@ void ColorPicker::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { btn_pick->set_icon(get_theme_icon(SNAME("screen_picker"), SNAME("ColorPicker"))); - bt_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); - + btn_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); + _update_presets(); _update_controls(); } break; case NOTIFICATION_ENTER_TREE: { btn_pick->set_icon(get_theme_icon(SNAME("screen_picker"), SNAME("ColorPicker"))); - bt_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); + btn_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); _update_controls(); _update_color(); @@ -69,7 +69,6 @@ void ColorPicker::_notification(int p_what) { for (int i = 0; i < preset_cache.size(); i++) { presets.push_back(preset_cache[i]); } - preset->update(); } #endif } break; @@ -372,22 +371,23 @@ void ColorPicker::_update_color(bool p_update_sliders) { } void ColorPicker::_update_presets() { - return; - //presets should be shown using buttons or something else, this method is not a good idea - - presets_per_row = 10; - Size2 size = bt_add_preset->get_size(); - Size2 preset_size = Size2(MIN(size.width * presets.size(), presets_per_row * size.width), size.height * (Math::ceil((float)presets.size() / presets_per_row))); - preset->set_custom_minimum_size(preset_size); - preset_container->set_custom_minimum_size(preset_size); - preset->draw_rect(Rect2(Point2(), preset_size), Color(1, 1, 1, 0)); - - for (int i = 0; i < presets.size(); i++) { - int x = (i % presets_per_row) * size.width; - int y = (Math::floor((float)i / presets_per_row)) * size.height; - preset->draw_rect(Rect2(Point2(x, y), size), presets[i]); + int preset_size = _get_preset_size(); + // Only update the preset button size if it has changed. + if (preset_size != prev_preset_size) { + prev_preset_size = preset_size; + btn_add_preset->set_custom_minimum_size(Size2(preset_size, preset_size)); + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *cpb = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + cpb->set_custom_minimum_size(Size2(preset_size, preset_size)); + } + } + // Only load preset buttons when the only child is the add-preset button. + if (preset_container->get_child_count() == 1) { + for (int i = 0; i < preset_cache.size(); i++) { + _add_preset_button(preset_size, preset_cache[i]); + } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } - _notification(NOTIFICATION_VISIBILITY_CHANGED); } void ColorPicker::_text_type_toggled() { @@ -422,14 +422,37 @@ ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const { return picker_type; } +inline int ColorPicker::_get_preset_size() { + return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("hseparation")) * (preset_column_count - 1))) / preset_column_count; +} + +void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { + ColorPresetButton *btn_preset = memnew(ColorPresetButton(p_color)); + btn_preset->set_preset_color(p_color); + btn_preset->set_custom_minimum_size(Size2(p_size, p_size)); + btn_preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input), varray(p_color)); + btn_preset->set_tooltip(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); + preset_container->add_child(btn_preset); +} + void ColorPicker::add_preset(const Color &p_color) { if (presets.find(p_color)) { presets.move_to_back(presets.find(p_color)); + + // Find button to move to the end. + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + preset_container->move_child(current_btn, preset_container->get_child_count() - 1); + break; + } + } } else { presets.push_back(p_color); preset_cache.push_back(p_color); + + _add_preset_button(_get_preset_size(), p_color); } - preset->update(); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -443,7 +466,15 @@ void ColorPicker::erase_preset(const Color &p_color) { if (presets.find(p_color)) { presets.erase(presets.find(p_color)); preset_cache.erase(preset_cache.find(p_color)); - preset->update(); + + // Find preset button to remove. + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->queue_delete(); + break; + } + } #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -560,7 +591,7 @@ void ColorPicker::_sample_draw() { const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95)); if (display_old_color && old_color.a < 1.0) { - sample->draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPicker")), rect_old, true); + sample->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), rect_old, true); } sample->draw_rect(rect_old, old_color); @@ -574,7 +605,7 @@ void ColorPicker::_sample_draw() { } if (color.a < 1.0) { - sample->draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPicker")), rect_new, true); + sample->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), rect_new, true); } sample->draw_rect(rect_new, color); @@ -734,7 +765,7 @@ void ColorPicker::_slider_draw(int p_which) { #endif if (p_which == 3) { - scroll[p_which]->draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + scroll[p_which]->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -932,43 +963,19 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { } } -void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { +void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_color) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - int index = 0; if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { - for (int i = 0; i < presets.size(); i++) { - int x = (i % presets_per_row) * bt_add_preset->get_size().x; - int y = (Math::floor((float)i / presets_per_row)) * bt_add_preset->get_size().y; - if (bev->get_position().x > x && bev->get_position().x < x + preset->get_size().x && bev->get_position().y > y && bev->get_position().y < y + preset->get_size().y) { - index = i; - } - } - set_pick_color(presets[index]); + set_pick_color(p_color); _update_color(); - emit_signal(SNAME("color_changed"), color); + emit_signal(SNAME("color_changed"), p_color); } else if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_RIGHT && presets_enabled) { - index = bev->get_position().x / (preset->get_size().x / presets.size()); - Color clicked_preset = presets[index]; - erase_preset(clicked_preset); - emit_signal(SNAME("preset_removed"), clicked_preset); - bt_add_preset->show(); + erase_preset(p_color); + emit_signal(SNAME("preset_removed"), p_color); } } - - Ref<InputEventMouseMotion> mev = p_event; - - if (mev.is_valid()) { - int index = mev->get_position().x * presets.size(); - if (preset->get_size().x != 0) { - index /= preset->get_size().x; - } - if (index < 0 || index >= presets.size()) { - return; - } - preset->set_tooltip(vformat(RTR("Color: #%s\nLMB: Set color\nRMB: Remove preset"), presets[index].to_html(presets[index].a < 1))); - } } void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { @@ -1064,11 +1071,11 @@ void ColorPicker::_html_focus_exit() { void ColorPicker::set_presets_enabled(bool p_enabled) { presets_enabled = p_enabled; if (!p_enabled) { - bt_add_preset->set_disabled(true); - bt_add_preset->set_focus_mode(FOCUS_NONE); + btn_add_preset->set_disabled(true); + btn_add_preset->set_focus_mode(FOCUS_NONE); } else { - bt_add_preset->set_disabled(false); - bt_add_preset->set_focus_mode(FOCUS_ALL); + btn_add_preset->set_disabled(false); + btn_add_preset->set_focus_mode(FOCUS_ALL); } } @@ -1080,7 +1087,6 @@ void ColorPicker::set_presets_visible(bool p_visible) { presets_visible = p_visible; preset_separator->set_visible(p_visible); preset_container->set_visible(p_visible); - preset_container2->set_visible(p_visible); } bool ColorPicker::are_presets_visible() const { @@ -1266,17 +1272,13 @@ ColorPicker::ColorPicker() : add_child(preset_separator); preset_container->set_h_size_flags(SIZE_EXPAND_FILL); + preset_container->set_columns(preset_column_count); add_child(preset_container); - preset_container->add_child(preset); - preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input)); - preset->connect("draw", callable_mp(this, &ColorPicker::_update_presets)); - - preset_container2->set_h_size_flags(SIZE_EXPAND_FILL); - add_child(preset_container2); - preset_container2->add_child(bt_add_preset); - bt_add_preset->set_tooltip(RTR("Add current color as a preset.")); - bt_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); + btn_add_preset->set_icon_align(Button::ALIGN_CENTER); + btn_add_preset->set_tooltip(RTR("Add current color as a preset.")); + btn_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); + preset_container->add_child(btn_add_preset); } ///////////////// @@ -1303,6 +1305,7 @@ void ColorPickerButton::pressed() { _update_picker(); popup->set_as_minsize(); + picker->_update_presets(); Rect2i usable_rect = popup->get_usable_parent_rect(); //let's try different positions to see which one we can use @@ -1428,3 +1431,64 @@ void ColorPickerButton::_bind_methods() { ColorPickerButton::ColorPickerButton() { set_toggle_mode(true); } + +///////////////// + +void ColorPresetButton::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_DRAW: { + const Rect2 r = Rect2(Point2(0, 0), get_size()); + Ref<StyleBox> sb_raw = get_theme_stylebox(SNAME("preset_fg"), SNAME("ColorPresetButton"))->duplicate(); + Ref<StyleBoxFlat> sb_flat = sb_raw; + Ref<StyleBoxTexture> sb_texture = sb_raw; + + if (sb_flat.is_valid()) { + if (preset_color.a < 1) { + // Draw a background pattern when the color is transparent. + sb_flat->set_bg_color(Color(1, 1, 1)); + sb_flat->draw(get_canvas_item(), r); + + Rect2 bg_texture_rect = r.grow_side(SIDE_LEFT, -sb_flat->get_margin(SIDE_LEFT)); + bg_texture_rect = bg_texture_rect.grow_side(SIDE_RIGHT, -sb_flat->get_margin(SIDE_RIGHT)); + bg_texture_rect = bg_texture_rect.grow_side(SIDE_TOP, -sb_flat->get_margin(SIDE_TOP)); + bg_texture_rect = bg_texture_rect.grow_side(SIDE_BOTTOM, -sb_flat->get_margin(SIDE_BOTTOM)); + + draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPresetButton")), bg_texture_rect, true); + sb_flat->set_bg_color(preset_color); + } + sb_flat->set_bg_color(preset_color); + sb_flat->draw(get_canvas_item(), r); + } else if (sb_texture.is_valid()) { + if (preset_color.a < 1) { + // Draw a background pattern when the color is transparent. + bool use_tile_texture = (sb_texture->get_h_axis_stretch_mode() == StyleBoxTexture::AxisStretchMode::AXIS_STRETCH_MODE_TILE) || (sb_texture->get_h_axis_stretch_mode() == StyleBoxTexture::AxisStretchMode::AXIS_STRETCH_MODE_TILE_FIT); + draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPresetButton")), r, use_tile_texture); + } + sb_texture->set_modulate(preset_color); + sb_texture->draw(get_canvas_item(), r); + } else { + WARN_PRINT("Unsupported StyleBox used for ColorPresetButton. Use StyleBoxFlat or StyleBoxTexture instead."); + } + if (preset_color.r > 1 || preset_color.g > 1 || preset_color.b > 1) { + // Draw an indicator to denote that the color is "overbright" and can't be displayed accurately in the preview + draw_texture(Control::get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPresetButton")), Vector2(0, 0)); + } + + } break; + } +} + +void ColorPresetButton::set_preset_color(const Color &p_color) { + preset_color = p_color; +} + +Color ColorPresetButton::get_preset_color() const { + return preset_color; +} + +ColorPresetButton::ColorPresetButton(Color p_color) { + preset_color = p_color; +} + +ColorPresetButton::~ColorPresetButton() { +} diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 60da3957aa..67ca007eb5 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -35,6 +35,7 @@ #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/check_button.h" +#include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/popup.h" @@ -43,6 +44,22 @@ #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" +class ColorPresetButton : public BaseButton { + GDCLASS(ColorPresetButton, BaseButton); + + Color preset_color; + +protected: + void _notification(int); + +public: + void set_preset_color(const Color &p_color); + Color get_preset_color() const; + + ColorPresetButton(Color p_color); + ~ColorPresetButton(); +}; + class ColorPicker : public BoxContainer { GDCLASS(ColorPicker, BoxContainer); @@ -69,12 +86,9 @@ private: Control *wheel = memnew(Control); Control *wheel_uv = memnew(Control); TextureRect *sample = memnew(TextureRect); - TextureRect *preset = memnew(TextureRect); - HBoxContainer *preset_container = memnew(HBoxContainer); - HBoxContainer *preset_container2 = memnew(HBoxContainer); + GridContainer *preset_container = memnew(GridContainer); HSeparator *preset_separator = memnew(HSeparator); - Button *bt_add_preset = memnew(Button); - List<Color> presets; + Button *btn_add_preset = memnew(Button); Button *btn_pick = memnew(Button); CheckButton *btn_hsv = memnew(CheckButton); CheckButton *btn_raw = memnew(CheckButton); @@ -83,14 +97,19 @@ private: Label *labels[4]; Button *text_type = memnew(Button); LineEdit *c_text = memnew(LineEdit); + bool edit_alpha = true; Size2i ms; bool text_is_constructor = false; - int presets_per_row = 0; PickerShapeType picker_type = SHAPE_HSV_WHEEL; + const int preset_column_count = 9; + int prev_preset_size = 0; + List<Color> presets; + Color color; Color old_color; + bool display_old_color = false; bool raw_mode_enabled = false; bool hsv_mode_enabled = false; @@ -100,6 +119,7 @@ private: bool spinning = false; bool presets_enabled = true; bool presets_visible = true; + float h = 0.0; float s = 0.0; float v = 0.0; @@ -109,7 +129,6 @@ private: void _value_changed(double); void _update_controls(); void _update_color(bool p_update_sliders = true); - void _update_presets(); void _update_text_value(); void _text_type_toggled(); void _sample_input(const Ref<InputEvent> &p_event); @@ -119,7 +138,7 @@ private: void _uv_input(const Ref<InputEvent> &p_event, Control *c); void _w_input(const Ref<InputEvent> &p_event); - void _preset_input(const Ref<InputEvent> &p_event); + void _preset_input(const Ref<InputEvent> &p_event, const Color &p_color); void _screen_input(const Ref<InputEvent> &p_event); void _add_preset_pressed(); void _screen_pick_pressed(); @@ -127,6 +146,9 @@ private: void _focus_exit(); void _html_focus_exit(); + inline int _get_preset_size(); + void _add_preset_button(int p_size, const Color &p_color); + protected: void _notification(int); static void _bind_methods(); @@ -152,6 +174,7 @@ public: void add_preset(const Color &p_color); void erase_preset(const Color &p_color); PackedColorArray get_presets() const; + void _update_presets(); void set_hsv_mode(bool p_enabled); bool is_hsv_mode() const; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index f114e06c75..6dba23d3c7 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -730,14 +730,9 @@ Variant Control::get_drag_data(const Point2 &p_point) { } } - if (get_script_instance()) { - Variant v = p_point; - const Variant *p = &v; - Callable::CallError ce; - Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_get_drag_data, &p, 1, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return ret; - } + Variant dd; + if (GDVIRTUAL_CALL(_get_drag_data, p_point, dd)) { + return dd; } return Variant(); @@ -752,16 +747,10 @@ bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const } } - if (get_script_instance()) { - Variant v = p_point; - const Variant *p[2] = { &v, &p_data }; - Callable::CallError ce; - Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_can_drop_data, p, 2, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return ret; - } + bool ret; + if (GDVIRTUAL_CALL(_can_drop_data, p_point, p_data, ret)) { + return ret; } - return false; } @@ -775,15 +764,7 @@ void Control::drop_data(const Point2 &p_point, const Variant &p_data) { } } - if (get_script_instance()) { - Variant v = p_point; - const Variant *p[2] = { &v, &p_data }; - Callable::CallError ce; - Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_drop_data, p, 2, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return; - } - } + GDVIRTUAL_CALL(_drop_data, p_point, p_data); } void Control::force_drag(const Variant &p_data, Control *p_control) { @@ -800,15 +781,11 @@ void Control::set_drag_preview(Control *p_control) { } Size2 Control::get_minimum_size() const { - ScriptInstance *si = const_cast<Control *>(this)->get_script_instance(); - if (si) { - Callable::CallError ce; - Variant s = si->call(SceneStringNames::get_singleton()->_get_minimum_size, nullptr, 0, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return s; - } + Vector2 ms; + if (GDVIRTUAL_CALL(_get_minimum_size, ms)) { + return ms; } - return Size2(); + return Vector2(); } template <class T> @@ -2123,8 +2100,9 @@ String Control::get_tooltip(const Point2 &p_pos) const { } Control *Control::make_custom_tooltip(const String &p_text) const { - if (get_script_instance()) { - return const_cast<Control *>(this)->call("_make_custom_tooltip", p_text); + Object *ret = nullptr; + if (GDVIRTUAL_CALL(_make_custom_tooltip, p_text, ret)) { + return Object::cast_to<Control>(ret); } return nullptr; } @@ -2499,14 +2477,11 @@ Vector<Vector2i> Control::structured_text_parser(StructuredTextParser p_theme_ty } } break; case STRUCTURED_TEXT_CUSTOM: { - if (get_script_instance()) { - Variant data = get_script_instance()->call(SceneStringNames::get_singleton()->_structured_text_parser, p_args, p_text); - if (data.get_type() == Variant::ARRAY) { - Array _data = data; - for (int i = 0; i < _data.size(); i++) { - if (_data[i].get_type() == Variant::VECTOR2I) { - ret.push_back(Vector2i(_data[i])); - } + Array r; + if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, r)) { + for (int i = 0; i < r.size(); i++) { + if (r[i].get_type() == Variant::VECTOR2I) { + ret.push_back(Vector2i(r[i])); } } } @@ -2823,20 +2798,7 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_auto_translate", "enable"), &Control::set_auto_translate); ClassDB::bind_method(D_METHOD("is_auto_translating"), &Control::is_auto_translating); - BIND_VMETHOD(MethodInfo("_structured_text_parser", PropertyInfo(Variant::ARRAY, "args"), PropertyInfo(Variant::STRING, "text"))); - BIND_VMETHOD(MethodInfo("_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); - BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_get_minimum_size")); - - MethodInfo get_drag_data = MethodInfo("_get_drag_data", PropertyInfo(Variant::VECTOR2, "position")); - get_drag_data.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(get_drag_data); - - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); - BIND_VMETHOD(MethodInfo("_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); - BIND_VMETHOD(MethodInfo( - PropertyInfo(Variant::OBJECT, "control", PROPERTY_HINT_RESOURCE_TYPE, "Control"), - "_make_custom_tooltip", PropertyInfo(Variant::STRING, "for_text"))); ADD_GROUP("Anchor", "anchor_"); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_LEFT); @@ -2994,5 +2956,12 @@ void Control::_bind_methods() { ADD_SIGNAL(MethodInfo("minimum_size_changed")); ADD_SIGNAL(MethodInfo("theme_changed")); - GDVIRTUAL_BIND(_has_point); + GDVIRTUAL_BIND(_has_point, "position"); + GDVIRTUAL_BIND(_structured_text_parser, "args", "text"); + GDVIRTUAL_BIND(_get_minimum_size); + + GDVIRTUAL_BIND(_get_drag_data, "at_position") + GDVIRTUAL_BIND(_can_drop_data, "at_position", "data") + GDVIRTUAL_BIND(_drop_data, "at_position", "data") + GDVIRTUAL_BIND(_make_custom_tooltip, "for_text") } diff --git a/scene/gui/control.h b/scene/gui/control.h index a871a8e9fb..0d7a3b8de0 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -273,6 +273,16 @@ private: _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const; GDVIRTUAL1RC(bool, _has_point, Vector2) + GDVIRTUAL2RC(Array, _structured_text_parser, Array, String) + GDVIRTUAL0RC(Vector2, _get_minimum_size) + + GDVIRTUAL1RC(Variant, _get_drag_data, Vector2) + GDVIRTUAL2RC(bool, _can_drop_data, Vector2, Variant) + GDVIRTUAL2(_drop_data, Vector2, Variant) + GDVIRTUAL1RC(Object *, _make_custom_tooltip, String) + + //GDVIRTUAL1(_gui_input, Ref<InputEvent>) + protected: virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index fcecbb5fca..cdb73e3698 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -805,68 +805,35 @@ bool GraphEdit::is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos, c return true; } -template <class Vector2> -static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, Vector2 start, Vector2 control_1, Vector2 control_2, Vector2 end) { - /* Formula from Wikipedia article on Bezier curves. */ - real_t omt = (1.0 - t); - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - real_t t3 = t2 * t; - - return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; -} - -void GraphEdit::_bake_segment2d(Vector<Vector2> &points, Vector<Color> &colors, float p_begin, float p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_min_depth, int p_max_depth, float p_tol, const Color &p_color, const Color &p_to_color, int &lines) const { - float mp = p_begin + (p_end - p_begin) * 0.5; - Vector2 beg = _bezier_interp(p_begin, p_a, p_a + p_out, p_b + p_in, p_b); - Vector2 mid = _bezier_interp(mp, p_a, p_a + p_out, p_b + p_in, p_b); - Vector2 end = _bezier_interp(p_end, p_a, p_a + p_out, p_b + p_in, p_b); - - Vector2 na = (mid - beg).normalized(); - Vector2 nb = (end - mid).normalized(); - float dp = Math::rad2deg(Math::acos(na.dot(nb))); - - if (p_depth >= p_min_depth && (dp < p_tol || p_depth >= p_max_depth)) { - points.push_back((beg + end) * 0.5); - colors.push_back(p_color.lerp(p_to_color, mp)); - lines++; - } else { - _bake_segment2d(points, colors, p_begin, mp, p_a, p_out, p_b, p_in, p_depth + 1, p_min_depth, p_max_depth, p_tol, p_color, p_to_color, lines); - _bake_segment2d(points, colors, mp, p_end, p_a, p_out, p_b, p_in, p_depth + 1, p_min_depth, p_max_depth, p_tol, p_color, p_to_color, lines); - } -} - -void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio) { - //cubic bezier code - float diff = p_to.x - p_from.x; - float cp_offset; - int cp_len = get_theme_constant(SNAME("bezier_len_pos")) * p_bezier_ratio; - int cp_neg_len = get_theme_constant(SNAME("bezier_len_neg")) * p_bezier_ratio; - - if (diff > 0) { - cp_offset = MIN(cp_len, diff * 0.5); - } else { - cp_offset = MAX(MIN(cp_len - diff, cp_neg_len), -diff * 0.5); +PackedVector2Array GraphEdit::get_connection_line(const Vector2 &p_from, const Vector2 &p_to) { + if (get_script_instance() && get_script_instance()->get_script().is_valid() && get_script_instance()->has_method("_get_connection_line")) { + return get_script_instance()->call("_get_connection_line", p_from, p_to); } - Vector2 c1 = Vector2(cp_offset * zoom, 0); - Vector2 c2 = Vector2(-cp_offset * zoom, 0); - - int lines = 0; + Curve2D curve; + Vector<Color> colors; + curve.add_point(p_from); + curve.set_point_out(0, Vector2(60, 0)); + curve.add_point(p_to); + curve.set_point_in(1, Vector2(-60, 0)); + return curve.tessellate(); +} - Vector<Point2> points; +void GraphEdit::_draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom) { + Vector<Vector2> points = get_connection_line(p_from / p_zoom, p_to / p_zoom); + Vector<Vector2> scaled_points; Vector<Color> colors; - points.push_back(p_from); - colors.push_back(p_color); - _bake_segment2d(points, colors, 0, 1, p_from, c1, p_to, c2, 0, 3, 9, 3, p_color, p_to_color, lines); - points.push_back(p_to); - colors.push_back(p_to_color); + float length = (p_from / p_zoom).distance_to(p_to / p_zoom); + for (int i = 0; i < points.size(); i++) { + float d = (p_from / p_zoom).distance_to(points[i]) / length; + colors.push_back(p_color.lerp(p_to_color, d)); + scaled_points.push_back(points[i] * p_zoom); + } #ifdef TOOLS_ENABLED - p_where->draw_polyline_colors(points, colors, Math::floor(p_width * EDSCALE), lines_antialiased); + p_where->draw_polyline_colors(scaled_points, colors, Math::floor(p_width * EDSCALE), lines_antialiased); #else - p_where->draw_polyline_colors(points, colors, p_width, lines_antialiased); + p_where->draw_polyline_colors(scaled_points, colors, p_width, lines_antialiased); #endif } @@ -913,7 +880,7 @@ void GraphEdit::_connections_layer_draw() { color = color.lerp(activity_color, E->get().activity); tocolor = tocolor.lerp(activity_color, E->get().activity); } - _draw_cos_line(connections_layer, frompos, topos, color, tocolor, lines_thickness); + _draw_connection_line(connections_layer, frompos, topos, color, tocolor, lines_thickness, zoom); } while (to_erase.size()) { @@ -952,7 +919,7 @@ void GraphEdit::_top_layer_draw() { if (!connecting_out) { SWAP(pos, topos); } - _draw_cos_line(top_layer, pos, topos, col, col, lines_thickness); + _draw_connection_line(top_layer, pos, topos, col, col, lines_thickness, zoom); } if (box_selecting) { @@ -1056,7 +1023,7 @@ void GraphEdit::_minimap_draw() { from_color = from_color.lerp(activity_color, E.activity); to_color = to_color.lerp(activity_color, E.activity); } - _draw_cos_line(minimap, from_position, to_position, from_color, to_color, 1.0, 0.5); + _draw_connection_line(minimap, from_position, to_position, from_color, to_color, 0.1, minimap->_convert_from_graph_position(Vector2(zoom, zoom)).length()); } // Draw the "camera" viewport. @@ -2048,7 +2015,6 @@ void GraphEdit::arrange_nodes() { if (gn->is_selected()) { selected_nodes.insert(gn->get_name()); - origin = origin < gn->get_position_offset() ? origin : gn->get_position_offset(); Set<StringName> s; for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from]); @@ -2072,6 +2038,11 @@ void GraphEdit::arrange_nodes() { } } + if (!selected_nodes.size()) { + arranging_graph = false; + return; + } + HashMap<int, Vector<StringName>> layers = _layering(selected_nodes, upper_neighbours); _crossing_minimisation(layers, upper_neighbours); @@ -2098,16 +2069,16 @@ void GraphEdit::arrange_nodes() { for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) { _place_block(E->get(), gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions); } + origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]); + origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x; for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) { StringName u = E->get(); - StringName prev = u; float start_from = origin.y + new_positions[E->get()].y; do { Vector2 cal_pos; cal_pos.y = start_from + (real_t)inner_shift[u]; new_positions.set(u, cal_pos); - prev = u; u = align[u]; } while (u != E->get()); } @@ -2130,10 +2101,11 @@ void GraphEdit::arrange_nodes() { if (current_node_size == largest_node_size) { cal_pos.x = start_from; } else { - float current_node_start_pos; - if (current_node_size >= largest_node_size / 2) { - current_node_start_pos = start_from; - } else { + float current_node_start_pos = start_from; + if (current_node_size < largest_node_size / 2) { + if (!(i || j)) { + start_from -= (largest_node_size - current_node_size); + } current_node_start_pos = start_from + largest_node_size - current_node_size; } cal_pos.x = current_node_start_pos; @@ -2179,6 +2151,7 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("add_valid_connection_type", "from_type", "to_type"), &GraphEdit::add_valid_connection_type); ClassDB::bind_method(D_METHOD("remove_valid_connection_type", "from_type", "to_type"), &GraphEdit::remove_valid_connection_type); ClassDB::bind_method(D_METHOD("is_valid_connection_type", "from_type", "to_type"), &GraphEdit::is_valid_connection_type); + ClassDB::bind_method(D_METHOD("get_connection_line", "from", "to"), &GraphEdit::get_connection_line); ClassDB::bind_method(D_METHOD("set_zoom", "zoom"), &GraphEdit::set_zoom); ClassDB::bind_method(D_METHOD("get_zoom"), &GraphEdit::get_zoom); @@ -2227,6 +2200,8 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected); + BIND_VMETHOD(MethodInfo(Variant::PACKED_VECTOR2_ARRAY, "_get_connection_line", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to"))); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset"), "set_scroll_ofs", "get_scroll_ofs"); ADD_PROPERTY(PropertyInfo(Variant::INT, "snap_distance"), "set_snap", "get_snap"); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 9fd7cbef22..7a9286be0f 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -169,9 +169,8 @@ private: float lines_thickness = 2.0f; bool lines_antialiased = true; - void _bake_segment2d(Vector<Vector2> &points, Vector<Color> &colors, float p_begin, float p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_min_depth, int p_max_depth, float p_tol, const Color &p_color, const Color &p_to_color, int &lines) const; - - void _draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio = 1.0); + PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to); + void _draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom); void _graph_node_raised(Node *p_gn); void _graph_node_moved(Node *p_gn); diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 258d65112a..c0e6e069cd 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -48,6 +48,8 @@ void ItemList::_shape(int p_idx) { } else { item.text_buf->set_flags(TextServer::BREAK_NONE); } + item.text_buf->set_text_overrun_behavior(text_overrun_behavior); + item.text_buf->set_max_lines_visible(max_text_lines); } int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bool p_selectable) { @@ -453,6 +455,7 @@ void ItemList::set_max_text_lines(int p_lines) { for (int i = 0; i < items.size(); i++) { if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { items.write[i].text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + items.write[i].text_buf->set_max_lines_visible(p_lines); } else { items.write[i].text_buf->set_flags(TextServer::BREAK_NONE); } @@ -930,8 +933,14 @@ void ItemList::_notification(int p_what) { } if (items[i].text != "") { + int max_width = -1; + if (fixed_column_width) { + max_width = fixed_column_width; + } else if (same_column_width) { + max_width = items[i].rect_cache.size.x; + } + items.write[i].text_buf->set_width(max_width); Size2 s = items[i].text_buf->get_size(); - //s.width=MIN(s.width,fixed_column_width); if (icon_mode == ICON_MODE_TOP) { minsize.x = MAX(minsize.x, s.width); @@ -1139,11 +1148,8 @@ void ItemList::_notification(int p_what) { if (icon_mode == ICON_MODE_TOP) { pos.x += Math::floor((items[i].rect_cache.size.width - icon_size.width) / 2); - pos.y += MIN( - Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2), - items[i].rect_cache.size.height - items[i].min_rect_cache.size.height); - text_ofs.y = icon_size.height + icon_margin; - text_ofs.y += items[i].rect_cache.size.height - items[i].min_rect_cache.size.height; + pos.y += icon_margin; + text_ofs.y = icon_size.height + icon_margin * 2; } else { pos.y += Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2); text_ofs.x = icon_size.width + icon_margin; @@ -1210,7 +1216,6 @@ void ItemList::_notification(int p_what) { text_ofs.x = size.width - text_ofs.x - max_len; } - items.write[i].text_buf->set_width(max_len); items.write[i].text_buf->set_align(HALIGN_CENTER); if (outline_size > 0 && font_outline_color.a > 0) { @@ -1488,6 +1493,21 @@ bool ItemList::has_auto_height() const { return auto_height; } +void ItemList::set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior) { + if (text_overrun_behavior != p_behavior) { + text_overrun_behavior = p_behavior; + for (int i = 0; i < items.size(); i++) { + items.write[i].text_buf->set_text_overrun_behavior(p_behavior); + } + shape_changed = true; + update(); + } +} + +TextParagraph::OverrunBehavior ItemList::get_text_overrun_behavior() const { + return text_overrun_behavior; +} + void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("add_item", "text", "icon", "selectable"), &ItemList::add_item, DEFVAL(Variant()), DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_icon_item", "icon", "selectable"), &ItemList::add_icon_item, DEFVAL(true)); @@ -1599,6 +1619,9 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_items"), &ItemList::_set_items); ClassDB::bind_method(D_METHOD("_get_items"), &ItemList::_get_items); + ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &ItemList::set_text_overrun_behavior); + ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &ItemList::get_text_overrun_behavior); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items"); ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Multi"), "set_select_mode", "get_select_mode"); @@ -1606,6 +1629,7 @@ void ItemList::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_text_lines", PROPERTY_HINT_RANGE, "1,10,1,or_greater"), "set_max_text_lines", "get_max_text_lines"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_height"), "set_auto_height", "has_auto_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); ADD_GROUP("Columns", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_columns", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), "set_max_columns", "get_max_columns"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "same_column_width"), "set_same_column_width", "is_same_column_width"); diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index 86a0174a20..8a234398c3 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -95,6 +95,7 @@ private: SelectMode select_mode = SELECT_SINGLE; IconMode icon_mode = ICON_MODE_LEFT; VScrollBar *scroll_bar; + TextParagraph::OverrunBehavior text_overrun_behavior = TextParagraph::OVERRUN_NO_TRIMMING; uint64_t search_time_msec = 0; String search_string; @@ -182,6 +183,9 @@ public: void set_item_custom_fg_color(int p_idx, const Color &p_custom_fg_color); Color get_item_custom_fg_color(int p_idx) const; + void set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior); + TextParagraph::OverrunBehavior get_text_overrun_behavior() const; + void select(int p_idx, bool p_single = true); void deselect(int p_idx); void deselect_all(); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 68e9171c15..11e08b231e 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -577,8 +577,8 @@ void LineEdit::_notification(int p_what) { #ifdef TOOLS_ENABLED case NOTIFICATION_ENTER_TREE: { if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) { - set_caret_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false)); - set_caret_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65)); + set_caret_blink_enabled(EDITOR_DEF("text_editor/appearance/caret/caret_blink", false)); + set_caret_blink_speed(EDITOR_DEF("text_editor/appearance/caret/caret_blink_speed", 0.65)); if (!EditorSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed))) { EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed)); @@ -946,6 +946,17 @@ void LineEdit::paste_text() { } } +bool LineEdit::has_undo() const { + if (undo_stack_pos == nullptr) { + return undo_stack.size() > 1; + } + return undo_stack_pos != undo_stack.front(); +} + +bool LineEdit::has_redo() const { + return undo_stack_pos != nullptr && undo_stack_pos != undo_stack.back(); +} + void LineEdit::undo() { if (!editable) { return; @@ -1824,8 +1835,8 @@ PopupMenu *LineEdit::get_menu() const { void LineEdit::_editor_settings_changed() { #ifdef TOOLS_ENABLED - set_caret_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false)); - set_caret_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65)); + set_caret_blink_enabled(EDITOR_DEF("text_editor/appearance/caret/caret_blink", false)); + set_caret_blink_speed(EDITOR_DEF("text_editor/appearance/caret/caret_blink_speed", 0.65)); #endif } @@ -2277,6 +2288,11 @@ void LineEdit::_ensure_menu() { menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); + + if (editable) { + menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo()); + menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo()); + } } LineEdit::LineEdit() { diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index c5c92d60aa..0e9c032e88 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -285,6 +285,8 @@ public: void copy_text(); void cut_text(); void paste_text(); + bool has_undo() const; + bool has_redo() const; void undo(); void redo(); diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 63b5793b3e..dc6c7fec28 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -86,6 +86,7 @@ void MenuButton::_popup_visibility_changed(bool p_visible) { } void MenuButton::pressed() { + emit_signal(SNAME("about_to_popup")); Size2 size = get_size(); Point2 gp = get_screen_position(); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 4bd88fde5f..aff367e398 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -358,9 +358,10 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { } int button_idx = b->get_button_index(); - if (b->is_pressed() || (!b->is_pressed() && during_grabbed_click)) { - // Allow activating item by releasing the LMB or any that was down when the popup appeared. - // However, if button was not held when opening menu, do not allow release to activate item. + if (!b->is_pressed()) { + // Activate the item on release of either the left mouse button or + // any mouse button held down when the popup was opened. + // This allows for opening the popup and triggering an action in a single mouse click. if (button_idx == MOUSE_BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) { bool was_during_grabbed_click = during_grabbed_click; during_grabbed_click = false; diff --git a/scene/gui/rich_text_effect.cpp b/scene/gui/rich_text_effect.cpp index 39718a269a..236d106af8 100644 --- a/scene/gui/rich_text_effect.cpp +++ b/scene/gui/rich_text_effect.cpp @@ -32,8 +32,15 @@ #include "core/object/script_language.h" -void RichTextEffect::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_process_custom_fx", PropertyInfo(Variant::OBJECT, "char_fx", PROPERTY_HINT_RESOURCE_TYPE, "CharFXTransform"))); +CharFXTransform::CharFXTransform() { +} + +CharFXTransform::~CharFXTransform() { + environment.clear(); +} + +void RichTextEffect::_bind_methods(){ + GDVIRTUAL_BIND(_process_custom_fx, "char_fx") } Variant RichTextEffect::get_bbcode() const { @@ -49,15 +56,10 @@ Variant RichTextEffect::get_bbcode() const { bool RichTextEffect::_process_effect_impl(Ref<CharFXTransform> p_cfx) { bool return_value = false; - if (get_script_instance()) { - Variant v = get_script_instance()->call("_process_custom_fx", p_cfx); - if (v.get_type() != Variant::BOOL) { - return_value = false; - } else { - return_value = (bool)v; - } + if (GDVIRTUAL_CALL(_process_custom_fx, p_cfx, return_value)) { + return return_value; } - return return_value; + return false; } RichTextEffect::RichTextEffect() { @@ -101,10 +103,3 @@ void CharFXTransform::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_index"), "set_glyph_index", "get_glyph_index"); ADD_PROPERTY(PropertyInfo(Variant::RID, "font"), "set_font", "get_font"); } - -CharFXTransform::CharFXTransform() { -} - -CharFXTransform::~CharFXTransform() { - environment.clear(); -} diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h index 67323e7f93..e674b2f62f 100644 --- a/scene/gui/rich_text_effect.h +++ b/scene/gui/rich_text_effect.h @@ -32,20 +32,8 @@ #define RICH_TEXT_EFFECT_H #include "core/io/resource.h" - -class RichTextEffect : public Resource { - GDCLASS(RichTextEffect, Resource); - OBJ_SAVE_TYPE(RichTextEffect); - -protected: - static void _bind_methods(); - -public: - Variant get_bbcode() const; - bool _process_effect_impl(Ref<class CharFXTransform> p_cfx); - - RichTextEffect(); -}; +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" class CharFXTransform : public RefCounted { GDCLASS(CharFXTransform, RefCounted); @@ -89,4 +77,20 @@ public: void set_environment(Dictionary p_environment) { environment = p_environment; } }; +class RichTextEffect : public Resource { + GDCLASS(RichTextEffect, Resource); + OBJ_SAVE_TYPE(RichTextEffect); + +protected: + static void _bind_methods(); + + GDVIRTUAL1RC(bool, _process_custom_fx, Ref<CharFXTransform>) + +public: + Variant get_bbcode() const; + bool _process_effect_impl(Ref<class CharFXTransform> p_cfx); + + RichTextEffect(); +}; + #endif // RICH_TEXT_EFFECT_H diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index cf2a1481a1..75c2da7ef9 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1127,7 +1127,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); while (ofs.y < size.height && from_line < main->lines.size()) { _find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char); - ofs.y += main->lines[from_line].text_buf->get_size().y; + ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) { if (r_outside != nullptr) { *r_outside = false; @@ -1435,7 +1435,7 @@ void RichTextLabel::_notification(int p_what) { while (ofs.y < size.height && from_line < main->lines.size()) { visible_paragraph_count++; visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, use_outline, shadow_ofs); - ofs.y += main->lines[from_line].text_buf->get_size().y; + ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); from_line++; } } break; diff --git a/scene/gui/shortcut.cpp b/scene/gui/shortcut.cpp index c0aac6d91a..d0cb08724e 100644 --- a/scene/gui/shortcut.cpp +++ b/scene/gui/shortcut.cpp @@ -32,7 +32,7 @@ #include "core/os/keyboard.h" void Shortcut::set_event(const Ref<InputEvent> &p_event) { - ERR_FAIL_COND(Object::cast_to<InputEventShortcut>(*p_event)); + ERR_FAIL_COND_MSG(Object::cast_to<InputEventShortcut>(*p_event), "Cannot set a shortcut event to an instance of InputEventShortcut."); event = p_event; emit_changed(); } diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index cc41d961f6..53a2fe4d93 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -535,7 +535,7 @@ void TabContainer::_notification(int p_what) { } void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x) { - Vector<Control *> tabs = _get_tabs(); + Control *control = get_tab_control(p_index); RID canvas = get_canvas_item(); Ref<Font> font = get_theme_font(SNAME("font")); Color font_outline_color = get_theme_color(SNAME("font_outline_color")); @@ -549,7 +549,6 @@ void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, in p_tab_style->draw(canvas, tab_rect); // Draw the tab contents. - Control *control = Object::cast_to<Control>(tabs[p_index]); String text = control->has_meta("_tab_name") ? String(atr(String(control->get_meta("_tab_name")))) : String(atr(control->get_name())); int x_content = tab_rect.position.x + p_tab_style->get_margin(SIDE_LEFT); @@ -620,10 +619,10 @@ void TabContainer::_repaint() { if (tabs_visible) { c->set_offset(SIDE_TOP, _get_top_margin()); } - c->set_offset(Side(SIDE_TOP), c->get_offset(Side(SIDE_TOP)) + sb->get_margin(Side(SIDE_TOP))); - c->set_offset(Side(SIDE_LEFT), c->get_offset(Side(SIDE_LEFT)) + sb->get_margin(Side(SIDE_LEFT))); - c->set_offset(Side(SIDE_RIGHT), c->get_offset(Side(SIDE_RIGHT)) - sb->get_margin(Side(SIDE_RIGHT))); - c->set_offset(Side(SIDE_BOTTOM), c->get_offset(Side(SIDE_BOTTOM)) - sb->get_margin(Side(SIDE_BOTTOM))); + c->set_offset(SIDE_TOP, c->get_offset(SIDE_TOP) + sb->get_margin(SIDE_TOP)); + c->set_offset(SIDE_LEFT, c->get_offset(SIDE_LEFT) + sb->get_margin(SIDE_LEFT)); + c->set_offset(SIDE_RIGHT, c->get_offset(SIDE_RIGHT) - sb->get_margin(SIDE_RIGHT)); + c->set_offset(SIDE_BOTTOM, c->get_offset(SIDE_BOTTOM) - sb->get_margin(SIDE_BOTTOM)); } else { c->hide(); @@ -682,7 +681,7 @@ Vector<Control *> TabContainer::_get_tabs() const { Vector<Control *> controls; for (int i = 0; i < get_child_count(); i++) { Control *control = Object::cast_to<Control>(get_child(i)); - if (!control || control->is_top_level_control()) { + if (!control || control->is_set_as_top_level()) { continue; } @@ -700,10 +699,7 @@ void TabContainer::add_child_notify(Node *p_child) { Container::add_child_notify(p_child); Control *c = Object::cast_to<Control>(p_child); - if (!c) { - return; - } - if (c->is_set_as_top_level()) { + if (!c || c->is_set_as_top_level()) { return; } @@ -726,10 +722,10 @@ void TabContainer::add_child_notify(Node *p_child) { c->set_offset(SIDE_TOP, _get_top_margin()); } Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel")); - c->set_offset(Side(SIDE_TOP), c->get_offset(Side(SIDE_TOP)) + sb->get_margin(Side(SIDE_TOP))); - c->set_offset(Side(SIDE_LEFT), c->get_offset(Side(SIDE_LEFT)) + sb->get_margin(Side(SIDE_LEFT))); - c->set_offset(Side(SIDE_RIGHT), c->get_offset(Side(SIDE_RIGHT)) - sb->get_margin(Side(SIDE_RIGHT))); - c->set_offset(Side(SIDE_BOTTOM), c->get_offset(Side(SIDE_BOTTOM)) - sb->get_margin(Side(SIDE_BOTTOM))); + c->set_offset(SIDE_TOP, c->get_offset(SIDE_TOP) + sb->get_margin(SIDE_TOP)); + c->set_offset(SIDE_LEFT, c->get_offset(SIDE_LEFT) + sb->get_margin(SIDE_LEFT)); + c->set_offset(SIDE_RIGHT, c->get_offset(SIDE_RIGHT) - sb->get_margin(SIDE_RIGHT)); + c->set_offset(SIDE_BOTTOM, c->get_offset(SIDE_BOTTOM) - sb->get_margin(SIDE_BOTTOM)); update(); p_child->connect("renamed", callable_mp(this, &TabContainer::_child_renamed_callback)); if (first && is_inside_tree()) { @@ -739,8 +735,13 @@ void TabContainer::add_child_notify(Node *p_child) { void TabContainer::move_child_notify(Node *p_child) { Container::move_child_notify(p_child); - call_deferred(SNAME("_update_current_tab")); - _refresh_texts(); + + Control *c = Object::cast_to<Control>(p_child); + if (!c || c->is_set_as_top_level()) { + return; + } + + _update_current_tab(); update(); } @@ -785,21 +786,18 @@ Control *TabContainer::get_tab_control(int p_idx) const { } Control *TabContainer::get_current_tab_control() const { - Vector<Control *> tabs = _get_tabs(); - if (current >= 0 && current < tabs.size()) { - return tabs[current]; - } else { - return nullptr; - } + return get_tab_control(current); } void TabContainer::remove_child_notify(Node *p_child) { Container::remove_child_notify(p_child); - if (!Object::cast_to<Control>(p_child)) { + Control *c = Object::cast_to<Control>(p_child); + if (!c || c->is_set_as_top_level()) { return; } + // Defer the call because tab is not yet removed (remove_child_notify is called right before p_child is actually removed). call_deferred(SNAME("_update_current_tab")); p_child->disconnect("renamed", callable_mp(this, &TabContainer::_child_renamed_callback)); @@ -808,10 +806,9 @@ void TabContainer::remove_child_notify(Node *p_child) { } void TabContainer::_update_current_tab() { - Vector<Control *> tabs = _get_tabs(); _refresh_texts(); - int tc = tabs.size(); + int tc = get_tab_count(); if (current >= tc) { current = tc - 1; } @@ -1019,12 +1016,8 @@ bool TabContainer::is_all_tabs_in_front() const { return all_tabs_in_front; } -Control *TabContainer::_get_tab(int p_idx) const { - return get_tab_control(p_idx); -} - void TabContainer::set_tab_title(int p_tab, const String &p_title) { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_name", p_title); _refresh_texts(); @@ -1032,7 +1025,7 @@ void TabContainer::set_tab_title(int p_tab, const String &p_title) { } String TabContainer::get_tab_title(int p_tab) const { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND_V(!child, ""); if (child->has_meta("_tab_name")) { return child->get_meta("_tab_name"); @@ -1042,14 +1035,14 @@ String TabContainer::get_tab_title(int p_tab) const { } void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_icon", p_icon); update(); } Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND_V(!child, Ref<Texture2D>()); if (child->has_meta("_tab_icon")) { return child->get_meta("_tab_icon"); @@ -1059,14 +1052,14 @@ Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const { } void TabContainer::set_tab_disabled(int p_tab, bool p_disabled) { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_disabled", p_disabled); update(); } bool TabContainer::get_tab_disabled(int p_tab) const { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND_V(!child, false); if (child->has_meta("_tab_disabled")) { return child->get_meta("_tab_disabled"); @@ -1076,7 +1069,7 @@ bool TabContainer::get_tab_disabled(int p_tab) const { } void TabContainer::set_tab_hidden(int p_tab, bool p_hidden) { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_hidden", p_hidden); update(); @@ -1095,7 +1088,7 @@ void TabContainer::set_tab_hidden(int p_tab, bool p_hidden) { } bool TabContainer::get_tab_hidden(int p_tab) const { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND_V(!child, false); if (child->has_meta("_tab_hidden")) { return child->get_meta("_tab_hidden"); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 4ed5255729..35f18eff8e 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -57,7 +57,6 @@ private: bool menu_hovered = false; int highlight_arrow = -1; TabAlign align = ALIGN_CENTER; - Control *_get_tab(int p_idx) const; int _get_top_margin() const; mutable ObjectID popup_obj_id; bool drag_to_rearrange_enabled = false; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 745920fcdd..a65abd5f49 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -2768,48 +2768,38 @@ Point2i TextEdit::get_next_visible_line_index_offset_from(int p_line_from, int p // Overridable actions void TextEdit::handle_unicode_input(const uint32_t p_unicode) { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_handle_unicode_input")) { - si->call("_handle_unicode_input", p_unicode); + if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode)) { return; } - _handle_unicode_input(p_unicode); + _handle_unicode_input_internal(p_unicode); } void TextEdit::backspace() { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_backspace")) { - si->call("_backspace"); + if (GDVIRTUAL_CALL(_backspace)) { return; } - _backspace(); + _backspace_internal(); } void TextEdit::cut() { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_cut")) { - si->call("_cut"); + if (GDVIRTUAL_CALL(_cut)) { return; } - _cut(); + _cut_internal(); } void TextEdit::copy() { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_copy")) { - si->call("_copy"); + if (GDVIRTUAL_CALL(_copy)) { return; } - _copy(); + _copy_internal(); } void TextEdit::paste() { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_paste")) { - si->call("_paste"); + if (GDVIRTUAL_CALL(_paste)) { return; } - _paste(); + _paste_internal(); } // Context menu. @@ -2963,6 +2953,18 @@ void TextEdit::end_complex_operation() { undo_stack.back()->get().chain_backward = true; } +bool TextEdit::has_undo() const { + if (undo_stack_pos == nullptr) { + int pending = current_op.type == TextOperation::TYPE_NONE ? 0 : 1; + return undo_stack.size() + pending > 0; + } + return undo_stack_pos != undo_stack.front(); +} + +bool TextEdit::has_redo() const { + return undo_stack_pos != nullptr; +} + void TextEdit::undo() { if (!editable) { return; @@ -4332,7 +4334,9 @@ bool TextEdit::is_highlight_all_occurrences_enabled() const { void TextEdit::set_draw_control_chars(bool p_draw_control_chars) { if (draw_control_chars != p_draw_control_chars) { draw_control_chars = p_draw_control_chars; - menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); + if (menu) { + menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); + } text.set_draw_control_chars(draw_control_chars); text.invalidate_all(); update(); @@ -4438,12 +4442,11 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy); ClassDB::bind_method(D_METHOD("paste"), &TextEdit::paste); - BIND_VMETHOD(MethodInfo("_handle_unicode_input", PropertyInfo(Variant::INT, "unicode"))) - BIND_VMETHOD(MethodInfo("_backspace")); - - BIND_VMETHOD(MethodInfo("_cut")); - BIND_VMETHOD(MethodInfo("_copy")); - BIND_VMETHOD(MethodInfo("_paste")); + GDVIRTUAL_BIND(_handle_unicode_input, "unicode_char") + GDVIRTUAL_BIND(_backspace) + GDVIRTUAL_BIND(_cut) + GDVIRTUAL_BIND(_copy) + GDVIRTUAL_BIND(_paste) // Context Menu BIND_ENUM_CONSTANT(MENU_CUT); @@ -4480,6 +4483,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("begin_complex_operation"), &TextEdit::begin_complex_operation); ClassDB::bind_method(D_METHOD("end_complex_operation"), &TextEdit::end_complex_operation); + ClassDB::bind_method(D_METHOD("has_undo"), &TextEdit::has_undo); + ClassDB::bind_method(D_METHOD("has_redo"), &TextEdit::has_redo); ClassDB::bind_method(D_METHOD("undo"), &TextEdit::undo); ClassDB::bind_method(D_METHOD("redo"), &TextEdit::redo); ClassDB::bind_method(D_METHOD("clear_undo_history"), &TextEdit::clear_undo_history); @@ -4868,7 +4873,7 @@ void TextEdit::_set_symbol_lookup_word(const String &p_symbol) { /* Text manipulation */ // Overridable actions -void TextEdit::_handle_unicode_input(const uint32_t p_unicode) { +void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { if (!editable) { return; } @@ -4899,11 +4904,16 @@ void TextEdit::_handle_unicode_input(const uint32_t p_unicode) { } } -void TextEdit::_backspace() { +void TextEdit::_backspace_internal() { if (!editable) { return; } + if (has_selection()) { + delete_selection(); + return; + } + int cc = get_caret_column(); int cl = get_caret_line(); @@ -4911,11 +4921,6 @@ void TextEdit::_backspace() { return; } - if (has_selection()) { - delete_selection(); - return; - } - int prev_line = cc ? cl : cl - 1; int prev_column = cc ? (cc - 1) : (text[cl - 1].length()); @@ -4930,7 +4935,7 @@ void TextEdit::_backspace() { set_caret_column(prev_column); } -void TextEdit::_cut() { +void TextEdit::_cut_internal() { if (!editable) { return; } @@ -4960,7 +4965,7 @@ void TextEdit::_cut() { cut_copy_line = clipboard; } -void TextEdit::_copy() { +void TextEdit::_copy_internal() { if (has_selection()) { DisplayServer::get_singleton()->clipboard_set(get_selected_text()); cut_copy_line = ""; @@ -4975,7 +4980,7 @@ void TextEdit::_copy() { } } -void TextEdit::_paste() { +void TextEdit::_paste_internal() { if (!editable) { return; } @@ -5068,6 +5073,11 @@ void TextEdit::_generate_context_menu() { menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); + + if (editable) { + menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo()); + menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo()); + } } int TextEdit::_get_menu_action_accelerator(const String &p_action) { diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index da322a7bcd..7d00ce14af 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -562,12 +562,18 @@ protected: /* Text manipulation */ // Overridable actions - virtual void _handle_unicode_input(const uint32_t p_unicode); - virtual void _backspace(); + virtual void _handle_unicode_input_internal(const uint32_t p_unicode); + virtual void _backspace_internal(); - virtual void _cut(); - virtual void _copy(); - virtual void _paste(); + virtual void _cut_internal(); + virtual void _copy_internal(); + virtual void _paste_internal(); + + GDVIRTUAL1(_handle_unicode_input, int) + GDVIRTUAL0(_backspace) + GDVIRTUAL0(_cut) + GDVIRTUAL0(_copy) + GDVIRTUAL0(_paste) public: /* General overrides. */ @@ -659,6 +665,8 @@ public: void begin_complex_operation(); void end_complex_operation(); + bool has_undo() const; + bool has_redo() const; void undo(); void redo(); void clear_undo_history(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 09db3205d9..d9892b53fc 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -167,6 +167,18 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { 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; + _changed_notify(p_column); +} + +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; _changed_notify(p_column); } @@ -175,6 +187,11 @@ bool TreeItem::is_checked(int p_column) const { return cells[p_column].checked; } +bool TreeItem::is_indeterminate(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), false); + return cells[p_column].indeterminate; +} + void TreeItem::set_text(int p_column, String p_text) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].text = p_text; @@ -1042,7 +1059,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cell_mode", "column"), &TreeItem::get_cell_mode); ClassDB::bind_method(D_METHOD("set_checked", "column", "checked"), &TreeItem::set_checked); + ClassDB::bind_method(D_METHOD("set_indeterminate", "column", "indeterminate"), &TreeItem::set_indeterminate); ClassDB::bind_method(D_METHOD("is_checked", "column"), &TreeItem::is_checked); + ClassDB::bind_method(D_METHOD("is_indeterminate", "column"), &TreeItem::is_indeterminate); ClassDB::bind_method(D_METHOD("set_text", "column", "text"), &TreeItem::set_text); ClassDB::bind_method(D_METHOD("get_text", "column"), &TreeItem::get_text); @@ -1228,6 +1247,7 @@ void Tree::update_cache() { cache.checked = get_theme_icon(SNAME("checked")); cache.unchecked = get_theme_icon(SNAME("unchecked")); + cache.indeterminate = get_theme_icon(SNAME("indeterminate")); if (is_layout_rtl()) { cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed_mirrored")); } else { @@ -1721,10 +1741,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 case TreeItem::CELL_MODE_CHECK: { Ref<Texture2D> checked = cache.checked; Ref<Texture2D> unchecked = cache.unchecked; + Ref<Texture2D> indeterminate = cache.indeterminate; Point2i check_ofs = item_rect.position; check_ofs.y += Math::floor((real_t)(item_rect.size.y - checked->get_height()) / 2); - if (p_item->cells[i].checked) { + if (p_item->cells[i].indeterminate) { + indeterminate->draw(ci, check_ofs); + } else if (p_item->cells[i].checked) { checked->draw(ci, check_ofs); } else { unchecked->draw(ci, check_ofs); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index be711b4c4f..c207737cc0 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -82,6 +82,7 @@ private: int icon_max_w = 0; bool expr = false; bool checked = false; + bool indeterminate = false; bool editable = false; bool selected = false; bool selectable = true; @@ -209,7 +210,9 @@ public: /* check mode */ void set_checked(int p_column, bool p_checked); + void set_indeterminate(int p_column, bool p_indeterminate); bool is_checked(int p_column) const; + bool is_indeterminate(int p_column) const; void set_text(int p_column, String p_text); String get_text(int p_column) const; @@ -491,6 +494,7 @@ private: Ref<Texture2D> checked; Ref<Texture2D> unchecked; + Ref<Texture2D> indeterminate; Ref<Texture2D> arrow_collapsed; Ref<Texture2D> arrow; Ref<Texture2D> select_arrow; diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index f2415eaf71..f329332725 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -151,9 +151,7 @@ void CanvasItem::_update_callback() { current_item_drawn = this; notification(NOTIFICATION_DRAW); emit_signal(SceneStringNames::get_singleton()->draw); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_draw); - } + GDVIRTUAL_CALL(_draw); current_item_drawn = nullptr; drawing = false; } @@ -934,7 +932,7 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_clip_children", "enable"), &CanvasItem::set_clip_children); ClassDB::bind_method(D_METHOD("is_clipping_children"), &CanvasItem::is_clipping_children); - BIND_VMETHOD(MethodInfo("_draw")); + GDVIRTUAL_BIND(_draw); ADD_GROUP("Visibility", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index dc7ad2bf5d..a591cab485 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -142,6 +142,7 @@ protected: void _notification(int p_what); static void _bind_methods(); + GDVIRTUAL0(_draw) public: enum { NOTIFICATION_TRANSFORM_CHANGED = SceneTree::NOTIFICATION_TRANSFORM_CHANGED, //unique diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 155af30a6d..1d617d1ff7 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -54,16 +54,11 @@ int Node::orphan_node_count = 0; void Node::_notification(int p_notification) { switch (p_notification) { case NOTIFICATION_PROCESS: { - if (get_script_instance()) { - Variant time = get_process_delta_time(); - get_script_instance()->call(SceneStringNames::get_singleton()->_process, time); - } + GDVIRTUAL_CALL(_process, get_process_delta_time()); + } break; case NOTIFICATION_PHYSICS_PROCESS: { - if (get_script_instance()) { - Variant time = get_physics_process_delta_time(); - get_script_instance()->call(SceneStringNames::get_singleton()->_physics_process, time); - } + GDVIRTUAL_CALL(_physics_process, get_process_delta_time()); } break; case NOTIFICATION_ENTER_TREE: { @@ -140,15 +135,14 @@ void Node::_notification(int p_notification) { set_process_unhandled_key_input(true); } - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_process)) { + if (GDVIRTUAL_IS_OVERRIDEN(_process)) { set_process(true); } - - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_physics_process)) { + if (GDVIRTUAL_IS_OVERRIDEN(_physics_process)) { set_physics_process(true); } - get_script_instance()->call(SceneStringNames::get_singleton()->_ready); + GDVIRTUAL_CALL(_ready); } if (data.filename.length()) { ERR_FAIL_COND(!is_inside_tree()); @@ -221,9 +215,7 @@ void Node::_propagate_enter_tree() { notification(NOTIFICATION_ENTER_TREE); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_enter_tree); - } + GDVIRTUAL_CALL(_enter_tree); emit_signal(SceneStringNames::get_singleton()->tree_entered); @@ -269,9 +261,8 @@ void Node::_propagate_exit_tree() { data.blocked--; - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_exit_tree); - } + GDVIRTUAL_CALL(_exit_tree); + emit_signal(SceneStringNames::get_singleton()->tree_exiting); notification(NOTIFICATION_EXIT_TREE, true); @@ -2737,11 +2728,13 @@ void Node::_bind_methods() { ADD_GROUP("Editor Description", "editor_"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "editor_description", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "set_editor_description", "get_editor_description"); - BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_physics_process", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_enter_tree")); - BIND_VMETHOD(MethodInfo("_exit_tree")); - BIND_VMETHOD(MethodInfo("_ready")); + GDVIRTUAL_BIND(_process, "delta"); + GDVIRTUAL_BIND(_physics_process, "delta"); + GDVIRTUAL_BIND(_enter_tree); + GDVIRTUAL_BIND(_exit_tree); + GDVIRTUAL_BIND(_ready); + GDVIRTUAL_BIND(_get_configuration_warnings); + BIND_VMETHOD(MethodInfo("_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); BIND_VMETHOD(MethodInfo("_unhandled_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); BIND_VMETHOD(MethodInfo("_unhandled_key_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEventKey"))); diff --git a/scene/main/node.h b/scene/main/node.h index 9997f4e055..8aa56aa97f 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -202,12 +202,18 @@ protected: static String _get_name_num_separator(); friend class SceneState; - friend class MultiplayerAPI; + friend class MultiplayerReplicator; void _add_child_nocheck(Node *p_child, const StringName &p_name); void _set_owner_nocheck(Node *p_owner); void _set_name_nocheck(const StringName &p_name); + GDVIRTUAL1(_process, double) + GDVIRTUAL1(_physics_process, double) + GDVIRTUAL0(_enter_tree) + GDVIRTUAL0(_exit_tree) + GDVIRTUAL0(_ready) + GDVIRTUAL0RC(Vector<String>, _get_configuration_warnings) public: enum { // you can make your own, but don't use the same numbers as other notifications in other nodes diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 6995c77b8e..73eec73d75 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -300,7 +300,7 @@ void Window::_propagate_window_notification(Node *p_node, int p_notification) { Node *child = p_node->get_child(i); Window *window = Object::cast_to<Window>(child); if (window) { - break; + continue; } _propagate_window_notification(child, p_notification); } diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 47c7e57e3f..6f8091c03f 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -168,7 +168,15 @@ #include "scene/resources/skeleton_modification_2d_physicalbones.h" #include "scene/resources/skeleton_modification_2d_stackholder.h" #include "scene/resources/skeleton_modification_2d_twoboneik.h" +#include "scene/resources/skeleton_modification_3d.h" +#include "scene/resources/skeleton_modification_3d_ccdik.h" +#include "scene/resources/skeleton_modification_3d_fabrik.h" +#include "scene/resources/skeleton_modification_3d_jiggle.h" +#include "scene/resources/skeleton_modification_3d_lookat.h" +#include "scene/resources/skeleton_modification_3d_stackholder.h" +#include "scene/resources/skeleton_modification_3d_twoboneik.h" #include "scene/resources/skeleton_modification_stack_2d.h" +#include "scene/resources/skeleton_modification_stack_3d.h" #include "scene/resources/sky.h" #include "scene/resources/sky_material.h" #include "scene/resources/sphere_shape_3d.h" @@ -754,6 +762,15 @@ void register_scene_types() { GDREGISTER_CLASS(ConvexPolygonShape3D); GDREGISTER_CLASS(ConcavePolygonShape3D); + ClassDB::register_class<SkeletonModificationStack3D>(); + ClassDB::register_class<SkeletonModification3D>(); + ClassDB::register_class<SkeletonModification3DLookAt>(); + ClassDB::register_class<SkeletonModification3DCCDIK>(); + ClassDB::register_class<SkeletonModification3DFABRIK>(); + ClassDB::register_class<SkeletonModification3DJiggle>(); + ClassDB::register_class<SkeletonModification3DTwoBoneIK>(); + ClassDB::register_class<SkeletonModification3DStackHolder>(); + OS::get_singleton()->yield(); //may take time to init GDREGISTER_CLASS(VelocityTracker3D); @@ -795,7 +812,6 @@ void register_scene_types() { GDREGISTER_CLASS(Font); GDREGISTER_CLASS(Curve); - GDREGISTER_CLASS(TextFile); GDREGISTER_CLASS(TextLine); GDREGISTER_CLASS(TextParagraph); diff --git a/scene/resources/canvas_item_material.cpp b/scene/resources/canvas_item_material.cpp index 7ba57a6532..7501efea9e 100644 --- a/scene/resources/canvas_item_material.cpp +++ b/scene/resources/canvas_item_material.cpp @@ -30,6 +30,8 @@ #include "canvas_item_material.h" +#include "core/version.h" + Mutex CanvasItemMaterial::material_mutex; SelfList<CanvasItemMaterial>::List *CanvasItemMaterial::dirty_materials = nullptr; Map<CanvasItemMaterial::MaterialKey, CanvasItemMaterial::ShaderData> CanvasItemMaterial::shader_map; @@ -78,7 +80,10 @@ void CanvasItemMaterial::_update_shader() { //must create a shader! - String code = "shader_type canvas_item;\nrender_mode "; + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + String code = "// NOTE: Shader automatically converted from " VERSION_NAME " " VERSION_FULL_CONFIG "'s CanvasItemMaterial.\n\n"; + + code += "shader_type canvas_item;\nrender_mode "; switch (blend_mode) { case BLEND_MODE_MIX: code += "blend_mix"; diff --git a/scene/resources/capsule_shape_2d.cpp b/scene/resources/capsule_shape_2d.cpp index 596fa70f15..0818e4fd99 100644 --- a/scene/resources/capsule_shape_2d.cpp +++ b/scene/resources/capsule_shape_2d.cpp @@ -59,7 +59,10 @@ void CapsuleShape2D::_update_shape() { } void CapsuleShape2D::set_radius(real_t p_radius) { - radius = MIN(p_radius, height * 0.5); + radius = p_radius; + if (radius > height * 0.5) { + height = radius * 2.0; + } _update_shape(); } @@ -68,7 +71,10 @@ real_t CapsuleShape2D::get_radius() const { } void CapsuleShape2D::set_height(real_t p_height) { - height = MAX(p_height, radius * 2); + height = p_height; + if (radius > height * 0.5) { + radius = height * 0.5; + } _update_shape(); } @@ -105,6 +111,8 @@ void CapsuleShape2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height"), "set_height", "get_height"); + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); } CapsuleShape2D::CapsuleShape2D() : diff --git a/scene/resources/capsule_shape_3d.cpp b/scene/resources/capsule_shape_3d.cpp index e267941d6a..afec7b1877 100644 --- a/scene/resources/capsule_shape_3d.cpp +++ b/scene/resources/capsule_shape_3d.cpp @@ -81,7 +81,7 @@ void CapsuleShape3D::_update_shape() { void CapsuleShape3D::set_radius(float p_radius) { radius = p_radius; if (radius > height * 0.5) { - radius = height * 0.5; + height = radius * 2.0; } _update_shape(); notify_change_to_owners(); @@ -94,7 +94,7 @@ float CapsuleShape3D::get_radius() const { void CapsuleShape3D::set_height(float p_height) { height = p_height; if (radius > height * 0.5) { - height = radius * 2; + radius = height * 0.5; } _update_shape(); notify_change_to_owners(); @@ -112,6 +112,8 @@ void CapsuleShape3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,4096,0.001"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,4096,0.001"), "set_height", "get_height"); + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); } CapsuleShape3D::CapsuleShape3D() : diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 3b666640f8..a364a27e80 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -662,19 +662,27 @@ void Curve2D::_bake() const { if (points.size() == 0) { baked_point_cache.resize(0); + baked_dist_cache.resize(0); return; } if (points.size() == 1) { baked_point_cache.resize(1); baked_point_cache.set(0, points[0].pos); + + baked_dist_cache.resize(1); + baked_dist_cache.set(0, 0.0); return; } Vector2 pos = points[0].pos; + float dist = 0.0; + List<Vector2> pointlist; + List<float> distlist; pointlist.push_back(pos); //start always from origin + distlist.push_back(0.0); for (int i = 0; i < points.size() - 1; i++) { float step = 0.1; // at least 10 substeps ought to be enough? @@ -712,7 +720,10 @@ void Curve2D::_bake() const { pos = npp; p = mid; + dist += d; + pointlist.push_back(pos); + distlist.push_back(dist); } else { p = np; } @@ -722,16 +733,20 @@ void Curve2D::_bake() const { Vector2 lastpos = points[points.size() - 1].pos; float rem = pos.distance_to(lastpos); - baked_max_ofs = (pointlist.size() - 1) * bake_interval + rem; + dist += rem; + baked_max_ofs = dist; pointlist.push_back(lastpos); + distlist.push_back(dist); baked_point_cache.resize(pointlist.size()); + baked_dist_cache.resize(distlist.size()); + Vector2 *w = baked_point_cache.ptrw(); - int idx = 0; + float *wd = baked_dist_cache.ptrw(); - for (const Vector2 &E : pointlist) { - w[idx] = E; - idx++; + for (int i = 0; i < pointlist.size(); i++) { + w[i] = pointlist[i]; + wd[i] = distlist[i]; } } @@ -766,19 +781,26 @@ Vector2 Curve2D::interpolate_baked(float p_offset, bool p_cubic) const { return r[bpc - 1]; } - int idx = Math::floor((double)p_offset / (double)bake_interval); - float frac = Math::fmod(p_offset, (float)bake_interval); - - if (idx >= bpc - 1) { - return r[bpc - 1]; - } else if (idx == bpc - 2) { - if (frac > 0) { - frac /= Math::fmod(baked_max_ofs, bake_interval); + int start = 0, end = bpc, idx = (end + start) / 2; + // binary search to find baked points + while (start < idx) { + float offset = baked_dist_cache[idx]; + if (p_offset <= offset) { + end = idx; + } else { + start = idx; } - } else { - frac /= bake_interval; + idx = (end + start) / 2; } + float offset_begin = baked_dist_cache[idx]; + float offset_end = baked_dist_cache[idx + 1]; + + float idx_interval = offset_end - offset_begin; + ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, Vector2(), "failed to find baked segment"); + + float frac = (p_offset - offset_begin) / idx_interval; + if (p_cubic) { Vector2 pre = idx > 0 ? r[idx - 1] : r[idx]; Vector2 post = (idx < (bpc - 2)) ? r[idx + 2] : r[idx + 1]; @@ -1145,6 +1167,7 @@ void Curve3D::_bake() const { baked_point_cache.resize(0); baked_tilt_cache.resize(0); baked_up_vector_cache.resize(0); + baked_dist_cache.resize(0); return; } @@ -1153,6 +1176,8 @@ void Curve3D::_bake() const { baked_point_cache.set(0, points[0].pos); baked_tilt_cache.resize(1); baked_tilt_cache.set(0, points[0].tilt); + baked_dist_cache.resize(1); + baked_dist_cache.set(0, 0.0); if (up_vector_enabled) { baked_up_vector_cache.resize(1); @@ -1165,8 +1190,12 @@ void Curve3D::_bake() const { } Vector3 pos = points[0].pos; + float dist = 0.0; List<Plane> pointlist; + List<float> distlist; + pointlist.push_back(Plane(pos, points[0].tilt)); + distlist.push_back(0.0); for (int i = 0; i < points.size() - 1; i++) { float step = 0.1; // at least 10 substeps ought to be enough? @@ -1207,7 +1236,10 @@ void Curve3D::_bake() const { Plane post; post.normal = pos; post.d = Math::lerp(points[i].tilt, points[i + 1].tilt, mid); + dist += d; + pointlist.push_back(post); + distlist.push_back(dist); } else { p = np; } @@ -1218,8 +1250,10 @@ void Curve3D::_bake() const { float lastilt = points[points.size() - 1].tilt; float rem = pos.distance_to(lastpos); - baked_max_ofs = (pointlist.size() - 1) * bake_interval + rem; + dist += rem; + baked_max_ofs = dist; pointlist.push_back(Plane(lastpos, lastilt)); + distlist.push_back(dist); baked_point_cache.resize(pointlist.size()); Vector3 *w = baked_point_cache.ptrw(); @@ -1231,6 +1265,9 @@ void Curve3D::_bake() const { baked_up_vector_cache.resize(up_vector_enabled ? pointlist.size() : 0); Vector3 *up_write = baked_up_vector_cache.ptrw(); + baked_dist_cache.resize(pointlist.size()); + float *wd = baked_dist_cache.ptrw(); + Vector3 sideways; Vector3 up; Vector3 forward; @@ -1242,6 +1279,7 @@ void Curve3D::_bake() const { for (const Plane &E : pointlist) { w[idx] = E.normal; wt[idx] = E.d; + wd[idx] = distlist[idx]; if (!up_vector_enabled) { idx++; @@ -1308,19 +1346,26 @@ Vector3 Curve3D::interpolate_baked(float p_offset, bool p_cubic) const { return r[bpc - 1]; } - int idx = Math::floor((double)p_offset / (double)bake_interval); - float frac = Math::fmod(p_offset, bake_interval); - - if (idx >= bpc - 1) { - return r[bpc - 1]; - } else if (idx == bpc - 2) { - if (frac > 0) { - frac /= Math::fmod(baked_max_ofs, bake_interval); + int start = 0, end = bpc, idx = (end + start) / 2; + // binary search to find baked points + while (start < idx) { + float offset = baked_dist_cache[idx]; + if (p_offset <= offset) { + end = idx; + } else { + start = idx; } - } else { - frac /= bake_interval; + idx = (end + start) / 2; } + float offset_begin = baked_dist_cache[idx]; + float offset_end = baked_dist_cache[idx + 1]; + + float idx_interval = offset_end - offset_begin; + ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, Vector3(), "failed to find baked segment"); + + float frac = (p_offset - offset_begin) / idx_interval; + if (p_cubic) { Vector3 pre = idx > 0 ? r[idx - 1] : r[idx]; Vector3 post = (idx < (bpc - 2)) ? r[idx + 2] : r[idx + 1]; diff --git a/scene/resources/curve.h b/scene/resources/curve.h index c25d307608..5808fd6508 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -161,6 +161,7 @@ class Curve2D : public Resource { mutable bool baked_cache_dirty = false; mutable PackedVector2Array baked_point_cache; + mutable PackedFloat32Array baked_dist_cache; mutable float baked_max_ofs = 0.0; void _bake() const; @@ -224,6 +225,7 @@ class Curve3D : public Resource { mutable PackedVector3Array baked_point_cache; mutable Vector<real_t> baked_tilt_cache; mutable PackedVector3Array baked_up_vector_cache; + mutable PackedFloat32Array baked_dist_cache; mutable float baked_max_ofs = 0.0; void _bake() const; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 8208c55801..dd7b348498 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -212,26 +212,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("outline_size", "LinkButton", 0); theme->set_constant("underline_spacing", "LinkButton", 2 * scale); - // ColorPickerButton - - theme->set_stylebox("normal", "ColorPickerButton", sb_button_normal); - theme->set_stylebox("pressed", "ColorPickerButton", sb_button_pressed); - theme->set_stylebox("hover", "ColorPickerButton", sb_button_hover); - theme->set_stylebox("disabled", "ColorPickerButton", sb_button_disabled); - theme->set_stylebox("focus", "ColorPickerButton", sb_button_focus); - - theme->set_font("font", "ColorPickerButton", Ref<Font>()); - theme->set_font_size("font_size", "ColorPickerButton", -1); - - theme->set_color("font_color", "ColorPickerButton", Color(1, 1, 1, 1)); - theme->set_color("font_pressed_color", "ColorPickerButton", Color(0.8, 0.8, 0.8, 1)); - theme->set_color("font_hover_color", "ColorPickerButton", Color(1, 1, 1, 1)); - theme->set_color("font_disabled_color", "ColorPickerButton", Color(0.9, 0.9, 0.9, 0.3)); - theme->set_color("font_outline_color", "ColorPickerButton", Color(1, 1, 1)); - - theme->set_constant("hseparation", "ColorPickerButton", 2 * scale); - theme->set_constant("outline_size", "ColorPickerButton", 0); - // OptionButton Ref<StyleBox> sb_optbutton_focus = sb_expand(make_stylebox(button_focus_png, 4, 4, 4, 4, 6, 2, 6, 2), 2, 2, 2, 2); @@ -704,6 +684,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("checked", "Tree", make_icon(checked_png)); theme->set_icon("unchecked", "Tree", make_icon(unchecked_png)); + theme->set_icon("indeterminate", "Tree", make_icon(indeterminate_png)); theme->set_icon("updown", "Tree", make_icon(updown_png)); theme->set_icon("select_arrow", "Tree", make_icon(dropdown_png)); theme->set_icon("arrow", "Tree", make_icon(arrow_down_png)); @@ -858,7 +839,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("add_preset", "ColorPicker", make_icon(icon_add_png)); theme->set_icon("color_hue", "ColorPicker", make_icon(color_picker_hue_png)); theme->set_icon("color_sample", "ColorPicker", make_icon(color_picker_sample_png)); - theme->set_icon("preset_bg", "ColorPicker", make_icon(mini_checkerboard_png)); + theme->set_icon("sample_bg", "ColorPicker", make_icon(mini_checkerboard_png)); theme->set_icon("overbright_indicator", "ColorPicker", make_icon(overbright_indicator_png)); theme->set_icon("bar_arrow", "ColorPicker", make_icon(bar_arrow_png)); theme->set_icon("picker_cursor", "ColorPicker", make_icon(picker_cursor_png)); @@ -866,6 +847,34 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // ColorPickerButton theme->set_icon("bg", "ColorPickerButton", make_icon(mini_checkerboard_png)); + theme->set_stylebox("normal", "ColorPickerButton", sb_button_normal); + theme->set_stylebox("pressed", "ColorPickerButton", sb_button_pressed); + theme->set_stylebox("hover", "ColorPickerButton", sb_button_hover); + theme->set_stylebox("disabled", "ColorPickerButton", sb_button_disabled); + theme->set_stylebox("focus", "ColorPickerButton", sb_button_focus); + + theme->set_font("font", "ColorPickerButton", Ref<Font>()); + theme->set_font_size("font_size", "ColorPickerButton", -1); + + theme->set_color("font_color", "ColorPickerButton", Color(1, 1, 1, 1)); + theme->set_color("font_pressed_color", "ColorPickerButton", Color(0.8, 0.8, 0.8, 1)); + theme->set_color("font_hover_color", "ColorPickerButton", Color(1, 1, 1, 1)); + theme->set_color("font_disabled_color", "ColorPickerButton", Color(0.9, 0.9, 0.9, 0.3)); + theme->set_color("font_outline_color", "ColorPickerButton", Color(1, 1, 1)); + + theme->set_constant("hseparation", "ColorPickerButton", 2 * scale); + theme->set_constant("outline_size", "ColorPickerButton", 0); + + // ColorPresetButton + + Ref<StyleBoxFlat> preset_sb = make_flat_stylebox(Color(1, 1, 1), 2, 2, 2, 2); + preset_sb->set_corner_radius_all(2); + preset_sb->set_corner_detail(2); + preset_sb->set_anti_aliased(false); + + theme->set_stylebox("preset_fg", "ColorPresetButton", preset_sb); + theme->set_icon("preset_bg", "ColorPresetButton", make_icon(mini_checkerboard_png)); + theme->set_icon("overbright_indicator", "ColorPresetButton", make_icon(overbright_indicator_png)); // TooltipPanel @@ -916,7 +925,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("shadow_offset_y", "RichTextLabel", 1 * scale); theme->set_constant("shadow_as_outline", "RichTextLabel", 0 * scale); - theme->set_constant("line_separation", "RichTextLabel", 1 * scale); + theme->set_constant("line_separation", "RichTextLabel", 0 * scale); theme->set_constant("table_hseparation", "RichTextLabel", 3 * scale); theme->set_constant("table_vseparation", "RichTextLabel", 3 * scale); diff --git a/scene/resources/default_theme/icon_grid_layout.png b/scene/resources/default_theme/icon_grid_layout.png Binary files differindex 00a6179d5e..a249252a79 100644 --- a/scene/resources/default_theme/icon_grid_layout.png +++ b/scene/resources/default_theme/icon_grid_layout.png diff --git a/scene/resources/default_theme/indeterminate.png b/scene/resources/default_theme/indeterminate.png Binary files differnew file mode 100644 index 0000000000..28a457b251 --- /dev/null +++ b/scene/resources/default_theme/indeterminate.png diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h index 865ee86c76..6a556c1112 100644 --- a/scene/resources/default_theme/theme_data.h +++ b/scene/resources/default_theme/theme_data.h @@ -178,11 +178,11 @@ static const unsigned char icon_folder_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x2e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xa0, 0x6, 0x78, 0x70, 0xf4, 0xc1, 0x7f, 0x24, 0x78, 0x18, 0x53, 0xc1, 0x7f, 0x54, 0x48, 0x50, 0xc1, 0x43, 0x1b, 0xbc, 0xa, 0x50, 0xad, 0x23, 0xa4, 0xe0, 0xff, 0x70, 0x52, 0x70, 0x18, 0x97, 0xf4, 0xfd, 0x43, 0xd4, 0x88, 0x4a, 0x0, 0x5a, 0xcb, 0x18, 0xab, 0x5e, 0xd9, 0x1a, 0x53, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char icon_grid_minimap_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1f, 0xf3, 0xff, 0x61, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc3, 0x0, 0x0, 0xe, 0xc3, 0x1, 0xc7, 0x6f, 0xa8, 0x64, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x2, 0xd, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0x75, 0x93, 0x31, 0x68, 0x14, 0x51, 0x10, 0x86, 0xbf, 0xd9, 0xd, 0xbb, 0xde, 0x76, 0x82, 0x21, 0xf8, 0xe0, 0xbc, 0x5d, 0x8b, 0x80, 0x69, 0x6c, 0xd2, 0x5a, 0x6a, 0x91, 0xc3, 0xd2, 0x46, 0x22, 0x8, 0x9, 0x89, 0x70, 0x85, 0x10, 0x41, 0xd, 0x24, 0x45, 0xb0, 0xb, 0x68, 0x11, 0x14, 0x24, 0x10, 0x22, 0x62, 0x21, 0x41, 0xe, 0x4b, 0x21, 0xa4, 0xb7, 0x49, 0x17, 0xb1, 0x8, 0xb9, 0xdd, 0xc7, 0x86, 0x33, 0x21, 0xe1, 0x3a, 0x8f, 0x64, 0x61, 0x6f, 0x2c, 0xbc, 0x3b, 0x36, 0xb9, 0xdc, 0xc0, 0x2b, 0xde, 0xcc, 0xfc, 0xf3, 0xff, 0xfc, 0xcc, 0x48, 0xa3, 0xd1, 0x78, 0x20, 0x22, 0x13, 0xbe, 0xef, 0xaf, 0xdf, 0xac, 0xd7, 0x1f, 0xe1, 0x38, 0xd3, 0xa8, 0x2a, 0xf0, 0x45, 0x6a, 0xb5, 0xcf, 0x5c, 0x11, 0xcd, 0x66, 0x33, 0x38, 0x3f, 0x3f, 0x9f, 0x13, 0x91, 0x7d, 0xb1, 0xd6, 0x6e, 0xaa, 0xea, 0xd3, 0xe0, 0xe8, 0xe8, 0xde, 0xe8, 0xee, 0xee, 0x37, 0xc0, 0xe9, 0xf6, 0x75, 0xf0, 0xfd, 0x9, 0x99, 0x9d, 0x6d, 0x15, 0x81, 0x59, 0x96, 0x3d, 0x3, 0x5e, 0x2, 0x63, 0x22, 0xf2, 0x69, 0xa4, 0x57, 0x1c, 0xdd, 0xdb, 0xfb, 0x5b, 0x0, 0x3, 0x38, 0x67, 0x41, 0x30, 0x11, 0xc7, 0xf1, 0x13, 0x0, 0x11, 0x71, 0xb2, 0x2c, 0x7b, 0xd8, 0xad, 0xad, 0x2, 0x6f, 0xb9, 0x0, 0x38, 0x3c, 0xfc, 0x5, 0x9c, 0xf6, 0xff, 0x22, 0x27, 0x27, 0xe3, 0xe3, 0x7f, 0xa, 0x3, 0x67, 0x45, 0xe4, 0xbb, 0xe7, 0x79, 0xb7, 0xc3, 0x30, 0x7c, 0xd7, 0x67, 0xe9, 0xe3, 0x67, 0x66, 0x5c, 0x60, 0x1, 0x50, 0x40, 0x51, 0x7d, 0x71, 0x6b, 0x72, 0xf2, 0x20, 0x8a, 0xa2, 0xf9, 0x28, 0x8a, 0xe6, 0x1, 0x3a, 0x9d, 0xce, 0x4f, 0x63, 0x4c, 0x3b, 0x4d, 0xd3, 0xd2, 0xc0, 0x80, 0x3c, 0xcf, 0xf, 0x92, 0xa9, 0xa9, 0x31, 0x60, 0x5, 0x58, 0x91, 0x5a, 0xed, 0xc7, 0x15, 0xfe, 0x95, 0xac, 0xb5, 0xcf, 0xf3, 0x3c, 0x3f, 0xe8, 0x25, 0x46, 0xa, 0xc5, 0xd, 0x11, 0x59, 0xb3, 0xd5, 0xea, 0x1b, 0xa0, 0x95, 0x54, 0xab, 0x5b, 0x97, 0xd1, 0x22, 0xb2, 0xa6, 0xaa, 0x6d, 0x60, 0xd, 0x58, 0xba, 0xa0, 0x20, 0xc, 0xc3, 0x65, 0xd7, 0x75, 0x23, 0xe0, 0x2e, 0xb0, 0x1, 0x5c, 0xbf, 0xf4, 0x0, 0xbe, 0xba, 0xae, 0x1b, 0x85, 0x61, 0xb8, 0x3c, 0xa0, 0x20, 0x4d, 0xd3, 0x52, 0xb9, 0x5c, 0x6e, 0xc5, 0x71, 0xbc, 0x23, 0x22, 0xd3, 0x61, 0x18, 0xde, 0x2f, 0xb2, 0x27, 0x49, 0xa2, 0xaa, 0xba, 0x53, 0x2e, 0x97, 0x5b, 0x69, 0x9a, 0x96, 0xf2, 0x3c, 0x1f, 0xf0, 0xc0, 0x5a, 0x6b, 0x5f, 0x1, 0x25, 0x86, 0x84, 0xe3, 0x38, 0x9e, 0xb5, 0x76, 0x2e, 0xcf, 0xf3, 0xfd, 0x1, 0x5, 0x22, 0xb2, 0xa1, 0xaa, 0x4b, 0x22, 0x72, 0xad, 0xcb, 0x38, 0xe0, 0x81, 0xaa, 0x7e, 0x0, 0xce, 0x44, 0xe4, 0xbd, 0xaa, 0xbe, 0xbe, 0xa0, 0xa0, 0x52, 0xa9, 0x2c, 0x7a, 0x9e, 0x17, 0x1, 0x3d, 0xe0, 0x55, 0x1e, 0x6c, 0x79, 0x9e, 0x17, 0x55, 0x2a, 0x95, 0xc5, 0x1, 0x5, 0xcd, 0x66, 0x33, 0x30, 0xc6, 0x9c, 0xc6, 0x71, 0xbc, 0x2d, 0x22, 0x8f, 0x87, 0x78, 0xb0, 0x6d, 0x8c, 0x39, 0xed, 0xae, 0x74, 0xdf, 0x83, 0x3a, 0x70, 0x9c, 0x65, 0x59, 0x23, 0x49, 0x92, 0x5, 0x11, 0x9, 0x86, 0x79, 0x20, 0x22, 0x41, 0x92, 0x24, 0xb, 0x59, 0x96, 0x35, 0x80, 0x63, 0xa0, 0x2e, 0x3d, 0xf6, 0xc2, 0x91, 0xdc, 0x0, 0x5c, 0x55, 0x5d, 0xbf, 0x4, 0x9e, 0x3, 0x72, 0xfe, 0xaf, 0xfb, 0xaa, 0xe7, 0x79, 0x1f, 0x8d, 0x31, 0x6d, 0x29, 0x36, 0xf5, 0xce, 0x14, 0xb8, 0x33, 0x44, 0xc4, 0x6f, 0xdf, 0xf7, 0xd7, 0x8d, 0x31, 0xed, 0x5e, 0xe2, 0x1f, 0xb, 0x5c, 0xe2, 0xcb, 0xd, 0x9b, 0x69, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +static const unsigned char icon_grid_layout_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1f, 0xf3, 0xff, 0x61, 0x0, 0x0, 0x5, 0x52, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x22, 0x3f, 0x3e, 0xa, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x35, 0x2e, 0x30, 0x22, 0x3e, 0xa, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x64, 0x63, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x75, 0x72, 0x6c, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x63, 0x2f, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x31, 0x2e, 0x31, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x65, 0x78, 0x69, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x78, 0x69, 0x66, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x2f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x23, 0x22, 0xa, 0x20, 0x20, 0x20, 0x65, 0x78, 0x69, 0x66, 0x3a, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x58, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x36, 0x22, 0xa, 0x20, 0x20, 0x20, 0x65, 0x78, 0x69, 0x66, 0x3a, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x59, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x36, 0x22, 0xa, 0x20, 0x20, 0x20, 0x65, 0x78, 0x69, 0x66, 0x3a, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x70, 0x61, 0x63, 0x65, 0x3d, 0x22, 0x31, 0x22, 0xa, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x57, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x36, 0x22, 0xa, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x36, 0x22, 0xa, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x3d, 0x22, 0x32, 0x22, 0xa, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x58, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x37, 0x32, 0x2e, 0x30, 0x22, 0xa, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x59, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x37, 0x32, 0x2e, 0x30, 0x22, 0xa, 0x20, 0x20, 0x20, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x33, 0x22, 0xa, 0x20, 0x20, 0x20, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x49, 0x43, 0x43, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x3d, 0x22, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2e, 0x31, 0x22, 0xa, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x70, 0x3a, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x44, 0x61, 0x74, 0x65, 0x3d, 0x22, 0x32, 0x30, 0x32, 0x31, 0x2d, 0x30, 0x38, 0x2d, 0x31, 0x35, 0x54, 0x30, 0x39, 0x3a, 0x34, 0x31, 0x3a, 0x34, 0x30, 0x2b, 0x30, 0x32, 0x3a, 0x30, 0x30, 0x22, 0xa, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x70, 0x3a, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x44, 0x61, 0x74, 0x65, 0x3d, 0x22, 0x32, 0x30, 0x32, 0x31, 0x2d, 0x30, 0x38, 0x2d, 0x31, 0x35, 0x54, 0x30, 0x39, 0x3a, 0x34, 0x31, 0x3a, 0x34, 0x30, 0x2b, 0x30, 0x32, 0x3a, 0x30, 0x30, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x64, 0x63, 0x3a, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x20, 0x78, 0x6d, 0x6c, 0x3a, 0x6c, 0x61, 0x6e, 0x67, 0x3d, 0x22, 0x78, 0x2d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3e, 0x47, 0x72, 0x69, 0x64, 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x64, 0x63, 0x3a, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x53, 0x65, 0x71, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x64, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3a, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x3d, 0x22, 0x41, 0x66, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x79, 0x20, 0x44, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x20, 0x31, 0x2e, 0x39, 0x2e, 0x31, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3a, 0x77, 0x68, 0x65, 0x6e, 0x3d, 0x22, 0x32, 0x30, 0x32, 0x31, 0x2d, 0x30, 0x38, 0x2d, 0x31, 0x35, 0x54, 0x30, 0x39, 0x3a, 0x34, 0x31, 0x3a, 0x34, 0x30, 0x2b, 0x30, 0x32, 0x3a, 0x30, 0x30, 0x22, 0x2f, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x53, 0x65, 0x71, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x3e, 0xa, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0xa, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0xa, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0xa, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x72, 0x22, 0x3f, 0x3e, 0x10, 0xfa, 0x51, 0xae, 0x0, 0x0, 0x1, 0x82, 0x69, 0x43, 0x43, 0x50, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2e, 0x31, 0x0, 0x0, 0x28, 0x91, 0x75, 0x91, 0xbf, 0x4b, 0x42, 0x51, 0x14, 0xc7, 0x3f, 0x5a, 0xa1, 0x94, 0x61, 0x50, 0x43, 0x43, 0x83, 0x84, 0x45, 0x83, 0x85, 0x15, 0x84, 0x2d, 0xd, 0x4a, 0xbf, 0xa0, 0x1a, 0xd4, 0x20, 0xab, 0x45, 0x9f, 0xbf, 0x2, 0xb5, 0xc7, 0x7b, 0x4a, 0x48, 0x6b, 0xd0, 0x2a, 0x14, 0x44, 0x2d, 0xfd, 0x1a, 0xea, 0x2f, 0xa8, 0x35, 0x68, 0xe, 0x82, 0xa2, 0x8, 0xa2, 0x2d, 0x68, 0x2e, 0x6a, 0xa9, 0x78, 0x9d, 0xa7, 0x82, 0x11, 0x79, 0x2e, 0xe7, 0x9e, 0xcf, 0xfd, 0xde, 0x7b, 0xe, 0xf7, 0x9e, 0xb, 0xd6, 0x70, 0x46, 0xc9, 0xea, 0x8d, 0x5e, 0xc8, 0xe6, 0xf2, 0x5a, 0x70, 0xd2, 0xef, 0x5a, 0x88, 0x2c, 0xba, 0x6c, 0xcf, 0xd8, 0x71, 0x62, 0xa3, 0xf, 0x5f, 0x54, 0xd1, 0xd5, 0xd9, 0xd0, 0x44, 0x98, 0xba, 0xf6, 0x71, 0x87, 0xc5, 0x8c, 0x37, 0xfd, 0x66, 0xad, 0xfa, 0xe7, 0xfe, 0xb5, 0x96, 0x78, 0x42, 0x57, 0xc0, 0x62, 0x17, 0x1e, 0x53, 0x54, 0x2d, 0x2f, 0x3c, 0x25, 0x3c, 0xb3, 0x96, 0x57, 0x4d, 0xde, 0x16, 0xee, 0x50, 0xd2, 0xd1, 0xb8, 0xf0, 0xa9, 0xb0, 0x47, 0x93, 0xb, 0xa, 0xdf, 0x9a, 0x7a, 0xac, 0xc2, 0x2f, 0x26, 0xa7, 0x2a, 0xfc, 0x65, 0xb2, 0x16, 0xe, 0x6, 0xc0, 0xda, 0x26, 0xec, 0x4a, 0xfd, 0xe2, 0xd8, 0x2f, 0x56, 0xd2, 0x5a, 0x56, 0x58, 0x5e, 0x8e, 0x3b, 0x9b, 0x29, 0x28, 0xd5, 0xfb, 0x98, 0x2f, 0x71, 0x24, 0x72, 0xf3, 0x21, 0x89, 0xdd, 0xe2, 0x5d, 0xe8, 0x4, 0x99, 0xc4, 0x8f, 0x8b, 0x69, 0xc6, 0x9, 0x30, 0xc2, 0x20, 0xa3, 0x32, 0x8f, 0xd0, 0xcf, 0x10, 0x3, 0xb2, 0xa2, 0x4e, 0xbe, 0xb7, 0x9c, 0x3f, 0xc7, 0xaa, 0xe4, 0x2a, 0x32, 0xab, 0x14, 0xd1, 0x58, 0x21, 0x45, 0x9a, 0x3c, 0x1e, 0x51, 0xb, 0x52, 0x3d, 0x21, 0x31, 0x29, 0x7a, 0x42, 0x46, 0x86, 0xa2, 0xd9, 0xff, 0xbf, 0x7d, 0xd5, 0x93, 0xc3, 0x43, 0x95, 0xea, 0xe, 0x3f, 0x34, 0x3d, 0x19, 0xc6, 0x5b, 0xf, 0xd8, 0xb6, 0xe0, 0xbb, 0x64, 0x18, 0x9f, 0x87, 0x86, 0xf1, 0x7d, 0x4, 0xd, 0x8f, 0x70, 0x91, 0xab, 0xe5, 0xaf, 0x1e, 0x80, 0xef, 0x5d, 0xf4, 0x52, 0x4d, 0x73, 0xef, 0x83, 0x73, 0x3, 0xce, 0x2e, 0x6b, 0x5a, 0x6c, 0x7, 0xce, 0x37, 0xa1, 0xf3, 0x41, 0x8d, 0x6a, 0xd1, 0xb2, 0xd4, 0x20, 0x6e, 0x4d, 0x26, 0xe1, 0xf5, 0x4, 0x5a, 0x23, 0xd0, 0x7e, 0xd, 0xcd, 0x4b, 0x95, 0x9e, 0x55, 0xf7, 0x39, 0xbe, 0x87, 0xf0, 0xba, 0x7c, 0xd5, 0x15, 0xec, 0xee, 0x41, 0xaf, 0x9c, 0x77, 0x2e, 0xff, 0x0, 0xa6, 0xc4, 0x68, 0x3, 0x1f, 0xd7, 0x32, 0xd8, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x1, 0x40, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0x9d, 0xd2, 0xbd, 0x4a, 0x5d, 0x41, 0x14, 0x86, 0xe1, 0xe7, 0x84, 0xa3, 0x51, 0x22, 0x82, 0x41, 0x8c, 0x12, 0x2, 0xeb, 0x1a, 0x2c, 0x84, 0x4, 0x52, 0x88, 0xb1, 0xd2, 0x26, 0x76, 0x62, 0x67, 0xeb, 0xd, 0x78, 0x19, 0x56, 0x69, 0x82, 0x62, 0x67, 0x25, 0x82, 0x60, 0x97, 0xc2, 0x52, 0x8, 0x24, 0x95, 0x16, 0xa9, 0x6, 0x8c, 0x18, 0xb5, 0x30, 0x88, 0xa7, 0x90, 0x88, 0x5a, 0xec, 0x39, 0x66, 0xd8, 0x7a, 0x54, 0x5c, 0xb0, 0xd8, 0xcc, 0xbb, 0xe7, 0xfb, 0x58, 0x3f, 0xd3, 0x50, 0x8b, 0x94, 0xd2, 0x6b, 0xcc, 0x60, 0x25, 0x22, 0xae, 0xea, 0xff, 0xeb, 0xd1, 0xbc, 0x47, 0xfc, 0xd, 0xbd, 0x58, 0x7e, 0x4c, 0xc, 0x8d, 0x42, 0x3c, 0x82, 0x9f, 0xe8, 0xc1, 0x26, 0xce, 0x3b, 0x68, 0xae, 0xf1, 0x25, 0x22, 0x76, 0xeb, 0x15, 0xf4, 0xa3, 0xf, 0x97, 0x18, 0xce, 0xdf, 0x4e, 0x6, 0xfd, 0x77, 0x2a, 0xc8, 0x55, 0x4, 0xb6, 0xf1, 0x1b, 0x1f, 0x23, 0xe2, 0xfa, 0x49, 0x2d, 0xa4, 0x94, 0x7a, 0x30, 0x98, 0xd9, 0x5b, 0x4c, 0x63, 0x49, 0x35, 0xb, 0x68, 0xe1, 0x2f, 0x46, 0xf0, 0x22, 0xb3, 0x93, 0x88, 0xb8, 0x68, 0x1f, 0x36, 0xb0, 0x9f, 0x73, 0x7, 0x87, 0xf8, 0x53, 0xb0, 0x23, 0x2c, 0xe0, 0xa0, 0x60, 0xeb, 0xa, 0xb7, 0x1, 0xac, 0x60, 0x34, 0x5f, 0x1a, 0x52, 0xcd, 0x67, 0x16, 0x73, 0xe8, 0xca, 0x6c, 0x3f, 0xdf, 0x59, 0xcd, 0x9a, 0x5b, 0x83, 0x67, 0x47, 0x7b, 0xb, 0xa7, 0x98, 0xcf, 0x9, 0xc7, 0xaa, 0x2d, 0xac, 0xe5, 0xf3, 0xbf, 0xcc, 0xde, 0xe1, 0x47, 0x66, 0x5b, 0xa5, 0xc1, 0x67, 0xff, 0x87, 0x78, 0xa5, 0x9a, 0xc1, 0x1a, 0x5e, 0x65, 0xd6, 0xc2, 0x24, 0x3e, 0xe4, 0x36, 0xe0, 0x84, 0xda, 0x1a, 0x1f, 0x8a, 0x94, 0xd2, 0x6, 0xc6, 0x30, 0x1e, 0x11, 0xbf, 0xda, 0xbc, 0x7c, 0x89, 0xc3, 0x58, 0x44, 0x77, 0x7, 0x8f, 0x2e, 0x4c, 0xa9, 0x1e, 0xd1, 0xa7, 0x88, 0xd8, 0x29, 0x5b, 0xa0, 0x7a, 0xc2, 0xf1, 0x80, 0x41, 0x23, 0xe7, 0x4b, 0xbc, 0x79, 0x6a, 0xe5, 0x65, 0xb, 0x5f, 0x53, 0x4a, 0x67, 0x29, 0xa5, 0xf7, 0x25, 0x6f, 0x76, 0x12, 0xdc, 0x13, 0x7b, 0x98, 0x88, 0x88, 0xef, 0x25, 0xbc, 0x1, 0x6c, 0x4d, 0x56, 0x9e, 0x2a, 0x4e, 0x48, 0xae, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char icon_grid_layout_png[] = { +static const unsigned char icon_grid_minimap_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1f, 0xf3, 0xff, 0x61, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc3, 0x0, 0x0, 0xe, 0xc3, 0x1, 0xc7, 0x6f, 0xa8, 0x64, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x2, 0xd, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0x75, 0x93, 0x31, 0x68, 0x14, 0x51, 0x10, 0x86, 0xbf, 0xd9, 0xd, 0xbb, 0xde, 0x76, 0x82, 0x21, 0xf8, 0xe0, 0xbc, 0x5d, 0x8b, 0x80, 0x69, 0x6c, 0xd2, 0x5a, 0x6a, 0x91, 0xc3, 0xd2, 0x46, 0x22, 0x8, 0x9, 0x89, 0x70, 0x85, 0x10, 0x41, 0xd, 0x24, 0x45, 0xb0, 0xb, 0x68, 0x11, 0x14, 0x24, 0x10, 0x22, 0x62, 0x21, 0x41, 0xe, 0x4b, 0x21, 0xa4, 0xb7, 0x49, 0x17, 0xb1, 0x8, 0xb9, 0xdd, 0xc7, 0x86, 0x33, 0x21, 0xe1, 0x3a, 0x8f, 0x64, 0x61, 0x6f, 0x2c, 0xbc, 0x3b, 0x36, 0xb9, 0xdc, 0xc0, 0x2b, 0xde, 0xcc, 0xfc, 0xf3, 0xff, 0xfc, 0xcc, 0x48, 0xa3, 0xd1, 0x78, 0x20, 0x22, 0x13, 0xbe, 0xef, 0xaf, 0xdf, 0xac, 0xd7, 0x1f, 0xe1, 0x38, 0xd3, 0xa8, 0x2a, 0xf0, 0x45, 0x6a, 0xb5, 0xcf, 0x5c, 0x11, 0xcd, 0x66, 0x33, 0x38, 0x3f, 0x3f, 0x9f, 0x13, 0x91, 0x7d, 0xb1, 0xd6, 0x6e, 0xaa, 0xea, 0xd3, 0xe0, 0xe8, 0xe8, 0xde, 0xe8, 0xee, 0xee, 0x37, 0xc0, 0xe9, 0xf6, 0x75, 0xf0, 0xfd, 0x9, 0x99, 0x9d, 0x6d, 0x15, 0x81, 0x59, 0x96, 0x3d, 0x3, 0x5e, 0x2, 0x63, 0x22, 0xf2, 0x69, 0xa4, 0x57, 0x1c, 0xdd, 0xdb, 0xfb, 0x5b, 0x0, 0x3, 0x38, 0x67, 0x41, 0x30, 0x11, 0xc7, 0xf1, 0x13, 0x0, 0x11, 0x71, 0xb2, 0x2c, 0x7b, 0xd8, 0xad, 0xad, 0x2, 0x6f, 0xb9, 0x0, 0x38, 0x3c, 0xfc, 0x5, 0x9c, 0xf6, 0xff, 0x22, 0x27, 0x27, 0xe3, 0xe3, 0x7f, 0xa, 0x3, 0x67, 0x45, 0xe4, 0xbb, 0xe7, 0x79, 0xb7, 0xc3, 0x30, 0x7c, 0xd7, 0x67, 0xe9, 0xe3, 0x67, 0x66, 0x5c, 0x60, 0x1, 0x50, 0x40, 0x51, 0x7d, 0x71, 0x6b, 0x72, 0xf2, 0x20, 0x8a, 0xa2, 0xf9, 0x28, 0x8a, 0xe6, 0x1, 0x3a, 0x9d, 0xce, 0x4f, 0x63, 0x4c, 0x3b, 0x4d, 0xd3, 0xd2, 0xc0, 0x80, 0x3c, 0xcf, 0xf, 0x92, 0xa9, 0xa9, 0x31, 0x60, 0x5, 0x58, 0x91, 0x5a, 0xed, 0xc7, 0x15, 0xfe, 0x95, 0xac, 0xb5, 0xcf, 0xf3, 0x3c, 0x3f, 0xe8, 0x25, 0x46, 0xa, 0xc5, 0xd, 0x11, 0x59, 0xb3, 0xd5, 0xea, 0x1b, 0xa0, 0x95, 0x54, 0xab, 0x5b, 0x97, 0xd1, 0x22, 0xb2, 0xa6, 0xaa, 0x6d, 0x60, 0xd, 0x58, 0xba, 0xa0, 0x20, 0xc, 0xc3, 0x65, 0xd7, 0x75, 0x23, 0xe0, 0x2e, 0xb0, 0x1, 0x5c, 0xbf, 0xf4, 0x0, 0xbe, 0xba, 0xae, 0x1b, 0x85, 0x61, 0xb8, 0x3c, 0xa0, 0x20, 0x4d, 0xd3, 0x52, 0xb9, 0x5c, 0x6e, 0xc5, 0x71, 0xbc, 0x23, 0x22, 0xd3, 0x61, 0x18, 0xde, 0x2f, 0xb2, 0x27, 0x49, 0xa2, 0xaa, 0xba, 0x53, 0x2e, 0x97, 0x5b, 0x69, 0x9a, 0x96, 0xf2, 0x3c, 0x1f, 0xf0, 0xc0, 0x5a, 0x6b, 0x5f, 0x1, 0x25, 0x86, 0x84, 0xe3, 0x38, 0x9e, 0xb5, 0x76, 0x2e, 0xcf, 0xf3, 0xfd, 0x1, 0x5, 0x22, 0xb2, 0xa1, 0xaa, 0x4b, 0x22, 0x72, 0xad, 0xcb, 0x38, 0xe0, 0x81, 0xaa, 0x7e, 0x0, 0xce, 0x44, 0xe4, 0xbd, 0xaa, 0xbe, 0xbe, 0xa0, 0xa0, 0x52, 0xa9, 0x2c, 0x7a, 0x9e, 0x17, 0x1, 0x3d, 0xe0, 0x55, 0x1e, 0x6c, 0x79, 0x9e, 0x17, 0x55, 0x2a, 0x95, 0xc5, 0x1, 0x5, 0xcd, 0x66, 0x33, 0x30, 0xc6, 0x9c, 0xc6, 0x71, 0xbc, 0x2d, 0x22, 0x8f, 0x87, 0x78, 0xb0, 0x6d, 0x8c, 0x39, 0xed, 0xae, 0x74, 0xdf, 0x83, 0x3a, 0x70, 0x9c, 0x65, 0x59, 0x23, 0x49, 0x92, 0x5, 0x11, 0x9, 0x86, 0x79, 0x20, 0x22, 0x41, 0x92, 0x24, 0xb, 0x59, 0x96, 0x35, 0x80, 0x63, 0xa0, 0x2e, 0x3d, 0xf6, 0xc2, 0x91, 0xdc, 0x0, 0x5c, 0x55, 0x5d, 0xbf, 0x4, 0x9e, 0x3, 0x72, 0xfe, 0xaf, 0xfb, 0xaa, 0xe7, 0x79, 0x1f, 0x8d, 0x31, 0x6d, 0x29, 0x36, 0xf5, 0xce, 0x14, 0xb8, 0x33, 0x44, 0xc4, 0x6f, 0xdf, 0xf7, 0xd7, 0x8d, 0x31, 0xed, 0x5e, 0xe2, 0x1f, 0xb, 0x5c, 0xe2, 0xcb, 0xd, 0x9b, 0x69, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -214,6 +214,14 @@ static const unsigned char icon_zoom_reset_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x33, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0xa, 0x3c, 0xc, 0x7b, 0xf0, 0xff, 0xc1, 0x7f, 0x9c, 0x22, 0xcf, 0x44, 0x1e, 0xbc, 0x84, 0x72, 0xb1, 0x8b, 0x3c, 0x58, 0x5, 0xe4, 0x40, 0xb8, 0x38, 0x45, 0x18, 0x60, 0x5c, 0x84, 0x30, 0x59, 0xa, 0xa0, 0x80, 0x6e, 0xa, 0x86, 0x92, 0x2f, 0x8, 0x3, 0x0, 0x69, 0xc8, 0x86, 0x87, 0x72, 0xca, 0x85, 0x23, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char indeterminate_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x36, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x38, 0x37, 0x40, 0x20, 0x20, 0x24, 0x20, 0x20, 0x24, 0x38, 0x36, 0x40, 0x20, 0x20, 0x25, 0x1e, 0x1e, 0x22, 0x1f, 0x1f, 0x23, 0x20, 0x20, 0x24, 0x22, 0x22, 0x27, 0x23, 0x23, 0x28, 0x25, 0x25, 0x2a, 0xfe, 0xfe, 0xfe, 0x98, 0x4d, 0x2d, 0x9a, 0x0, 0x0, 0x0, 0x12, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x50, 0x66, 0x68, 0xb4, 0xfa, 0xfb, 0xb4, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1c, 0x77, 0x5e, 0x7f, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x85, 0xcf, 0xc1, 0x12, 0x80, 0x20, 0x8, 0x45, 0xd1, 0x4, 0x14, 0xd, 0xc5, 0xfa, 0xff, 0x9f, 0x8d, 0x9c, 0x6c, 0x9a, 0xb4, 0xe9, 0x2e, 0xcf, 0x42, 0x9e, 0xcb, 0x32, 0xe4, 0x0, 0xc9, 0xb7, 0x8, 0xc1, 0x19, 0x40, 0x60, 0xc9, 0x2d, 0xe1, 0x0, 0x6, 0xc8, 0x45, 0x6b, 0x4b, 0xb, 0xa3, 0x1, 0x89, 0x6e, 0x57, 0x2a, 0x64, 0xe0, 0x73, 0xed, 0x50, 0xb3, 0x9f, 0xc3, 0x7e, 0xf7, 0x5, 0x7f, 0x6f, 0xc, 0x67, 0x9f, 0xc3, 0xe2, 0x39, 0xc, 0x52, 0xec, 0xd3, 0xd7, 0x4, 0xb3, 0xcf, 0xbd, 0x3a, 0x0, 0xa0, 0xa2, 0x8, 0xbc, 0xf6, 0x84, 0x3a, 0x9d, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + +static const unsigned char indeterminate_disabled_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x3c, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x6a, 0x69, 0x70, 0x6a, 0x68, 0x70, 0x58, 0x58, 0x5c, 0x58, 0x58, 0x5b, 0x58, 0x58, 0x5b, 0x5c, 0x5c, 0x5f, 0x5a, 0x5a, 0x5e, 0x59, 0x59, 0x5d, 0x58, 0x58, 0x5b, 0x57, 0x57, 0x5a, 0x56, 0x56, 0x59, 0xff, 0x0, 0x0, 0xff, 0xff, 0xff, 0x9e, 0x9e, 0x9e, 0x8c, 0x93, 0x80, 0x95, 0x0, 0x0, 0x0, 0x14, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x50, 0x66, 0x68, 0xb4, 0xb4, 0xfa, 0xfa, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6c, 0xb9, 0x8d, 0x1e, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x85, 0xcf, 0xd1, 0xe, 0x80, 0x20, 0x8, 0x85, 0xe1, 0x4, 0xd4, 0x30, 0x51, 0xb6, 0xde, 0xff, 0x5d, 0x23, 0x97, 0xad, 0xa5, 0xad, 0xff, 0xf2, 0xbb, 0x90, 0xe3, 0xb2, 0xc, 0x39, 0x40, 0xf2, 0x2d, 0x42, 0x70, 0x6, 0x10, 0x58, 0x6b, 0x4b, 0x39, 0x80, 0x1, 0x72, 0x91, 0xdc, 0x92, 0xc2, 0x68, 0x40, 0x2a, 0xdb, 0x95, 0x28, 0x19, 0xf8, 0x9a, 0x3b, 0xe4, 0xea, 0xe7, 0xb0, 0xdf, 0x7d, 0xc1, 0xdf, 0x1b, 0xc3, 0xd9, 0xe7, 0xb0, 0x74, 0xe, 0x83, 0x98, 0xfa, 0xf4, 0x35, 0xc2, 0xec, 0x73, 0xaf, 0xe, 0x57, 0x20, 0x8, 0x2c, 0x1a, 0x56, 0xe5, 0x32, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char line_edit_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xa, 0x4, 0x3, 0x0, 0x0, 0x0, 0x7f, 0x1c, 0xd2, 0x8e, 0x0, 0x0, 0x0, 0x2a, 0x50, 0x4c, 0x54, 0x45, 0x17, 0x16, 0x1a, 0x1d, 0x1c, 0x21, 0x20, 0x1e, 0x24, 0x21, 0x1f, 0x25, 0x1d, 0x1c, 0x21, 0x20, 0x1e, 0x24, 0x1d, 0x1c, 0x21, 0x1d, 0x1c, 0x21, 0x24, 0x22, 0x29, 0x28, 0x26, 0x2d, 0x28, 0x26, 0x2e, 0x2b, 0x2a, 0x31, 0x2c, 0x2a, 0x32, 0xff, 0xff, 0xff, 0xb9, 0x11, 0x56, 0x3e, 0x0, 0x0, 0x0, 0x8, 0x74, 0x52, 0x4e, 0x53, 0x6f, 0xef, 0xf7, 0xf7, 0xf0, 0xf9, 0xf1, 0xee, 0xcf, 0x21, 0xd2, 0xdf, 0x0, 0x0, 0x0, 0x2d, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x60, 0x54, 0x36, 0x36, 0x12, 0x60, 0xf0, 0x98, 0xb5, 0x6a, 0x65, 0xb, 0x43, 0xe4, 0x9e, 0x33, 0xa7, 0xa7, 0x32, 0x58, 0x9d, 0x39, 0x73, 0x66, 0x31, 0x16, 0x12, 0x22, 0xb, 0x52, 0xd9, 0xc6, 0xc0, 0x2, 0xd4, 0x55, 0x0, 0x0, 0xc, 0x14, 0x1a, 0x90, 0x55, 0x1a, 0xec, 0xdb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 896a33e02e..1d2a2ef26c 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -31,6 +31,7 @@ #include "material.h" #include "core/config/engine.h" +#include "core/version.h" #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" @@ -469,7 +470,12 @@ void BaseMaterial3D::_update_shader() { //must create a shader! - String code = "shader_type spatial;\nrender_mode "; + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + String code = vformat( + "// NOTE: Shader automatically converted from " VERSION_NAME " " VERSION_FULL_CONFIG "'s %s.\n\n", + orm ? "ORMMaterial3D" : "StandardMaterial3D"); + + code += "shader_type spatial;\nrender_mode "; switch (blend_mode) { case BLEND_MODE_MIX: code += "blend_mix"; diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index 91569e65d6..0495a9e92c 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -30,6 +30,8 @@ #include "particles_material.h" +#include "core/version.h" + Mutex ParticlesMaterial::material_mutex; SelfList<ParticlesMaterial>::List *ParticlesMaterial::dirty_materials = nullptr; Map<ParticlesMaterial::MaterialKey, ParticlesMaterial::ShaderData> ParticlesMaterial::shader_map; @@ -43,31 +45,31 @@ void ParticlesMaterial::init_shaders() { shader_names->direction = "direction"; shader_names->spread = "spread"; shader_names->flatness = "flatness"; - shader_names->initial_linear_velocity = "initial_linear_velocity"; - shader_names->initial_angle = "initial_angle"; - shader_names->angular_velocity = "angular_velocity"; - shader_names->orbit_velocity = "orbit_velocity"; - shader_names->linear_accel = "linear_accel"; - shader_names->radial_accel = "radial_accel"; - shader_names->tangent_accel = "tangent_accel"; - shader_names->damping = "damping"; - shader_names->scale = "scale"; - shader_names->hue_variation = "hue_variation"; - shader_names->anim_speed = "anim_speed"; - shader_names->anim_offset = "anim_offset"; - - shader_names->initial_linear_velocity_random = "initial_linear_velocity_random"; - shader_names->initial_angle_random = "initial_angle_random"; - shader_names->angular_velocity_random = "angular_velocity_random"; - shader_names->orbit_velocity_random = "orbit_velocity_random"; - shader_names->linear_accel_random = "linear_accel_random"; - shader_names->radial_accel_random = "radial_accel_random"; - shader_names->tangent_accel_random = "tangent_accel_random"; - shader_names->damping_random = "damping_random"; - shader_names->scale_random = "scale_random"; - shader_names->hue_variation_random = "hue_variation_random"; - shader_names->anim_speed_random = "anim_speed_random"; - shader_names->anim_offset_random = "anim_offset_random"; + shader_names->initial_linear_velocity_min = "initial_linear_velocity_min"; + shader_names->initial_angle_min = "initial_angle_min"; + shader_names->angular_velocity_min = "angular_velocity_min"; + shader_names->orbit_velocity_min = "orbit_velocity_min"; + shader_names->linear_accel_min = "linear_accel_min"; + shader_names->radial_accel_min = "radial_accel_min"; + shader_names->tangent_accel_min = "tangent_accel_min"; + shader_names->damping_min = "damping_min"; + shader_names->scale_min = "scale_min"; + shader_names->hue_variation_min = "hue_variation_min"; + shader_names->anim_speed_min = "anim_speed_min"; + shader_names->anim_offset_min = "anim_offset_min"; + + shader_names->initial_linear_velocity_max = "initial_linear_velocity_max"; + shader_names->initial_angle_max = "initial_angle_max"; + shader_names->angular_velocity_max = "angular_velocity_max"; + shader_names->orbit_velocity_max = "orbit_velocity_max"; + shader_names->linear_accel_max = "linear_accel_max"; + shader_names->radial_accel_max = "radial_accel_max"; + shader_names->tangent_accel_max = "tangent_accel_max"; + shader_names->damping_max = "damping_max"; + shader_names->scale_max = "scale_max"; + shader_names->hue_variation_max = "hue_variation_max"; + shader_names->anim_speed_max = "anim_speed_max"; + shader_names->anim_offset_max = "anim_offset_max"; shader_names->angle_texture = "angle_texture"; shader_names->angular_velocity_texture = "angular_velocity_texture"; @@ -141,7 +143,10 @@ void ParticlesMaterial::_update_shader() { //must create a shader! - String code = "shader_type particles;\n"; + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + String code = "// NOTE: Shader automatically converted from " VERSION_NAME " " VERSION_FULL_CONFIG "'s ParticlesMaterial.\n\n"; + + code += "shader_type particles;\n"; if (collision_scale) { code += "render_mode collision_use_scale;\n"; @@ -150,31 +155,31 @@ void ParticlesMaterial::_update_shader() { code += "uniform vec3 direction;\n"; code += "uniform float spread;\n"; code += "uniform float flatness;\n"; - code += "uniform float initial_linear_velocity;\n"; - code += "uniform float initial_angle;\n"; - code += "uniform float angular_velocity;\n"; - code += "uniform float orbit_velocity;\n"; - code += "uniform float linear_accel;\n"; - code += "uniform float radial_accel;\n"; - code += "uniform float tangent_accel;\n"; - code += "uniform float damping;\n"; - code += "uniform float scale;\n"; - code += "uniform float hue_variation;\n"; - code += "uniform float anim_speed;\n"; - code += "uniform float anim_offset;\n"; - - code += "uniform float initial_linear_velocity_random;\n"; - code += "uniform float initial_angle_random;\n"; - code += "uniform float angular_velocity_random;\n"; - code += "uniform float orbit_velocity_random;\n"; - code += "uniform float linear_accel_random;\n"; - code += "uniform float radial_accel_random;\n"; - code += "uniform float tangent_accel_random;\n"; - code += "uniform float damping_random;\n"; - code += "uniform float scale_random;\n"; - code += "uniform float hue_variation_random;\n"; - code += "uniform float anim_speed_random;\n"; - code += "uniform float anim_offset_random;\n"; + code += "uniform float initial_linear_velocity_min;\n"; + code += "uniform float initial_angle_min;\n"; + code += "uniform float angular_velocity_min;\n"; + code += "uniform float orbit_velocity_min;\n"; + code += "uniform float linear_accel_min;\n"; + code += "uniform float radial_accel_min;\n"; + code += "uniform float tangent_accel_min;\n"; + code += "uniform float damping_min;\n"; + code += "uniform float scale_min;\n"; + code += "uniform float hue_variation_min;\n"; + code += "uniform float anim_speed_min;\n"; + code += "uniform float anim_offset_min;\n"; + + code += "uniform float initial_linear_velocity_max;\n"; + code += "uniform float initial_angle_max;\n"; + code += "uniform float angular_velocity_max;\n"; + code += "uniform float orbit_velocity_max;\n"; + code += "uniform float linear_accel_max;\n"; + code += "uniform float radial_accel_max;\n"; + code += "uniform float tangent_accel_max;\n"; + code += "uniform float damping_max;\n"; + code += "uniform float scale_max;\n"; + code += "uniform float hue_variation_max;\n"; + code += "uniform float anim_speed_max;\n"; + code += "uniform float anim_offset_max;\n"; code += "uniform float lifetime_randomness;\n"; switch (emission_shape) { @@ -324,7 +329,7 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) { code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(0.0, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_offset = 0.0;\n"; + code += " float tex_anim_offset = 1.0;\n"; } code += " float spread_rad = spread * degree_to_rad;\n"; @@ -334,7 +339,7 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(0.0, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_velocity = 0.0;\n"; + code += " float tex_linear_velocity = 1.0;\n"; } if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { @@ -342,7 +347,7 @@ void ParticlesMaterial::_update_shader() { code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; code += " angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n"; code += " vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n"; - code += " VELOCITY = rot * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; + code += " VELOCITY = rot * mix(initial_linear_velocity_min,initial_linear_velocity_max, rand_from_seed(alt_seed));\n"; code += " }\n"; } else { @@ -364,16 +369,16 @@ void ParticlesMaterial::_update_shader() { code += " binormal = normalize(binormal);\n"; code += " vec3 normal = cross(binormal, direction_nrm);\n"; code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n"; - code += " VELOCITY = spread_direction * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; + code += " VELOCITY = spread_direction * mix(initial_linear_velocity_min, initial_linear_velocity_max,rand_from_seed(alt_seed));\n"; code += " }\n"; } code += " }\n"; - code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; + code += " float base_angle = (tex_angle) * mix(initial_angle_min, initial_angle_max, angle_rand);\n"; code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle code += " CUSTOM.y = 0.0;\n"; // phase code += " CUSTOM.w = (1.0 - lifetime_randomness * rand_from_seed(alt_seed));\n"; - code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random);\n"; // animation offset (0-1) + code += " CUSTOM.z = (tex_anim_offset) * mix(anim_offset_min, anim_offset_max, anim_offset_rand);\n"; // animation offset (0-1) code += " if (RESTART_POSITION) {\n"; @@ -466,63 +471,63 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_velocity = 0.0;\n"; + code += " float tex_linear_velocity = 1.0;\n"; } if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { code += " float tex_orbit_velocity = textureLod(orbit_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_orbit_velocity = 0.0;\n"; + code += " float tex_orbit_velocity = 1.0;\n"; } } if (tex_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { code += " float tex_angular_velocity = textureLod(angular_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_angular_velocity = 0.0;\n"; + code += " float tex_angular_velocity = 1.0;\n"; } if (tex_parameters[PARAM_LINEAR_ACCEL].is_valid()) { code += " float tex_linear_accel = textureLod(linear_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_accel = 0.0;\n"; + code += " float tex_linear_accel = 1.0;\n"; } if (tex_parameters[PARAM_RADIAL_ACCEL].is_valid()) { code += " float tex_radial_accel = textureLod(radial_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_radial_accel = 0.0;\n"; + code += " float tex_radial_accel = 1.0;\n"; } if (tex_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { code += " float tex_tangent_accel = textureLod(tangent_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_tangent_accel = 0.0;\n"; + code += " float tex_tangent_accel = 1.0;\n"; } if (tex_parameters[PARAM_DAMPING].is_valid()) { code += " float tex_damping = textureLod(damping_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_damping = 0.0;\n"; + code += " float tex_damping = 1.0;\n"; } if (tex_parameters[PARAM_ANGLE].is_valid()) { code += " float tex_angle = textureLod(angle_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_angle = 0.0;\n"; + code += " float tex_angle = 1.0;\n"; } if (tex_parameters[PARAM_ANIM_SPEED].is_valid()) { code += " float tex_anim_speed = textureLod(anim_speed_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_speed = 0.0;\n"; + code += " float tex_anim_speed = 1.0;\n"; } if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) { code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_offset = 0.0;\n"; + code += " float tex_anim_offset = 1.0;\n"; } code += " vec3 force = gravity;\n"; @@ -531,18 +536,19 @@ void ParticlesMaterial::_update_shader() { code += " pos.z = 0.0;\n"; } code += " // apply linear acceleration\n"; - code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * (linear_accel + tex_linear_accel) * mix(1.0, rand_from_seed(alt_seed), linear_accel_random) : vec3(0.0);\n"; + code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * tex_linear_accel * mix(linear_accel_min, linear_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n"; code += " // apply radial acceleration\n"; code += " vec3 org = EMISSION_TRANSFORM[3].xyz;\n"; code += " vec3 diff = pos - org;\n"; - code += " force += length(diff) > 0.0 ? normalize(diff) * (radial_accel + tex_radial_accel) * mix(1.0, rand_from_seed(alt_seed), radial_accel_random) : vec3(0.0);\n"; + code += " force += length(diff) > 0.0 ? normalize(diff) * tex_radial_accel * mix(radial_accel_min, radial_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n"; code += " // apply tangential acceleration;\n"; + code += " float tangent_accel_val = tex_tangent_accel * mix(tangent_accel_min, tangent_accel_max, rand_from_seed(alt_seed))\n;"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; + code += " force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * tangent_accel_val : vec3(0.0);\n"; } else { code += " vec3 crossDiff = cross(normalize(diff), normalize(gravity));\n"; - code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; + code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * tangent_accel_val : vec3(0.0);\n"; } if (attractor_interaction_enabled) { code += " force += ATTRACTOR_FORCE;\n\n"; @@ -552,7 +558,7 @@ void ParticlesMaterial::_update_shader() { code += " VELOCITY += force * DELTA;\n"; code += " // orbit velocity\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random);\n"; + code += " float orbit_amount = tex_orbit_velocity * mix(orbit_velocity_min, orbit_velocity_max, rand_from_seed(alt_seed));\n"; code += " if (orbit_amount != 0.0) {\n"; code += " float ang = orbit_amount * DELTA * pi * 2.0;\n"; code += " mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));\n"; @@ -564,9 +570,10 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { code += " VELOCITY = normalize(VELOCITY) * tex_linear_velocity;\n"; } - code += " if (damping + tex_damping > 0.0) {\n"; + code += " float dmp = mix(damping_min, damping_max, rand_from_seed(alt_seed));\n"; + code += " if (dmp * tex_damping > 0.0) {\n"; code += " float v = length(VELOCITY);\n"; - code += " float damp = (damping + tex_damping) * mix(1.0, rand_from_seed(alt_seed), damping_random);\n"; + code += " float damp = tex_damping * dmp;\n"; code += " v -= damp * DELTA;\n"; code += " if (v < 0.0) {\n"; code += " VELOCITY = vec3(0.0);\n"; @@ -574,26 +581,26 @@ void ParticlesMaterial::_update_shader() { code += " VELOCITY = normalize(VELOCITY) * v;\n"; code += " }\n"; code += " }\n"; - code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; - code += " base_angle += CUSTOM.y * LIFETIME * (angular_velocity + tex_angular_velocity) * mix(1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, angular_velocity_random);\n"; + code += " float base_angle = (tex_angle) * mix(initial_angle_min, initial_angle_max, rand_from_seed(alt_seed));\n"; + code += " base_angle += CUSTOM.y * LIFETIME * (tex_angular_velocity) * mix(angular_velocity_min,angular_velocity_max, rand_from_seed(alt_seed));\n"; code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle - code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random) + CUSTOM.y * (anim_speed + tex_anim_speed) * mix(1.0, rand_from_seed(alt_seed), anim_speed_random);\n"; // angle + code += " CUSTOM.z = (tex_anim_offset) * mix(anim_offset_min, anim_offset_max, rand_from_seed(alt_seed)) + CUSTOM.y * tex_anim_speed * mix(anim_speed_min, anim_speed_max, rand_from_seed(alt_seed));\n"; // angle // apply color // apply hue rotation if (tex_parameters[PARAM_SCALE].is_valid()) { - code += " float tex_scale = textureLod(scale_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " vec3 tex_scale = textureLod(scale_texture, vec2(tv, 0.0), 0.0).rgb;\n"; } else { - code += " float tex_scale = 1.0;\n"; + code += " vec3 tex_scale = vec3(1.0);\n"; } if (tex_parameters[PARAM_HUE_VARIATION].is_valid()) { code += " float tex_hue_variation = textureLod(hue_variation_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_hue_variation = 0.0;\n"; + code += " float tex_hue_variation = 1.0;\n"; } - code += " float hue_rot_angle = (hue_variation + tex_hue_variation) * pi * 2.0 * mix(1.0, hue_rot_rand * 2.0 - 1.0, hue_variation_random);\n"; + code += " float hue_rot_angle = (tex_hue_variation) * pi * 2.0 * mix(hue_variation_min, hue_variation_max, rand_from_seed(alt_seed));\n"; code += " float hue_rot_c = cos(hue_rot_angle);\n"; code += " float hue_rot_s = sin(hue_rot_angle);\n"; code += " mat4 hue_rot_mat = mat4(vec4(0.299, 0.587, 0.114, 0.0),\n"; @@ -655,18 +662,18 @@ void ParticlesMaterial::_update_shader() { } // turn particle by rotation in Y if (particle_flags[PARTICLE_FLAG_ROTATE_Y]) { + code += " vec4 origin = TRANSFORM[3];\n"; code += " TRANSFORM = mat4(vec4(cos(CUSTOM.x), 0.0, -sin(CUSTOM.x), 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(sin(CUSTOM.x), 0.0, cos(CUSTOM.x), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " TRANSFORM[3] = origin;\n"; } } //scale by scale - code += " float base_scale = tex_scale * mix(scale, 1.0, scale_random * scale_rand);\n"; - code += " if (base_scale < 0.000001) {\n"; - code += " base_scale = 0.000001;\n"; - code += " }\n"; + code += " float base_scale = mix(scale_min, scale_max, scale_rand);\n"; + code += " base_scale = sign(base_scale) * max(abs(base_scale), 0.001);\n"; - code += " TRANSFORM[0].xyz *= base_scale;\n"; - code += " TRANSFORM[1].xyz *= base_scale;\n"; - code += " TRANSFORM[2].xyz *= base_scale;\n"; + code += " TRANSFORM[0].xyz *= base_scale * sign(tex_scale.r) * max(abs(tex_scale.r), 0.001);\n"; + code += " TRANSFORM[1].xyz *= base_scale * sign(tex_scale.g) * max(abs(tex_scale.g), 0.001);\n"; + code += " TRANSFORM[2].xyz *= base_scale * sign(tex_scale.b) * max(abs(tex_scale.b), 0.001);\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { code += " VELOCITY.z = 0.0;\n"; code += " TRANSFORM[3].z = 0.0;\n"; @@ -772,110 +779,116 @@ float ParticlesMaterial::get_flatness() const { return flatness; } -void ParticlesMaterial::set_param(Parameter p_param, float p_value) { +void ParticlesMaterial::set_param_min(Parameter p_param, float p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - parameters[p_param] = p_value; + params_min[p_param] = p_value; + if (params_min[p_param] > params_max[p_param]) { + set_param_max(p_param, p_value); + } switch (p_param) { case PARAM_INITIAL_LINEAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity_min, p_value); } break; case PARAM_ANGULAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_min, p_value); } break; case PARAM_ORBIT_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_min, p_value); } break; case PARAM_LINEAR_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_min, p_value); } break; case PARAM_RADIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_min, p_value); } break; case PARAM_TANGENTIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_min, p_value); } break; case PARAM_DAMPING: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_min, p_value); } break; case PARAM_ANGLE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle_min, p_value); } break; case PARAM_SCALE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_min, p_value); } break; case PARAM_HUE_VARIATION: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_min, p_value); } break; case PARAM_ANIM_SPEED: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_min, p_value); } break; case PARAM_ANIM_OFFSET: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_min, p_value); } break; case PARAM_MAX: break; // Can't happen, but silences warning } } -float ParticlesMaterial::get_param(Parameter p_param) const { +float ParticlesMaterial::get_param_min(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return parameters[p_param]; + return params_min[p_param]; } -void ParticlesMaterial::set_param_randomness(Parameter p_param, float p_value) { +void ParticlesMaterial::set_param_max(Parameter p_param, float p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - randomness[p_param] = p_value; + params_max[p_param] = p_value; + if (params_min[p_param] > params_max[p_param]) { + set_param_min(p_param, p_value); + } switch (p_param) { case PARAM_INITIAL_LINEAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity_max, p_value); } break; case PARAM_ANGULAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_max, p_value); } break; case PARAM_ORBIT_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_max, p_value); } break; case PARAM_LINEAR_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_max, p_value); } break; case PARAM_RADIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_max, p_value); } break; case PARAM_TANGENTIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_max, p_value); } break; case PARAM_DAMPING: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_max, p_value); } break; case PARAM_ANGLE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle_max, p_value); } break; case PARAM_SCALE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_max, p_value); } break; case PARAM_HUE_VARIATION: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_max, p_value); } break; case PARAM_ANIM_SPEED: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_max, p_value); } break; case PARAM_ANIM_OFFSET: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_max, p_value); } break; case PARAM_MAX: break; // Can't happen, but silences warning } } -float ParticlesMaterial::get_param_randomness(Parameter p_param) const { +float ParticlesMaterial::get_param_max(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return randomness[p_param]; + return params_max[p_param]; } static void _adjust_curve_range(const Ref<Texture2D> &p_texture, float p_min, float p_max) { @@ -1254,11 +1267,11 @@ void ParticlesMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flatness", "amount"), &ParticlesMaterial::set_flatness); ClassDB::bind_method(D_METHOD("get_flatness"), &ParticlesMaterial::get_flatness); - ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &ParticlesMaterial::set_param); - ClassDB::bind_method(D_METHOD("get_param", "param"), &ParticlesMaterial::get_param); + ClassDB::bind_method(D_METHOD("set_param_min", "param", "value"), &ParticlesMaterial::set_param_min); + ClassDB::bind_method(D_METHOD("get_param_min", "param"), &ParticlesMaterial::get_param_min); - ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &ParticlesMaterial::set_param_randomness); - ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &ParticlesMaterial::get_param_randomness); + ClassDB::bind_method(D_METHOD("set_param_max", "param", "value"), &ParticlesMaterial::set_param_max); + ClassDB::bind_method(D_METHOD("get_param_max", "param"), &ParticlesMaterial::get_param_max); ClassDB::bind_method(D_METHOD("set_param_texture", "param", "texture"), &ParticlesMaterial::set_param_texture); ClassDB::bind_method(D_METHOD("get_param_texture", "param"), &ParticlesMaterial::get_param_texture); @@ -1364,54 +1377,54 @@ void ParticlesMaterial::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY); ADD_GROUP("Angular Velocity", "angular_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_min", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param", "get_param", PARAM_DAMPING); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param", "get_param", PARAM_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE); ADD_GROUP("Scale", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture,CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE); ADD_GROUP("Color", ""); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_color_ramp", "get_color_ramp"); ADD_GROUP("Hue Variation", "hue_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param", "get_param", PARAM_HUE_VARIATION); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_HUE_VARIATION); ADD_GROUP("Animation", "anim_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_OFFSET); ADD_GROUP("Sub Emitter", "sub_emitter_"); @@ -1467,18 +1480,30 @@ ParticlesMaterial::ParticlesMaterial() : set_direction(Vector3(1, 0, 0)); set_spread(45); set_flatness(0); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); + set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_min(PARAM_ANGULAR_VELOCITY, 0); + set_param_min(PARAM_ORBIT_VELOCITY, 0); + set_param_min(PARAM_LINEAR_ACCEL, 0); + set_param_min(PARAM_RADIAL_ACCEL, 0); + set_param_min(PARAM_TANGENTIAL_ACCEL, 0); + set_param_min(PARAM_DAMPING, 0); + set_param_min(PARAM_ANGLE, 0); + set_param_min(PARAM_SCALE, 1); + set_param_min(PARAM_HUE_VARIATION, 0); + set_param_min(PARAM_ANIM_SPEED, 0); + set_param_min(PARAM_ANIM_OFFSET, 0); + set_param_max(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_max(PARAM_ANGULAR_VELOCITY, 0); + set_param_max(PARAM_ORBIT_VELOCITY, 0); + set_param_max(PARAM_LINEAR_ACCEL, 0); + set_param_max(PARAM_RADIAL_ACCEL, 0); + set_param_max(PARAM_TANGENTIAL_ACCEL, 0); + set_param_max(PARAM_DAMPING, 0); + set_param_max(PARAM_ANGLE, 0); + set_param_max(PARAM_SCALE, 1); + set_param_max(PARAM_HUE_VARIATION, 0); + set_param_max(PARAM_ANIM_SPEED, 0); + set_param_max(PARAM_ANIM_OFFSET, 0); set_emission_shape(EMISSION_SHAPE_POINT); set_emission_sphere_radius(1); set_emission_box_extents(Vector3(1, 1, 1)); @@ -1500,10 +1525,6 @@ ParticlesMaterial::ParticlesMaterial() : set_collision_friction(0.0); set_collision_use_scale(false); - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } - for (int i = 0; i < PARTICLE_FLAG_MAX; i++) { particle_flags[i] = false; } diff --git a/scene/resources/particles_material.h b/scene/resources/particles_material.h index ac7a500f73..8ab26aff77 100644 --- a/scene/resources/particles_material.h +++ b/scene/resources/particles_material.h @@ -61,6 +61,7 @@ public: PARAM_MAX }; + // When extending, make sure not to overflow the size of the MaterialKey below. enum ParticleFlags { PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, PARTICLE_FLAG_ROTATE_Y, @@ -68,6 +69,7 @@ public: PARTICLE_FLAG_MAX }; + // When extending, make sure not to overflow the size of the MaterialKey below. enum EmissionShape { EMISSION_SHAPE_POINT, EMISSION_SHAPE_SPHERE, @@ -78,6 +80,7 @@ public: EMISSION_SHAPE_MAX }; + // When extending, make sure not to overflow the size of the MaterialKey below. enum SubEmitterMode { SUB_EMITTER_DISABLED, SUB_EMITTER_CONSTANT, @@ -88,11 +91,13 @@ public: private: union MaterialKey { + // The bit size of the struct must be kept below or equal to 32 bits. + // Consider this when extending ParticleFlags, EmissionShape, or SubEmitterMode. struct { uint32_t texture_mask : 16; uint32_t texture_color : 1; uint32_t particle_flags : 4; - uint32_t emission_shape : 2; + uint32_t emission_shape : 3; uint32_t invalid_key : 1; uint32_t has_emission_color : 1; uint32_t sub_emitter : 2; @@ -149,31 +154,31 @@ private: StringName direction; StringName spread; StringName flatness; - StringName initial_linear_velocity; - StringName initial_angle; - StringName angular_velocity; - StringName orbit_velocity; - StringName linear_accel; - StringName radial_accel; - StringName tangent_accel; - StringName damping; - StringName scale; - StringName hue_variation; - StringName anim_speed; - StringName anim_offset; - - StringName initial_linear_velocity_random; - StringName initial_angle_random; - StringName angular_velocity_random; - StringName orbit_velocity_random; - StringName linear_accel_random; - StringName radial_accel_random; - StringName tangent_accel_random; - StringName damping_random; - StringName scale_random; - StringName hue_variation_random; - StringName anim_speed_random; - StringName anim_offset_random; + StringName initial_linear_velocity_min; + StringName initial_angle_min; + StringName angular_velocity_min; + StringName orbit_velocity_min; + StringName linear_accel_min; + StringName radial_accel_min; + StringName tangent_accel_min; + StringName damping_min; + StringName scale_min; + StringName hue_variation_min; + StringName anim_speed_min; + StringName anim_offset_min; + + StringName initial_linear_velocity_max; + StringName initial_angle_max; + StringName angular_velocity_max; + StringName orbit_velocity_max; + StringName linear_accel_max; + StringName radial_accel_max; + StringName tangent_accel_max; + StringName damping_max; + StringName scale_max; + StringName hue_variation_max; + StringName anim_speed_max; + StringName anim_offset_max; StringName angle_texture; StringName angular_velocity_texture; @@ -225,8 +230,8 @@ private: float spread; float flatness; - float parameters[PARAM_MAX]; - float randomness[PARAM_MAX]; + float params_min[PARAM_MAX]; + float params_max[PARAM_MAX]; Ref<Texture2D> tex_parameters[PARAM_MAX]; Color color; @@ -278,11 +283,11 @@ public: void set_flatness(float p_flatness); float get_flatness() const; - void set_param(Parameter p_param, float p_value); - float get_param(Parameter p_param) const; + void set_param_min(Parameter p_param, float p_value); + float get_param_min(Parameter p_param) const; - void set_param_randomness(Parameter p_param, float p_value); - float get_param_randomness(Parameter p_param) const; + void set_param_max(Parameter p_param, float p_value); + float get_param_max(Parameter p_param) const; void set_param_texture(Parameter p_param, const Ref<Texture2D> &p_texture); Ref<Texture2D> get_param_texture(Parameter p_param) const; diff --git a/scene/resources/skeleton_modification_2d.cpp b/scene/resources/skeleton_modification_2d.cpp index 2d5b42ddf4..e533fb054a 100644 --- a/scene/resources/skeleton_modification_2d.cpp +++ b/scene/resources/skeleton_modification_2d.cpp @@ -44,7 +44,7 @@ /////////////////////////////////////// void SkeletonModification2D::_execute(float p_delta) { - call("_execute", p_delta); + GDVIRTUAL_CALL(_execute, p_delta); if (!enabled) { return; @@ -59,11 +59,11 @@ void SkeletonModification2D::_setup_modification(SkeletonModificationStack2D *p_ WARN_PRINT("Could not setup modification with name " + get_name()); } - call("_setup_modification", p_stack); + GDVIRTUAL_CALL(_setup_modification, Ref<SkeletonModificationStack2D>(p_stack)); } void SkeletonModification2D::_draw_editor_gizmo() { - call("_draw_editor_gizmo"); + GDVIRTUAL_CALL(_draw_editor_gizmo); } void SkeletonModification2D::set_enabled(bool p_enabled) { @@ -228,9 +228,9 @@ bool SkeletonModification2D::get_editor_draw_gizmo() const { } void SkeletonModification2D::_bind_methods() { - BIND_VMETHOD(MethodInfo("_execute", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_setup_modification", PropertyInfo(Variant::OBJECT, "modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D"))); - BIND_VMETHOD(MethodInfo("_draw_editor_gizmo")); + GDVIRTUAL_BIND(_execute, "delta"); + GDVIRTUAL_BIND(_setup_modification, "modification_stack") + GDVIRTUAL_BIND(_draw_editor_gizmo) ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification2D::set_enabled); ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification2D::get_enabled); diff --git a/scene/resources/skeleton_modification_2d.h b/scene/resources/skeleton_modification_2d.h index 18633e55cb..aaddb9136e 100644 --- a/scene/resources/skeleton_modification_2d.h +++ b/scene/resources/skeleton_modification_2d.h @@ -57,6 +57,10 @@ protected: bool _print_execution_error(bool p_condition, String p_message); + GDVIRTUAL1(_execute, double) + GDVIRTUAL1(_setup_modification, Ref<SkeletonModificationStack2D>) + GDVIRTUAL0(_draw_editor_gizmo) + public: virtual void _execute(float _delta); virtual void _setup_modification(SkeletonModificationStack2D *p_stack); diff --git a/scene/resources/skeleton_modification_2d_fabrik.cpp b/scene/resources/skeleton_modification_2d_fabrik.cpp index c03a92b503..6e9429034f 100644 --- a/scene/resources/skeleton_modification_2d_fabrik.cpp +++ b/scene/resources/skeleton_modification_2d_fabrik.cpp @@ -149,15 +149,6 @@ void SkeletonModification2DFABRIK::_execute(float p_delta) { return; } fabrik_transform_chain.write[i] = joint_bone2d_node->get_global_transform(); - - // Apply magnet positions - if (i == 0) { - continue; // The origin cannot use a magnet position! - } else { - Transform2D joint_trans = fabrik_transform_chain[i]; - joint_trans.set_origin(joint_trans.get_origin() + fabrik_data_chain[i].magnet_position); - fabrik_transform_chain.write[i] = joint_trans; - } } Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[fabrik_data_chain.size() - 1].bone2d_node_cache)); @@ -223,6 +214,11 @@ void SkeletonModification2DFABRIK::chain_backwards() { Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone2d_node_cache)); Transform2D final_bone2d_trans = fabrik_transform_chain[final_joint_index]; + // Apply magnet position + if (final_joint_index != 0) { + final_bone2d_trans.set_origin(final_bone2d_trans.get_origin() + fabrik_data_chain[final_joint_index].magnet_position); + } + // Set the rotation of the tip bone final_bone2d_trans = final_bone2d_trans.looking_at(target_global_pose.get_origin()); @@ -245,6 +241,11 @@ void SkeletonModification2DFABRIK::chain_backwards() { Bone2D *current_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); Transform2D current_pose = fabrik_transform_chain[i]; + // Apply magnet position + if (i != 0) { + current_pose.set_origin(current_pose.get_origin() + fabrik_data_chain[i].magnet_position); + } + 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(); Vector2 finish_position = previous_pose.get_origin().lerp(current_pose.get_origin(), length); diff --git a/scene/resources/skeleton_modification_3d.cpp b/scene/resources/skeleton_modification_3d.cpp new file mode 100644 index 0000000000..ee02ede2d5 --- /dev/null +++ b/scene/resources/skeleton_modification_3d.cpp @@ -0,0 +1,162 @@ +/*************************************************************************/ +/* skeleton_modification_3d.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "skeleton_modification_3d.h" +#include "scene/3d/skeleton_3d.h" + +void SkeletonModification3D::_execute(real_t p_delta) { + GDVIRTUAL_CALL(_execute, p_delta); + + if (!enabled) + return; +} + +void SkeletonModification3D::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + if (stack) { + is_setup = true; + } else { + WARN_PRINT("Could not setup modification with name " + this->get_name()); + } + + GDVIRTUAL_CALL(_setup_modification, Ref<SkeletonModificationStack3D>(p_stack)); +} + +void SkeletonModification3D::set_enabled(bool p_enabled) { + enabled = p_enabled; +} + +bool SkeletonModification3D::get_enabled() { + return enabled; +} + +// Helper function. Needed for CCDIK. +real_t SkeletonModification3D::clamp_angle(real_t p_angle, real_t p_min_bound, real_t p_max_bound, bool p_invert) { + // Map to the 0 to 360 range (in radians though) instead of the -180 to 180 range. + if (p_angle < 0) { + p_angle = Math_TAU + p_angle; + } + + // Make min and max in the range of 0 to 360 (in radians), and make sure they are in the right order + if (p_min_bound < 0) { + p_min_bound = Math_TAU + p_min_bound; + } + if (p_max_bound < 0) { + 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; + } + + // 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; + } + } + } + return p_angle; +} + +bool SkeletonModification3D::_print_execution_error(bool p_condition, String p_message) { + // If the modification is not setup, don't bother printing the error + if (!is_setup) { + return p_condition; + } + + if (p_condition && !execution_error_found) { + ERR_PRINT(p_message); + execution_error_found = true; + } + return p_condition; +} + +Ref<SkeletonModificationStack3D> SkeletonModification3D::get_modification_stack() { + return stack; +} + +void SkeletonModification3D::set_is_setup(bool p_is_setup) { + is_setup = p_is_setup; +} + +bool SkeletonModification3D::get_is_setup() const { + return is_setup; +} + +void SkeletonModification3D::set_execution_mode(int p_mode) { + execution_mode = p_mode; +} + +int SkeletonModification3D::get_execution_mode() const { + return execution_mode; +} + +void SkeletonModification3D::_bind_methods() { + GDVIRTUAL_BIND(_execute, "delta"); + GDVIRTUAL_BIND(_setup_modification, "modification_stack") + + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification3D::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification3D::get_enabled); + ClassDB::bind_method(D_METHOD("get_modification_stack"), &SkeletonModification3D::get_modification_stack); + ClassDB::bind_method(D_METHOD("set_is_setup", "is_setup"), &SkeletonModification3D::set_is_setup); + ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModification3D::get_is_setup); + ClassDB::bind_method(D_METHOD("set_execution_mode", "execution_mode"), &SkeletonModification3D::set_execution_mode); + ClassDB::bind_method(D_METHOD("get_execution_mode"), &SkeletonModification3D::get_execution_mode); + ClassDB::bind_method(D_METHOD("clamp_angle", "angle", "min", "max", "invert"), &SkeletonModification3D::clamp_angle); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process, physics_process"), "set_execution_mode", "get_execution_mode"); +} + +SkeletonModification3D::SkeletonModification3D() { + stack = nullptr; + is_setup = false; +} diff --git a/scene/resources/skeleton_modification_3d.h b/scene/resources/skeleton_modification_3d.h new file mode 100644 index 0000000000..fb1f3d33d1 --- /dev/null +++ b/scene/resources/skeleton_modification_3d.h @@ -0,0 +1,79 @@ +/*************************************************************************/ +/* skeleton_modification_3d.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. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATION3D_H +#define SKELETONMODIFICATION3D_H + +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_stack_3d.h" + +class SkeletonModificationStack3D; + +class SkeletonModification3D : public Resource { + GDCLASS(SkeletonModification3D, Resource); + friend class Skeleton3D; + friend class SkeletonModificationStack3D; + +protected: + static void _bind_methods(); + + SkeletonModificationStack3D *stack; + int execution_mode = 0; // 0 = process + + bool enabled = true; + bool is_setup = false; + bool execution_error_found = false; + + bool _print_execution_error(bool p_condition, String p_message); + + GDVIRTUAL1(_execute, double) + GDVIRTUAL1(_setup_modification, Ref<SkeletonModificationStack3D>) + +public: + virtual void _execute(real_t p_delta); + virtual void _setup_modification(SkeletonModificationStack3D *p_stack); + + real_t clamp_angle(real_t p_angle, real_t p_min_bound, real_t p_max_bound, bool p_invert); + + void set_enabled(bool p_enabled); + bool get_enabled(); + + void set_execution_mode(int p_mode); + int get_execution_mode() const; + + Ref<SkeletonModificationStack3D> get_modification_stack(); + + void set_is_setup(bool p_setup); + bool get_is_setup() const; + + SkeletonModification3D(); +}; + +#endif // SKELETONMODIFICATION3D_H diff --git a/scene/resources/skeleton_modification_3d_ccdik.cpp b/scene/resources/skeleton_modification_3d_ccdik.cpp new file mode 100644 index 0000000000..6409022563 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_ccdik.cpp @@ -0,0 +1,474 @@ +/*************************************************************************/ +/* skeleton_modification_3d_ccdik.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_ccdik.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DCCDIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int ccdik_data_size = ccdik_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, ccdik_data_size, false); + + if (what == "bone_name") { + set_ccdik_joint_bone_name(which, p_value); + } else if (what == "bone_index") { + set_ccdik_joint_bone_index(which, p_value); + } else if (what == "ccdik_axis") { + set_ccdik_joint_ccdik_axis(which, p_value); + } else if (what == "enable_joint_constraint") { + set_ccdik_joint_enable_constraint(which, p_value); + } else if (what == "joint_constraint_angle_min") { + set_ccdik_joint_constraint_angle_min(which, Math::deg2rad(real_t(p_value))); + } else if (what == "joint_constraint_angle_max") { + set_ccdik_joint_constraint_angle_max(which, Math::deg2rad(real_t(p_value))); + } else if (what == "joint_constraint_angles_invert") { + set_ccdik_joint_constraint_invert(which, p_value); + } + return true; + } + return true; +} + +bool SkeletonModification3DCCDIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + const int ccdik_data_size = ccdik_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, ccdik_data_size, false); + + if (what == "bone_name") { + r_ret = get_ccdik_joint_bone_name(which); + } else if (what == "bone_index") { + r_ret = get_ccdik_joint_bone_index(which); + } else if (what == "ccdik_axis") { + r_ret = get_ccdik_joint_ccdik_axis(which); + } else if (what == "enable_joint_constraint") { + r_ret = get_ccdik_joint_enable_constraint(which); + } else if (what == "joint_constraint_angle_min") { + r_ret = Math::rad2deg(get_ccdik_joint_constraint_angle_min(which)); + } else if (what == "joint_constraint_angle_max") { + r_ret = Math::rad2deg(get_ccdik_joint_constraint_angle_max(which)); + } else if (what == "joint_constraint_angles_invert") { + r_ret = get_ccdik_joint_constraint_invert(which); + } + return true; + } + return true; +} + +void SkeletonModification3DCCDIK::_get_property_list(List<PropertyInfo> *p_list) const { + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + + p_list->push_back(PropertyInfo(Variant::INT, base_string + "ccdik_axis", + PROPERTY_HINT_ENUM, "X Axis, Y Axis, Z Axis", PROPERTY_USAGE_DEFAULT)); + + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "enable_joint_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (ccdik_data_chain[i].enable_constraint) { + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "joint_constraint_angle_min", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "joint_constraint_angle_max", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "joint_constraint_angles_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + } +} + +void SkeletonModification3DCCDIK::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + _print_execution_error(true, "Target cache is out of date. Attempting to update"); + update_target_cache(); + return; + } + if (tip_node_cache.is_null()) { + _print_execution_error(true, "Tip cache is out of date. Attempting to update"); + update_tip_cache(); + return; + } + + // Reset the local bone overrides for CCDIK affected nodes + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + stack->skeleton->set_bone_local_pose_override(ccdik_data_chain[i].bone_idx, + stack->skeleton->get_bone_local_pose_override(ccdik_data_chain[i].bone_idx), + 0.0, false); + } + + Node3D *node_target = Object::cast_to<Node3D>(ObjectDB::get_instance(target_node_cache)); + Node3D *node_tip = Object::cast_to<Node3D>(ObjectDB::get_instance(tip_node_cache)); + + if (_print_execution_error(!node_target || !node_target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { + return; + } + if (_print_execution_error(!node_tip || !node_tip->is_inside_tree(), "Tip node is not in the scene tree. Cannot execute modification!")) { + return; + } + + if (use_high_quality_solve) { + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + for (uint32_t j = i; j < ccdik_data_chain.size(); j++) { + _execute_ccdik_joint(j, node_target, node_tip); + } + } + } else { + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + _execute_ccdik_joint(i, node_target, node_tip); + } + } + + execution_error_found = false; +} + +void SkeletonModification3DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node3D *p_target, Node3D *p_tip) { + CCDIK_Joint_Data ccdik_data = ccdik_data_chain[p_joint_idx]; + + if (_print_execution_error(ccdik_data.bone_idx < 0 || ccdik_data.bone_idx > stack->skeleton->get_bone_count(), + "CCDIK joint: bone index for joint" + itos(p_joint_idx) + " not found. Cannot execute modification!")) { + return; + } + + Transform3D bone_trans = stack->skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, stack->skeleton->get_bone_global_pose(ccdik_data.bone_idx)); + Transform3D tip_trans = stack->skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, stack->skeleton->world_transform_to_global_pose(p_tip->get_global_transform())); + Transform3D target_trans = stack->skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, stack->skeleton->world_transform_to_global_pose(p_target->get_global_transform())); + + if (tip_trans.origin.distance_to(target_trans.origin) <= 0.01) { + return; + } + + // Inspired (and very loosely based on) by the CCDIK algorithm made by Zalo on GitHub (https://github.com/zalo/MathUtilities) + // Convert the 3D position to a 2D position so we can use Atan2 (via the angle function) + // to know how much rotation we need on the given axis to place the tip at the target. + Vector2 tip_pos_2d; + Vector2 target_pos_2d; + if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_X) { + tip_pos_2d = Vector2(tip_trans.origin.y, tip_trans.origin.z); + target_pos_2d = Vector2(target_trans.origin.y, target_trans.origin.z); + bone_trans.basis.rotate_local(Vector3(1, 0, 0), target_pos_2d.angle() - tip_pos_2d.angle()); + } else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Y) { + tip_pos_2d = Vector2(tip_trans.origin.z, tip_trans.origin.x); + target_pos_2d = Vector2(target_trans.origin.z, target_trans.origin.x); + bone_trans.basis.rotate_local(Vector3(0, 1, 0), target_pos_2d.angle() - tip_pos_2d.angle()); + } else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Z) { + tip_pos_2d = Vector2(tip_trans.origin.x, tip_trans.origin.y); + target_pos_2d = Vector2(target_trans.origin.x, target_trans.origin.y); + bone_trans.basis.rotate_local(Vector3(0, 0, 1), target_pos_2d.angle() - tip_pos_2d.angle()); + } else { + // Should never happen, but... + ERR_FAIL_MSG("CCDIK joint: Unknown axis vector passed for joint" + itos(p_joint_idx) + ". Cannot execute modification!"); + } + + if (ccdik_data.enable_constraint) { + Vector3 rotation_axis; + real_t rotation_angle; + bone_trans.basis.get_axis_angle(rotation_axis, rotation_angle); + + // Note: When the axis has a negative direction, the angle is OVER 180 degrees and therefore we need to account for this + // when constraining. + if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_X) { + if (rotation_axis.x < 0) { + rotation_angle += Math_PI; + rotation_axis = Vector3(1, 0, 0); + } + } else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Y) { + if (rotation_axis.y < 0) { + rotation_angle += Math_PI; + rotation_axis = Vector3(0, 1, 0); + } + } else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Z) { + if (rotation_axis.z < 0) { + rotation_angle += Math_PI; + rotation_axis = Vector3(0, 0, 1); + } + } else { + // Should never happen, but... + ERR_FAIL_MSG("CCDIK joint: Unknown axis vector passed for joint" + itos(p_joint_idx) + ". Cannot execute modification!"); + } + rotation_angle = clamp_angle(rotation_angle, ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angles_invert); + + bone_trans.basis.set_axis_angle(rotation_axis, rotation_angle); + } + + stack->skeleton->set_bone_local_pose_override(ccdik_data.bone_idx, bone_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(ccdik_data.bone_idx); +} + +void SkeletonModification3DCCDIK::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + if (stack != nullptr) { + is_setup = true; + execution_error_found = false; + update_target_cache(); + update_tip_cache(); + } +} + +void SkeletonModification3DCCDIK::update_target_cache() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in scene tree!"); + target_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DCCDIK::update_tip_cache() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update tip cache: modification is not properly setup!"); + return; + } + + tip_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(tip_node)) { + Node *node = stack->skeleton->get_node(tip_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update tip cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update tip cache: node is not in scene tree!"); + tip_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DCCDIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification3DCCDIK::get_target_node() const { + return target_node; +} + +void SkeletonModification3DCCDIK::set_tip_node(const NodePath &p_tip_node) { + tip_node = p_tip_node; + update_tip_cache(); +} + +NodePath SkeletonModification3DCCDIK::get_tip_node() const { + return tip_node; +} + +void SkeletonModification3DCCDIK::set_use_high_quality_solve(bool p_high_quality) { + use_high_quality_solve = p_high_quality; +} + +bool SkeletonModification3DCCDIK::get_use_high_quality_solve() const { + return use_high_quality_solve; +} + +// CCDIK joint data functions +String SkeletonModification3DCCDIK::get_ccdik_joint_bone_name(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, String()); + return ccdik_data_chain[p_joint_idx].bone_name; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_bone_name(int p_joint_idx, String p_bone_name) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ccdik_data_chain[p_joint_idx].bone_name = p_bone_name; + + if (stack) { + if (stack->skeleton) { + ccdik_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(p_bone_name); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +int SkeletonModification3DCCDIK::get_ccdik_joint_bone_index(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return ccdik_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + ccdik_data_chain[p_joint_idx].bone_idx = p_bone_idx; + + if (stack) { + if (stack->skeleton) { + ccdik_data_chain[p_joint_idx].bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +int SkeletonModification3DCCDIK::get_ccdik_joint_ccdik_axis(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return ccdik_data_chain[p_joint_idx].ccdik_axis; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_ccdik_axis(int p_joint_idx, int p_axis) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ERR_FAIL_COND_MSG(p_axis < 0, "CCDIK axis is out of range: The axis mode is too low!"); + ccdik_data_chain[p_joint_idx].ccdik_axis = p_axis; + notify_property_list_changed(); +} + +bool SkeletonModification3DCCDIK::get_ccdik_joint_enable_constraint(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return ccdik_data_chain[p_joint_idx].enable_constraint; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_enable) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ccdik_data_chain[p_joint_idx].enable_constraint = p_enable; + notify_property_list_changed(); +} + +real_t SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_min(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return ccdik_data_chain[p_joint_idx].constraint_angle_min; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_min(int p_joint_idx, real_t p_angle_min) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ccdik_data_chain[p_joint_idx].constraint_angle_min = p_angle_min; +} + +real_t SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_max(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return ccdik_data_chain[p_joint_idx].constraint_angle_max; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_max(int p_joint_idx, real_t p_angle_max) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ccdik_data_chain[p_joint_idx].constraint_angle_max = p_angle_max; +} + +bool SkeletonModification3DCCDIK::get_ccdik_joint_constraint_invert(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return ccdik_data_chain[p_joint_idx].constraint_angles_invert; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_constraint_invert(int p_joint_idx, bool p_invert) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ccdik_data_chain[p_joint_idx].constraint_angles_invert = p_invert; +} + +int SkeletonModification3DCCDIK::get_ccdik_data_chain_length() { + return ccdik_data_chain.size(); +} +void SkeletonModification3DCCDIK::set_ccdik_data_chain_length(int p_length) { + ERR_FAIL_COND(p_length < 0); + ccdik_data_chain.resize(p_length); + execution_error_found = false; + notify_property_list_changed(); +} + +void SkeletonModification3DCCDIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DCCDIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DCCDIK::get_target_node); + + ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification3DCCDIK::set_tip_node); + ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification3DCCDIK::get_tip_node); + + ClassDB::bind_method(D_METHOD("set_use_high_quality_solve", "high_quality_solve"), &SkeletonModification3DCCDIK::set_use_high_quality_solve); + ClassDB::bind_method(D_METHOD("get_use_high_quality_solve"), &SkeletonModification3DCCDIK::get_use_high_quality_solve); + + // CCDIK joint data functions + ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_name", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_bone_name); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_name", "joint_idx", "bone_name"), &SkeletonModification3DCCDIK::set_ccdik_joint_bone_name); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_index", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_index", "joint_idx", "bone_index"), &SkeletonModification3DCCDIK::set_ccdik_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_ccdik_axis", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_ccdik_axis); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_ccdik_axis", "joint_idx", "axis"), &SkeletonModification3DCCDIK::set_ccdik_joint_ccdik_axis); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_enable_joint_constraint", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_enable_joint_constraint", "joint_idx", "enable"), &SkeletonModification3DCCDIK::set_ccdik_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_min", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_min", "joint_idx", "min_angle"), &SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_max", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_max", "joint_idx", "max_angle"), &SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_invert", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_constraint_invert); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_invert", "joint_idx", "invert"), &SkeletonModification3DCCDIK::set_ccdik_joint_constraint_invert); + + ClassDB::bind_method(D_METHOD("set_ccdik_data_chain_length", "length"), &SkeletonModification3DCCDIK::set_ccdik_data_chain_length); + ClassDB::bind_method(D_METHOD("get_ccdik_data_chain_length"), &SkeletonModification3DCCDIK::get_ccdik_data_chain_length); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_tip_node", "get_tip_node"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "high_quality_solve", PROPERTY_HINT_NONE, ""), "set_use_high_quality_solve", "get_use_high_quality_solve"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "ccdik_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_ccdik_data_chain_length", "get_ccdik_data_chain_length"); +} + +SkeletonModification3DCCDIK::SkeletonModification3DCCDIK() { + stack = nullptr; + is_setup = false; + enabled = true; +} + +SkeletonModification3DCCDIK::~SkeletonModification3DCCDIK() { +} diff --git a/scene/resources/skeleton_modification_3d_ccdik.h b/scene/resources/skeleton_modification_3d_ccdik.h new file mode 100644 index 0000000000..e7537cc5b0 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_ccdik.h @@ -0,0 +1,114 @@ +/*************************************************************************/ +/* skeleton_modification_3d_ccdik.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. */ +/*************************************************************************/ + +#include "core/templates/local_vector.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DCCDIK_H +#define SKELETONMODIFICATION3DCCDIK_H + +class SkeletonModification3DCCDIK : public SkeletonModification3D { + GDCLASS(SkeletonModification3DCCDIK, SkeletonModification3D); + +private: + enum CCDIK_Axes { + AXIS_X, + AXIS_Y, + AXIS_Z + }; + + struct CCDIK_Joint_Data { + String bone_name = ""; + int bone_idx = -1; + int ccdik_axis = 0; + + bool enable_constraint = false; + real_t constraint_angle_min = 0; + real_t constraint_angle_max = (2.0 * Math_PI); + bool constraint_angles_invert = false; + }; + + LocalVector<CCDIK_Joint_Data> ccdik_data_chain; + NodePath target_node; + ObjectID target_node_cache; + + NodePath tip_node; + ObjectID tip_node_cache; + + bool use_high_quality_solve = true; + + void update_target_cache(); + void update_tip_cache(); + + void _execute_ccdik_joint(int p_joint_idx, Node3D *p_target, Node3D *p_tip); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_tip_node(const NodePath &p_tip_node); + NodePath get_tip_node() const; + + void set_use_high_quality_solve(bool p_solve); + bool get_use_high_quality_solve() const; + + String get_ccdik_joint_bone_name(int p_joint_idx) const; + void set_ccdik_joint_bone_name(int p_joint_idx, String p_bone_name); + int get_ccdik_joint_bone_index(int p_joint_idx) const; + void set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx); + int get_ccdik_joint_ccdik_axis(int p_joint_idx) const; + void set_ccdik_joint_ccdik_axis(int p_joint_idx, int p_axis); + bool get_ccdik_joint_enable_constraint(int p_joint_idx) const; + void set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_enable); + real_t get_ccdik_joint_constraint_angle_min(int p_joint_idx) const; + void set_ccdik_joint_constraint_angle_min(int p_joint_idx, real_t p_angle_min); + real_t get_ccdik_joint_constraint_angle_max(int p_joint_idx) const; + void set_ccdik_joint_constraint_angle_max(int p_joint_idx, real_t p_angle_max); + bool get_ccdik_joint_constraint_invert(int p_joint_idx) const; + void set_ccdik_joint_constraint_invert(int p_joint_idx, bool p_invert); + + int get_ccdik_data_chain_length(); + void set_ccdik_data_chain_length(int p_new_length); + + SkeletonModification3DCCDIK(); + ~SkeletonModification3DCCDIK(); +}; + +#endif //SKELETONMODIFICATION3DCCDIK_H diff --git a/scene/resources/skeleton_modification_3d_fabrik.cpp b/scene/resources/skeleton_modification_3d_fabrik.cpp new file mode 100644 index 0000000000..69f75eb7b5 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_fabrik.cpp @@ -0,0 +1,628 @@ +/*************************************************************************/ +/* skeleton_modification_3d_fabrik.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_fabrik.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DFABRIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int fabrik_data_size = fabrik_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, fabrik_data_size, false); + + if (what == "bone_name") { + set_fabrik_joint_bone_name(which, p_value); + } else if (what == "bone_index") { + set_fabrik_joint_bone_index(which, p_value); + } else if (what == "length") { + set_fabrik_joint_length(which, p_value); + } else if (what == "magnet_position") { + set_fabrik_joint_magnet(which, p_value); + } else if (what == "auto_calculate_length") { + set_fabrik_joint_auto_calculate_length(which, p_value); + } else if (what == "use_tip_node") { + set_fabrik_joint_use_tip_node(which, p_value); + } else if (what == "tip_node") { + set_fabrik_joint_tip_node(which, p_value); + } else if (what == "use_target_basis") { + set_fabrik_joint_use_target_basis(which, p_value); + } else if (what == "roll") { + set_fabrik_joint_roll(which, Math::deg2rad(real_t(p_value))); + } + return true; + } + return true; +} + +bool SkeletonModification3DFABRIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + const int fabrik_data_size = fabrik_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, fabrik_data_size, false); + + if (what == "bone_name") { + r_ret = get_fabrik_joint_bone_name(which); + } else if (what == "bone_index") { + r_ret = get_fabrik_joint_bone_index(which); + } else if (what == "length") { + r_ret = get_fabrik_joint_length(which); + } else if (what == "magnet_position") { + r_ret = get_fabrik_joint_magnet(which); + } else if (what == "auto_calculate_length") { + r_ret = get_fabrik_joint_auto_calculate_length(which); + } else if (what == "use_tip_node") { + r_ret = get_fabrik_joint_use_tip_node(which); + } else if (what == "tip_node") { + r_ret = get_fabrik_joint_tip_node(which); + } else if (what == "use_target_basis") { + r_ret = get_fabrik_joint_use_target_basis(which); + } else if (what == "roll") { + r_ret = Math::rad2deg(get_fabrik_joint_roll(which)); + } + return true; + } + return true; +} + +void SkeletonModification3DFABRIK::_get_property_list(List<PropertyInfo> *p_list) const { + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "roll", PROPERTY_HINT_RANGE, "-360,360,0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "auto_calculate_length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + + if (!fabrik_data_chain[i].auto_calculate_length) { + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } else { + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_tip_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (fabrik_data_chain[i].use_tip_node) { + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "tip_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT)); + } + } + + // Cannot apply magnet to the origin of the chain, as it will not do anything. + if (i > 0) { + p_list->push_back(PropertyInfo(Variant::VECTOR3, base_string + "magnet_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + // Only give the override basis option on the last bone in the chain, so only include it for the last bone. + if (i == fabrik_data_chain.size() - 1) { + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_target_basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + } +} + +void SkeletonModification3DFABRIK::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + _print_execution_error(true, "Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + + if (_print_execution_error(fabrik_data_chain.size() <= 1, "FABRIK requires at least two joints to operate. Cannot execute modification!")) { + return; + } + + Node3D *node_target = Object::cast_to<Node3D>(ObjectDB::get_instance(target_node_cache)); + if (_print_execution_error(!node_target || !node_target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { + return; + } + + // Verify that all joints have a valid bone ID, and that all bone lengths are zero or more + // Also, while we are here, apply magnet positions. + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + if (_print_execution_error(fabrik_data_chain[i].bone_idx < 0, "FABRIK Joint " + itos(i) + " has an invalid bone ID. Cannot execute!")) { + return; + } + + if (fabrik_data_chain[i].length < 0 && fabrik_data_chain[i].auto_calculate_length) { + fabrik_joint_auto_calculate_length(i); + } + if (_print_execution_error(fabrik_data_chain[i].length < 0, "FABRIK Joint " + itos(i) + " has an invalid joint length. Cannot execute!")) { + return; + } + + Transform3D local_pose_override = stack->skeleton->get_bone_local_pose_override(fabrik_data_chain[i].bone_idx); + + // Apply magnet positions: + if (stack->skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx) >= 0) { + int parent_bone_idx = stack->skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx); + Transform3D conversion_transform = (stack->skeleton->get_bone_global_pose(parent_bone_idx) * stack->skeleton->get_bone_rest(parent_bone_idx)); + local_pose_override.origin += conversion_transform.basis.xform_inv(fabrik_data_chain[i].magnet_position); + } else { + local_pose_override.origin += fabrik_data_chain[i].magnet_position; + } + + stack->skeleton->set_bone_local_pose_override(fabrik_data_chain[i].bone_idx, local_pose_override, stack->strength, true); + } + + target_global_pose = stack->skeleton->world_transform_to_global_pose(node_target->get_global_transform()); + origin_global_pose = stack->skeleton->local_pose_to_global_pose( + fabrik_data_chain[0].bone_idx, stack->skeleton->get_bone_local_pose_override(fabrik_data_chain[0].bone_idx)); + + final_joint_idx = fabrik_data_chain.size() - 1; + real_t target_distance = stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[final_joint_idx].bone_idx, target_global_pose).origin.length(); + chain_iterations = 0; + + while (target_distance > chain_tolerance) { + chain_backwards(); + chain_forwards(); + + // update the target distance + target_distance = stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[final_joint_idx].bone_idx, target_global_pose).origin.length(); + + // update chain iterations + chain_iterations += 1; + if (chain_iterations >= chain_max_iterations) { + break; + } + } + chain_apply(); + + execution_error_found = false; +} + +void SkeletonModification3DFABRIK::chain_backwards() { + int final_bone_idx = fabrik_data_chain[final_joint_idx].bone_idx; + Transform3D final_joint_trans = stack->skeleton->local_pose_to_global_pose(final_bone_idx, stack->skeleton->get_bone_local_pose_override(final_bone_idx)); + + // Get the direction the final bone is facing in. + stack->skeleton->update_bone_rest_forward_vector(final_bone_idx); + Transform3D final_bone_direction_trans = final_joint_trans.looking_at(target_global_pose.origin, Vector3(0, 1, 0)); + final_bone_direction_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(final_bone_idx, final_bone_direction_trans.basis); + Vector3 direction = final_bone_direction_trans.basis.xform(stack->skeleton->get_bone_axis_forward_vector(final_bone_idx)).normalized(); + + // If set to override, then use the target's Basis rather than the bone's + if (fabrik_data_chain[final_joint_idx].use_target_basis) { + direction = target_global_pose.basis.xform(stack->skeleton->get_bone_axis_forward_vector(final_bone_idx)).normalized(); + } + + // set the position of the final joint to the target position + final_joint_trans.origin = target_global_pose.origin - (direction * fabrik_data_chain[final_joint_idx].length); + final_joint_trans = stack->skeleton->global_pose_to_local_pose(final_bone_idx, final_joint_trans); + stack->skeleton->set_bone_local_pose_override(final_bone_idx, final_joint_trans, stack->strength, true); + + // for all other joints, move them towards the target + int i = final_joint_idx; + while (i >= 1) { + int next_bone_idx = fabrik_data_chain[i].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)); + i -= 1; + 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(); + current_trans.origin = next_bone_trans.origin.lerp(current_trans.origin, length); + + // Apply it back to the skeleton + stack->skeleton->set_bone_local_pose_override(current_bone_idx, stack->skeleton->global_pose_to_local_pose(current_bone_idx, current_trans), stack->strength, true); + } +} + +void SkeletonModification3DFABRIK::chain_forwards() { + // Set root at the initial position. + int origin_bone_idx = fabrik_data_chain[0].bone_idx; + Transform3D root_transform = stack->skeleton->local_pose_to_global_pose(origin_bone_idx, stack->skeleton->get_bone_local_pose_override(origin_bone_idx)); + root_transform.origin = origin_global_pose.origin; + stack->skeleton->set_bone_local_pose_override(origin_bone_idx, stack->skeleton->global_pose_to_local_pose(origin_bone_idx, root_transform), stack->strength, true); + + for (uint32_t i = 0; i < fabrik_data_chain.size() - 1; i++) { + 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)); + 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(); + next_bone_trans.origin = current_trans.origin.lerp(next_bone_trans.origin, length); + + // Apply it back to the skeleton + stack->skeleton->set_bone_local_pose_override(next_bone_idx, stack->skeleton->global_pose_to_local_pose(next_bone_idx, next_bone_trans), stack->strength, true); + } +} + +void SkeletonModification3DFABRIK::chain_apply() { + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + int current_bone_idx = fabrik_data_chain[i].bone_idx; + Transform3D current_trans = stack->skeleton->get_bone_local_pose_override(current_bone_idx); + current_trans = stack->skeleton->local_pose_to_global_pose(current_bone_idx, current_trans); + + // If this is the last bone in the chain... + if (i == fabrik_data_chain.size() - 1) { + if (fabrik_data_chain[i].use_target_basis == false) { // Point to target... + // Get the forward direction that the basis is facing in right now. + stack->skeleton->update_bone_rest_forward_vector(current_bone_idx); + Vector3 forward_vector = stack->skeleton->get_bone_axis_forward_vector(current_bone_idx); + // Rotate the bone towards the target: + current_trans.basis.rotate_to_align(forward_vector, current_trans.origin.direction_to(target_global_pose.origin)); + current_trans.basis.rotate_local(forward_vector, fabrik_data_chain[i].roll); + } else { // Use the target's Basis... + current_trans.basis = target_global_pose.basis.orthonormalized().scaled(current_trans.basis.get_scale()); + } + } else { // every other bone in the chain... + int next_bone_idx = fabrik_data_chain[i + 1].bone_idx; + Transform3D next_trans = stack->skeleton->local_pose_to_global_pose(next_bone_idx, stack->skeleton->get_bone_local_pose_override(next_bone_idx)); + + // Get the forward direction that the basis is facing in right now. + stack->skeleton->update_bone_rest_forward_vector(current_bone_idx); + Vector3 forward_vector = stack->skeleton->get_bone_axis_forward_vector(current_bone_idx); + // Rotate the bone towards the next bone in the chain: + current_trans.basis.rotate_to_align(forward_vector, current_trans.origin.direction_to(next_trans.origin)); + current_trans.basis.rotate_local(forward_vector, fabrik_data_chain[i].roll); + } + current_trans = stack->skeleton->global_pose_to_local_pose(current_bone_idx, current_trans); + current_trans.origin = Vector3(0, 0, 0); + stack->skeleton->set_bone_local_pose_override(current_bone_idx, current_trans, stack->strength, true); + } + + // Update all the bones so the next modification has up-to-date data. + stack->skeleton->force_update_all_bone_transforms(); +} + +void SkeletonModification3DFABRIK::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + if (stack != nullptr) { + is_setup = true; + execution_error_found = false; + update_target_cache(); + + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + update_joint_tip_cache(i); + } + } +} + +void SkeletonModification3DFABRIK::update_target_cache() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); + return; + } + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree() && target_node.is_empty() == false) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DFABRIK::update_joint_tip_cache(int p_joint_idx) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_MSG(p_joint_idx, bone_chain_size, "FABRIK joint not found"); + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update tip cache: modification is not properly setup!"); + return; + } + fabrik_data_chain[p_joint_idx].tip_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree() && fabrik_data_chain[p_joint_idx].tip_node.is_empty() == false) { + if (stack->skeleton->has_node(fabrik_data_chain[p_joint_idx].tip_node)) { + Node *node = stack->skeleton->get_node(fabrik_data_chain[p_joint_idx].tip_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update tip cache for joint " + itos(p_joint_idx) + ": node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update tip cache for joint " + itos(p_joint_idx) + ": node is not in scene tree!"); + fabrik_data_chain[p_joint_idx].tip_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DFABRIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification3DFABRIK::get_target_node() const { + return target_node; +} + +int SkeletonModification3DFABRIK::get_fabrik_data_chain_length() { + return fabrik_data_chain.size(); +} + +void SkeletonModification3DFABRIK::set_fabrik_data_chain_length(int p_length) { + ERR_FAIL_COND(p_length < 0); + fabrik_data_chain.resize(p_length); + execution_error_found = false; + notify_property_list_changed(); +} + +real_t SkeletonModification3DFABRIK::get_chain_tolerance() { + return chain_tolerance; +} + +void SkeletonModification3DFABRIK::set_chain_tolerance(real_t p_tolerance) { + ERR_FAIL_COND_MSG(p_tolerance <= 0, "FABRIK chain tolerance must be more than zero!"); + chain_tolerance = p_tolerance; +} + +int SkeletonModification3DFABRIK::get_chain_max_iterations() { + return chain_max_iterations; +} +void SkeletonModification3DFABRIK::set_chain_max_iterations(int p_iterations) { + ERR_FAIL_COND_MSG(p_iterations <= 0, "FABRIK chain iterations must be at least one. Set enabled to false to disable the FABRIK chain."); + chain_max_iterations = p_iterations; +} + +// FABRIK joint data functions +String SkeletonModification3DFABRIK::get_fabrik_joint_bone_name(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, String()); + return fabrik_data_chain[p_joint_idx].bone_name; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_bone_name(int p_joint_idx, String p_bone_name) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].bone_name = p_bone_name; + + if (stack) { + if (stack->skeleton) { + fabrik_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(p_bone_name); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +int SkeletonModification3DFABRIK::get_fabrik_joint_bone_index(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return fabrik_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + fabrik_data_chain[p_joint_idx].bone_idx = p_bone_idx; + + if (stack) { + if (stack->skeleton) { + fabrik_data_chain[p_joint_idx].bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +real_t SkeletonModification3DFABRIK::get_fabrik_joint_length(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return fabrik_data_chain[p_joint_idx].length; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_length(int p_joint_idx, real_t p_bone_length) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ERR_FAIL_COND_MSG(p_bone_length < 0, "FABRIK joint length cannot be less than zero!"); + + if (!is_setup) { + fabrik_data_chain[p_joint_idx].length = p_bone_length; + return; + } + + if (fabrik_data_chain[p_joint_idx].auto_calculate_length) { + WARN_PRINT("FABRIK Length not set: auto calculate length is enabled for this joint!"); + fabrik_joint_auto_calculate_length(p_joint_idx); + } else { + fabrik_data_chain[p_joint_idx].length = p_bone_length; + } + + execution_error_found = false; +} + +Vector3 SkeletonModification3DFABRIK::get_fabrik_joint_magnet(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, Vector3()); + return fabrik_data_chain[p_joint_idx].magnet_position; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_magnet(int p_joint_idx, Vector3 p_magnet) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].magnet_position = p_magnet; +} + +bool SkeletonModification3DFABRIK::get_fabrik_joint_auto_calculate_length(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return fabrik_data_chain[p_joint_idx].auto_calculate_length; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_auto_calculate_length(int p_joint_idx, bool p_auto_calculate) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].auto_calculate_length = p_auto_calculate; + fabrik_joint_auto_calculate_length(p_joint_idx); + notify_property_list_changed(); +} + +void SkeletonModification3DFABRIK::fabrik_joint_auto_calculate_length(int p_joint_idx) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + if (!fabrik_data_chain[p_joint_idx].auto_calculate_length) { + return; + } + + if (!stack || !stack->skeleton || !is_setup) { + _print_execution_error(true, "Cannot auto calculate joint length: modification is not properly setup!"); + return; + } + ERR_FAIL_INDEX_MSG(fabrik_data_chain[p_joint_idx].bone_idx, stack->skeleton->get_bone_count(), + "Bone for joint " + itos(p_joint_idx) + " is not set or points to an unknown bone!"); + + if (fabrik_data_chain[p_joint_idx].use_tip_node) { // Use the tip node to update joint length. + + update_joint_tip_cache(p_joint_idx); + + Node3D *tip_node = Object::cast_to<Node3D>(ObjectDB::get_instance(fabrik_data_chain[p_joint_idx].tip_node_cache)); + ERR_FAIL_COND_MSG(!tip_node, "Tip node for joint " + itos(p_joint_idx) + "is not a Node3D-based node. Cannot calculate length..."); + ERR_FAIL_COND_MSG(!tip_node->is_inside_tree(), "Tip node for joint " + itos(p_joint_idx) + "is not in the scene tree. Cannot calculate length..."); + + Transform3D node_trans = tip_node->get_global_transform(); + node_trans = stack->skeleton->world_transform_to_global_pose(node_trans); + node_trans = stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, node_trans); + fabrik_data_chain[p_joint_idx].length = node_trans.origin.length(); + } else { // Use child bone(s) to update joint length, if possible + Vector<int> bone_children = stack->skeleton->get_bone_children(fabrik_data_chain[p_joint_idx].bone_idx); + if (bone_children.size() <= 0) { + ERR_FAIL_MSG("Cannot calculate length for joint " + itos(p_joint_idx) + "joint uses leaf bone. \nPlease manually set the bone length or use a tip node!"); + return; + } + + real_t final_length = 0; + for (int i = 0; i < bone_children.size(); i++) { + Transform3D child_transform = stack->skeleton->get_bone_global_pose(bone_children[i]); + final_length += stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, child_transform).origin.length(); + } + fabrik_data_chain[p_joint_idx].length = final_length / bone_children.size(); + } + execution_error_found = false; + notify_property_list_changed(); +} + +bool SkeletonModification3DFABRIK::get_fabrik_joint_use_tip_node(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return fabrik_data_chain[p_joint_idx].use_tip_node; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_use_tip_node(int p_joint_idx, bool p_use_tip_node) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].use_tip_node = p_use_tip_node; + notify_property_list_changed(); +} + +NodePath SkeletonModification3DFABRIK::get_fabrik_joint_tip_node(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, NodePath()); + return fabrik_data_chain[p_joint_idx].tip_node; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_tip_node(int p_joint_idx, NodePath p_tip_node) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].tip_node = p_tip_node; + update_joint_tip_cache(p_joint_idx); +} + +bool SkeletonModification3DFABRIK::get_fabrik_joint_use_target_basis(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return fabrik_data_chain[p_joint_idx].use_target_basis; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_use_target_basis(int p_joint_idx, bool p_use_target_basis) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].use_target_basis = p_use_target_basis; +} + +real_t SkeletonModification3DFABRIK::get_fabrik_joint_roll(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, 0.0); + return fabrik_data_chain[p_joint_idx].roll; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_roll(int p_joint_idx, real_t p_roll) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].roll = p_roll; +} + +void SkeletonModification3DFABRIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DFABRIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DFABRIK::get_target_node); + ClassDB::bind_method(D_METHOD("set_fabrik_data_chain_length", "length"), &SkeletonModification3DFABRIK::set_fabrik_data_chain_length); + ClassDB::bind_method(D_METHOD("get_fabrik_data_chain_length"), &SkeletonModification3DFABRIK::get_fabrik_data_chain_length); + ClassDB::bind_method(D_METHOD("set_chain_tolerance", "tolerance"), &SkeletonModification3DFABRIK::set_chain_tolerance); + ClassDB::bind_method(D_METHOD("get_chain_tolerance"), &SkeletonModification3DFABRIK::get_chain_tolerance); + ClassDB::bind_method(D_METHOD("set_chain_max_iterations", "max_iterations"), &SkeletonModification3DFABRIK::set_chain_max_iterations); + ClassDB::bind_method(D_METHOD("get_chain_max_iterations"), &SkeletonModification3DFABRIK::get_chain_max_iterations); + + // FABRIK joint data functions + ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_name", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_bone_name); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_name", "joint_idx", "bone_name"), &SkeletonModification3DFABRIK::set_fabrik_joint_bone_name); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_index", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_index", "joint_idx", "bone_index"), &SkeletonModification3DFABRIK::set_fabrik_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_length", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_length); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_length", "joint_idx", "length"), &SkeletonModification3DFABRIK::set_fabrik_joint_length); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_magnet", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_magnet); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_magnet", "joint_idx", "magnet_position"), &SkeletonModification3DFABRIK::set_fabrik_joint_magnet); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_auto_calculate_length", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_auto_calculate_length); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_auto_calculate_length", "joint_idx", "auto_calculate_length"), &SkeletonModification3DFABRIK::set_fabrik_joint_auto_calculate_length); + ClassDB::bind_method(D_METHOD("fabrik_joint_auto_calculate_length", "joint_idx"), &SkeletonModification3DFABRIK::fabrik_joint_auto_calculate_length); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_tip_node", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_use_tip_node); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_tip_node", "joint_idx", "use_tip_node"), &SkeletonModification3DFABRIK::set_fabrik_joint_use_tip_node); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_tip_node", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_tip_node); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_tip_node", "joint_idx", "tip_node"), &SkeletonModification3DFABRIK::set_fabrik_joint_tip_node); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_target_basis", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_use_target_basis); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_target_basis", "joint_idx", "use_target_basis"), &SkeletonModification3DFABRIK::set_fabrik_joint_use_target_basis); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fabrik_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_fabrik_data_chain_length", "get_fabrik_data_chain_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "chain_tolerance", PROPERTY_HINT_RANGE, "0,100,0.001"), "set_chain_tolerance", "get_chain_tolerance"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "chain_max_iterations", PROPERTY_HINT_RANGE, "1,50,1"), "set_chain_max_iterations", "get_chain_max_iterations"); +} + +SkeletonModification3DFABRIK::SkeletonModification3DFABRIK() { + stack = nullptr; + is_setup = false; + enabled = true; +} + +SkeletonModification3DFABRIK::~SkeletonModification3DFABRIK() { +} diff --git a/scene/resources/skeleton_modification_3d_fabrik.h b/scene/resources/skeleton_modification_3d_fabrik.h new file mode 100644 index 0000000000..9b5da883d4 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_fabrik.h @@ -0,0 +1,122 @@ +/*************************************************************************/ +/* skeleton_modification_3d_fabrik.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. */ +/*************************************************************************/ + +#include "core/templates/local_vector.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DFABRIK_H +#define SKELETONMODIFICATION3DFABRIK_H + +class SkeletonModification3DFABRIK : public SkeletonModification3D { + GDCLASS(SkeletonModification3DFABRIK, SkeletonModification3D); + +private: + struct FABRIK_Joint_Data { + String bone_name = ""; + int bone_idx = -1; + real_t length = -1; + Vector3 magnet_position = Vector3(0, 0, 0); + + bool auto_calculate_length = true; + bool use_tip_node = false; + NodePath tip_node = NodePath(); + ObjectID tip_node_cache; + + bool use_target_basis = false; + real_t roll = 0; + }; + + LocalVector<FABRIK_Joint_Data> fabrik_data_chain; + NodePath target_node; + ObjectID target_node_cache; + + real_t chain_tolerance = 0.01; + int chain_max_iterations = 10; + int chain_iterations = 0; + + void update_target_cache(); + void update_joint_tip_cache(int p_joint_idx); + + int final_joint_idx = 0; + Transform3D target_global_pose = Transform3D(); + Transform3D origin_global_pose = Transform3D(); + + void chain_backwards(); + void chain_forwards(); + void chain_apply(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + int get_fabrik_data_chain_length(); + void set_fabrik_data_chain_length(int p_new_length); + + real_t get_chain_tolerance(); + void set_chain_tolerance(real_t p_tolerance); + + int get_chain_max_iterations(); + void set_chain_max_iterations(int p_iterations); + + String get_fabrik_joint_bone_name(int p_joint_idx) const; + void set_fabrik_joint_bone_name(int p_joint_idx, String p_bone_name); + int get_fabrik_joint_bone_index(int p_joint_idx) const; + void set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx); + real_t get_fabrik_joint_length(int p_joint_idx) const; + void set_fabrik_joint_length(int p_joint_idx, real_t p_bone_length); + Vector3 get_fabrik_joint_magnet(int p_joint_idx) const; + void set_fabrik_joint_magnet(int p_joint_idx, Vector3 p_magnet); + bool get_fabrik_joint_auto_calculate_length(int p_joint_idx) const; + void set_fabrik_joint_auto_calculate_length(int p_joint_idx, bool p_auto_calculate); + void fabrik_joint_auto_calculate_length(int p_joint_idx); + bool get_fabrik_joint_use_tip_node(int p_joint_idx) const; + void set_fabrik_joint_use_tip_node(int p_joint_idx, bool p_use_tip_node); + NodePath get_fabrik_joint_tip_node(int p_joint_idx) const; + void set_fabrik_joint_tip_node(int p_joint_idx, NodePath p_tip_node); + bool get_fabrik_joint_use_target_basis(int p_joint_idx) const; + void set_fabrik_joint_use_target_basis(int p_joint_idx, bool p_use_basis); + real_t get_fabrik_joint_roll(int p_joint_idx) const; + void set_fabrik_joint_roll(int p_joint_idx, real_t p_roll); + + SkeletonModification3DFABRIK(); + ~SkeletonModification3DFABRIK(); +}; + +#endif //SKELETONMODIFICATION3DFABRIK_H diff --git a/scene/resources/skeleton_modification_3d_jiggle.cpp b/scene/resources/skeleton_modification_3d_jiggle.cpp new file mode 100644 index 0000000000..1fb7dad2ad --- /dev/null +++ b/scene/resources/skeleton_modification_3d_jiggle.cpp @@ -0,0 +1,573 @@ +/*************************************************************************/ +/* skeleton_modification_3d_jiggle.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_jiggle.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DJiggle::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + const int jiggle_size = jiggle_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, jiggle_size, false); + + if (what == "bone_name") { + set_jiggle_joint_bone_name(which, p_value); + } else if (what == "bone_index") { + set_jiggle_joint_bone_index(which, p_value); + } else if (what == "override_defaults") { + set_jiggle_joint_override(which, p_value); + } else if (what == "stiffness") { + set_jiggle_joint_stiffness(which, p_value); + } else if (what == "mass") { + set_jiggle_joint_mass(which, p_value); + } else if (what == "damping") { + set_jiggle_joint_damping(which, p_value); + } else if (what == "use_gravity") { + set_jiggle_joint_use_gravity(which, p_value); + } else if (what == "gravity") { + set_jiggle_joint_gravity(which, p_value); + } else if (what == "roll") { + set_jiggle_joint_roll(which, Math::deg2rad(real_t(p_value))); + } + return true; + } else { + if (path == "use_colliders") { + set_use_colliders(p_value); + } else if (path == "collision_mask") { + set_collision_mask(p_value); + } + return true; + } + return true; +} + +bool SkeletonModification3DJiggle::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + const int jiggle_size = jiggle_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, jiggle_size, false); + + if (what == "bone_name") { + r_ret = get_jiggle_joint_bone_name(which); + } else if (what == "bone_index") { + r_ret = get_jiggle_joint_bone_index(which); + } else if (what == "override_defaults") { + r_ret = get_jiggle_joint_override(which); + } else if (what == "stiffness") { + r_ret = get_jiggle_joint_stiffness(which); + } else if (what == "mass") { + r_ret = get_jiggle_joint_mass(which); + } else if (what == "damping") { + r_ret = get_jiggle_joint_damping(which); + } else if (what == "use_gravity") { + r_ret = get_jiggle_joint_use_gravity(which); + } else if (what == "gravity") { + r_ret = get_jiggle_joint_gravity(which); + } else if (what == "roll") { + r_ret = Math::rad2deg(get_jiggle_joint_roll(which)); + } + return true; + } else { + if (path == "use_colliders") { + r_ret = get_use_colliders(); + } else if (path == "collision_mask") { + r_ret = get_collision_mask(); + } + return true; + } + return true; +} + +void SkeletonModification3DJiggle::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "use_colliders", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (use_colliders) { + p_list->push_back(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS, "", PROPERTY_USAGE_DEFAULT)); + } + + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "roll", PROPERTY_HINT_RANGE, "-360,360,0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "override_defaults", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + + if (jiggle_data_chain[i].override_defaults) { + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "stiffness", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "mass", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (jiggle_data_chain[i].use_gravity) { + p_list->push_back(PropertyInfo(Variant::VECTOR3, base_string + "gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + } + } +} + +void SkeletonModification3DJiggle::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + if (target_node_cache.is_null()) { + _print_execution_error(true, "Target cache is out of date. Attempting to update..."); + update_cache(); + return; + } + Node3D *target = Object::cast_to<Node3D>(ObjectDB::get_instance(target_node_cache)); + _print_execution_error(!target || !target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!"); + + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + _execute_jiggle_joint(i, target, p_delta); + } + + execution_error_found = false; +} + +void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Node3D *p_target, real_t p_delta) { + // Adopted from: https://wiki.unity3d.com/index.php/JiggleBone + // With modifications by TwistedTwigleg. + + if (jiggle_data_chain[p_joint_idx].bone_idx <= -2) { + jiggle_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(jiggle_data_chain[p_joint_idx].bone_name); + } + if (_print_execution_error( + jiggle_data_chain[p_joint_idx].bone_idx < 0 || jiggle_data_chain[p_joint_idx].bone_idx > stack->skeleton->get_bone_count(), + "Jiggle joint " + itos(p_joint_idx) + " bone index is invald. Cannot execute modification!")) { + return; + } + + Transform3D new_bone_trans = stack->skeleton->local_pose_to_global_pose(jiggle_data_chain[p_joint_idx].bone_idx, stack->skeleton->get_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx)); + Vector3 target_position = stack->skeleton->world_transform_to_global_pose(p_target->get_global_transform()).origin; + + jiggle_data_chain[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta; + + if (jiggle_data_chain[p_joint_idx].use_gravity) { + Vector3 gravity_to_apply = new_bone_trans.basis.inverse().xform(jiggle_data_chain[p_joint_idx].gravity); + jiggle_data_chain[p_joint_idx].force += gravity_to_apply * p_delta; + } + + jiggle_data_chain[p_joint_idx].acceleration = jiggle_data_chain[p_joint_idx].force / jiggle_data_chain[p_joint_idx].mass; + jiggle_data_chain[p_joint_idx].velocity += jiggle_data_chain[p_joint_idx].acceleration * (1 - jiggle_data_chain[p_joint_idx].damping); + + jiggle_data_chain[p_joint_idx].dynamic_position += jiggle_data_chain[p_joint_idx].velocity + jiggle_data_chain[p_joint_idx].force; + jiggle_data_chain[p_joint_idx].dynamic_position += new_bone_trans.origin - jiggle_data_chain[p_joint_idx].last_position; + jiggle_data_chain[p_joint_idx].last_position = new_bone_trans.origin; + + // Collision detection/response + if (use_colliders) { + if (execution_mode == SkeletonModificationStack3D::EXECUTION_MODE::execution_mode_physics_process) { + Ref<World3D> world_3d = stack->skeleton->get_world_3d(); + ERR_FAIL_COND(world_3d.is_null()); + PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space()); + PhysicsDirectSpaceState3D::RayResult ray_result; + + // Convert to world transforms, which is what the physics server needs + Transform3D new_bone_trans_world = stack->skeleton->global_pose_to_world_transform(new_bone_trans); + Transform3D dynamic_position_world = stack->skeleton->global_pose_to_world_transform(Transform3D(Basis(), jiggle_data_chain[p_joint_idx].dynamic_position)); + + bool ray_hit = space_state->intersect_ray(new_bone_trans_world.origin, dynamic_position_world.get_origin(), + ray_result, Set<RID>(), collision_mask); + + if (ray_hit) { + jiggle_data_chain[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position; + jiggle_data_chain[p_joint_idx].acceleration = Vector3(0, 0, 0); + jiggle_data_chain[p_joint_idx].velocity = Vector3(0, 0, 0); + } else { + jiggle_data_chain[p_joint_idx].last_noncollision_position = jiggle_data_chain[p_joint_idx].dynamic_position; + } + + } else { + WARN_PRINT_ONCE("Jiggle modifier: You cannot detect colliders without the stack mode being set to _physics_process!"); + } + } + + // Get the forward direction that the basis is facing in right now. + stack->skeleton->update_bone_rest_forward_vector(jiggle_data_chain[p_joint_idx].bone_idx); + Vector3 forward_vector = stack->skeleton->get_bone_axis_forward_vector(jiggle_data_chain[p_joint_idx].bone_idx); + + // Rotate the bone using the dynamic position! + new_bone_trans.basis.rotate_to_align(forward_vector, new_bone_trans.origin.direction_to(jiggle_data_chain[p_joint_idx].dynamic_position)); + + // Roll + new_bone_trans.basis.rotate_local(forward_vector, jiggle_data_chain[p_joint_idx].roll); + + new_bone_trans = stack->skeleton->global_pose_to_local_pose(jiggle_data_chain[p_joint_idx].bone_idx, new_bone_trans); + stack->skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, new_bone_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(jiggle_data_chain[p_joint_idx].bone_idx); +} + +void SkeletonModification3DJiggle::_update_jiggle_joint_data() { + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + if (!jiggle_data_chain[i].override_defaults) { + set_jiggle_joint_stiffness(i, stiffness); + set_jiggle_joint_mass(i, mass); + set_jiggle_joint_damping(i, damping); + set_jiggle_joint_use_gravity(i, use_gravity); + set_jiggle_joint_gravity(i, gravity); + } + } +} + +void SkeletonModification3DJiggle::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + + if (stack) { + is_setup = true; + execution_error_found = false; + + if (stack->skeleton) { + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + int bone_idx = jiggle_data_chain[i].bone_idx; + if (bone_idx > 0 && bone_idx < stack->skeleton->get_bone_count()) { + jiggle_data_chain[i].dynamic_position = stack->skeleton->local_pose_to_global_pose(bone_idx, stack->skeleton->get_bone_local_pose_override(bone_idx)).origin; + } + } + } + + update_cache(); + } +} + +void SkeletonModification3DJiggle::update_cache() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DJiggle::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_cache(); +} + +NodePath SkeletonModification3DJiggle::get_target_node() const { + return target_node; +} + +void SkeletonModification3DJiggle::set_stiffness(real_t p_stiffness) { + ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); + stiffness = p_stiffness; + _update_jiggle_joint_data(); +} + +real_t SkeletonModification3DJiggle::get_stiffness() const { + return stiffness; +} + +void SkeletonModification3DJiggle::set_mass(real_t p_mass) { + ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); + mass = p_mass; + _update_jiggle_joint_data(); +} + +real_t SkeletonModification3DJiggle::get_mass() const { + return mass; +} + +void SkeletonModification3DJiggle::set_damping(real_t p_damping) { + ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); + ERR_FAIL_COND_MSG(p_damping > 1, "Damping cannot be more than one!"); + damping = p_damping; + _update_jiggle_joint_data(); +} + +real_t SkeletonModification3DJiggle::get_damping() const { + return damping; +} + +void SkeletonModification3DJiggle::set_use_gravity(bool p_use_gravity) { + use_gravity = p_use_gravity; + _update_jiggle_joint_data(); +} + +bool SkeletonModification3DJiggle::get_use_gravity() const { + return use_gravity; +} + +void SkeletonModification3DJiggle::set_gravity(Vector3 p_gravity) { + gravity = p_gravity; + _update_jiggle_joint_data(); +} + +Vector3 SkeletonModification3DJiggle::get_gravity() const { + return gravity; +} + +void SkeletonModification3DJiggle::set_use_colliders(bool p_use_collider) { + use_colliders = p_use_collider; + notify_property_list_changed(); +} + +bool SkeletonModification3DJiggle::get_use_colliders() const { + return use_colliders; +} + +void SkeletonModification3DJiggle::set_collision_mask(int p_mask) { + collision_mask = p_mask; +} + +int SkeletonModification3DJiggle::get_collision_mask() const { + return collision_mask; +} + +// Jiggle joint data functions +int SkeletonModification3DJiggle::get_jiggle_data_chain_length() { + return jiggle_data_chain.size(); +} + +void SkeletonModification3DJiggle::set_jiggle_data_chain_length(int p_length) { + ERR_FAIL_COND(p_length < 0); + jiggle_data_chain.resize(p_length); + execution_error_found = false; + notify_property_list_changed(); +} + +void SkeletonModification3DJiggle::set_jiggle_joint_bone_name(int p_joint_idx, String p_name) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + + jiggle_data_chain[p_joint_idx].bone_name = p_name; + if (stack && stack->skeleton) { + jiggle_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(p_name); + } + execution_error_found = false; + notify_property_list_changed(); +} + +String SkeletonModification3DJiggle::get_jiggle_joint_bone_name(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, ""); + return jiggle_data_chain[p_joint_idx].bone_name; +} + +int SkeletonModification3DJiggle::get_jiggle_joint_bone_index(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return jiggle_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + jiggle_data_chain[p_joint_idx].bone_idx = p_bone_idx; + + if (stack) { + if (stack->skeleton) { + jiggle_data_chain[p_joint_idx].bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +void SkeletonModification3DJiggle::set_jiggle_joint_override(int p_joint_idx, bool p_override) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].override_defaults = p_override; + _update_jiggle_joint_data(); + notify_property_list_changed(); +} + +bool SkeletonModification3DJiggle::get_jiggle_joint_override(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return jiggle_data_chain[p_joint_idx].override_defaults; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_stiffness(int p_joint_idx, real_t p_stiffness) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].stiffness = p_stiffness; +} + +real_t SkeletonModification3DJiggle::get_jiggle_joint_stiffness(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return jiggle_data_chain[p_joint_idx].stiffness; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_mass(int p_joint_idx, real_t p_mass) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].mass = p_mass; +} + +real_t SkeletonModification3DJiggle::get_jiggle_joint_mass(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return jiggle_data_chain[p_joint_idx].mass; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_damping(int p_joint_idx, real_t p_damping) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].damping = p_damping; +} + +real_t SkeletonModification3DJiggle::get_jiggle_joint_damping(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return jiggle_data_chain[p_joint_idx].damping; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].use_gravity = p_use_gravity; + notify_property_list_changed(); +} + +bool SkeletonModification3DJiggle::get_jiggle_joint_use_gravity(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return jiggle_data_chain[p_joint_idx].use_gravity; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_gravity(int p_joint_idx, Vector3 p_gravity) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].gravity = p_gravity; +} + +Vector3 SkeletonModification3DJiggle::get_jiggle_joint_gravity(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, Vector3(0, 0, 0)); + return jiggle_data_chain[p_joint_idx].gravity; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_roll(int p_joint_idx, real_t p_roll) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].roll = p_roll; +} + +real_t SkeletonModification3DJiggle::get_jiggle_joint_roll(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, 0.0); + return jiggle_data_chain[p_joint_idx].roll; +} + +void SkeletonModification3DJiggle::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DJiggle::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DJiggle::get_target_node); + + ClassDB::bind_method(D_METHOD("set_jiggle_data_chain_length", "length"), &SkeletonModification3DJiggle::set_jiggle_data_chain_length); + ClassDB::bind_method(D_METHOD("get_jiggle_data_chain_length"), &SkeletonModification3DJiggle::get_jiggle_data_chain_length); + + ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &SkeletonModification3DJiggle::set_stiffness); + ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification3DJiggle::get_stiffness); + ClassDB::bind_method(D_METHOD("set_mass", "mass"), &SkeletonModification3DJiggle::set_mass); + ClassDB::bind_method(D_METHOD("get_mass"), &SkeletonModification3DJiggle::get_mass); + ClassDB::bind_method(D_METHOD("set_damping", "damping"), &SkeletonModification3DJiggle::set_damping); + ClassDB::bind_method(D_METHOD("get_damping"), &SkeletonModification3DJiggle::get_damping); + ClassDB::bind_method(D_METHOD("set_use_gravity", "use_gravity"), &SkeletonModification3DJiggle::set_use_gravity); + ClassDB::bind_method(D_METHOD("get_use_gravity"), &SkeletonModification3DJiggle::get_use_gravity); + ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &SkeletonModification3DJiggle::set_gravity); + ClassDB::bind_method(D_METHOD("get_gravity"), &SkeletonModification3DJiggle::get_gravity); + + ClassDB::bind_method(D_METHOD("set_use_colliders", "use_colliders"), &SkeletonModification3DJiggle::set_use_colliders); + ClassDB::bind_method(D_METHOD("get_use_colliders"), &SkeletonModification3DJiggle::get_use_colliders); + ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &SkeletonModification3DJiggle::set_collision_mask); + ClassDB::bind_method(D_METHOD("get_collision_mask"), &SkeletonModification3DJiggle::get_collision_mask); + + // Jiggle joint data functions + ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_name", "joint_idx", "name"), &SkeletonModification3DJiggle::set_jiggle_joint_bone_name); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_name", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_bone_name); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification3DJiggle::set_jiggle_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_index", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_override", "joint_idx", "override"), &SkeletonModification3DJiggle::set_jiggle_joint_override); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_override", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_override); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification3DJiggle::set_jiggle_joint_stiffness); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_stiffness", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_stiffness); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_mass", "joint_idx", "mass"), &SkeletonModification3DJiggle::set_jiggle_joint_mass); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_mass", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_mass); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_damping", "joint_idx", "damping"), &SkeletonModification3DJiggle::set_jiggle_joint_damping); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_damping", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_damping); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification3DJiggle::set_jiggle_joint_use_gravity); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_use_gravity", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_use_gravity); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_gravity", "joint_idx", "gravity"), &SkeletonModification3DJiggle::set_jiggle_joint_gravity); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_gravity", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_gravity); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_roll", "joint_idx", "roll"), &SkeletonModification3DJiggle::set_jiggle_joint_roll); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_roll", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_roll); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "jiggle_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_jiggle_data_chain_length", "get_jiggle_data_chain_length"); + ADD_GROUP("Default Joint Settings", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness"), "set_stiffness", "get_stiffness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01"), "set_damping", "get_damping"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_gravity"), "set_use_gravity", "get_use_gravity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); + ADD_GROUP("", ""); +} + +SkeletonModification3DJiggle::SkeletonModification3DJiggle() { + stack = nullptr; + is_setup = false; + jiggle_data_chain = Vector<Jiggle_Joint_Data>(); + stiffness = 3; + mass = 0.75; + damping = 0.75; + use_gravity = false; + gravity = Vector3(0, -6.0, 0); + enabled = true; +} + +SkeletonModification3DJiggle::~SkeletonModification3DJiggle() { +} diff --git a/scene/resources/skeleton_modification_3d_jiggle.h b/scene/resources/skeleton_modification_3d_jiggle.h new file mode 100644 index 0000000000..c210c8fa73 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_jiggle.h @@ -0,0 +1,138 @@ +/*************************************************************************/ +/* skeleton_modification_3d_jiggle.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. */ +/*************************************************************************/ + +#include "core/templates/local_vector.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DJIGGLE_H +#define SKELETONMODIFICATION3DJIGGLE_H + +class SkeletonModification3DJiggle : public SkeletonModification3D { + GDCLASS(SkeletonModification3DJiggle, SkeletonModification3D); + +private: + struct Jiggle_Joint_Data { + String bone_name = ""; + int bone_idx = -1; + + bool override_defaults = false; + real_t stiffness = 3; + real_t mass = 0.75; + real_t damping = 0.75; + bool use_gravity = false; + Vector3 gravity = Vector3(0, -6.0, 0); + real_t roll = 0; + + Vector3 cached_rotation = Vector3(0, 0, 0); + Vector3 force = Vector3(0, 0, 0); + Vector3 acceleration = Vector3(0, 0, 0); + Vector3 velocity = Vector3(0, 0, 0); + Vector3 last_position = Vector3(0, 0, 0); + Vector3 dynamic_position = Vector3(0, 0, 0); + + Vector3 last_noncollision_position = Vector3(0, 0, 0); + }; + + NodePath target_node; + ObjectID target_node_cache; + LocalVector<Jiggle_Joint_Data> jiggle_data_chain; + + real_t stiffness = 3; + real_t mass = 0.75; + real_t damping = 0.75; + bool use_gravity = false; + Vector3 gravity = Vector3(0, -6.0, 0); + + bool use_colliders = false; + uint32_t collision_mask = 1; + + void update_cache(); + void _execute_jiggle_joint(int p_joint_idx, Node3D *p_target, real_t p_delta); + void _update_jiggle_joint_data(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_stiffness(real_t p_stiffness); + real_t get_stiffness() const; + void set_mass(real_t p_mass); + real_t get_mass() const; + void set_damping(real_t p_damping); + real_t get_damping() const; + + void set_use_gravity(bool p_use_gravity); + bool get_use_gravity() const; + void set_gravity(Vector3 p_gravity); + Vector3 get_gravity() const; + + void set_use_colliders(bool p_use_colliders); + bool get_use_colliders() const; + void set_collision_mask(int p_mask); + int get_collision_mask() const; + + int get_jiggle_data_chain_length(); + void set_jiggle_data_chain_length(int p_new_length); + + void set_jiggle_joint_bone_name(int p_joint_idx, String p_name); + String get_jiggle_joint_bone_name(int p_joint_idx) const; + void set_jiggle_joint_bone_index(int p_joint_idx, int p_idx); + int get_jiggle_joint_bone_index(int p_joint_idx) const; + + void set_jiggle_joint_override(int p_joint_idx, bool p_override); + bool get_jiggle_joint_override(int p_joint_idx) const; + void set_jiggle_joint_stiffness(int p_joint_idx, real_t p_stiffness); + real_t get_jiggle_joint_stiffness(int p_joint_idx) const; + void set_jiggle_joint_mass(int p_joint_idx, real_t p_mass); + real_t get_jiggle_joint_mass(int p_joint_idx) const; + void set_jiggle_joint_damping(int p_joint_idx, real_t p_damping); + real_t get_jiggle_joint_damping(int p_joint_idx) const; + void set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity); + bool get_jiggle_joint_use_gravity(int p_joint_idx) const; + void set_jiggle_joint_gravity(int p_joint_idx, Vector3 p_gravity); + Vector3 get_jiggle_joint_gravity(int p_joint_idx) const; + void set_jiggle_joint_roll(int p_joint_idx, real_t p_roll); + real_t get_jiggle_joint_roll(int p_joint_idx) const; + + SkeletonModification3DJiggle(); + ~SkeletonModification3DJiggle(); +}; + +#endif //SKELETONMODIFICATION3DJIGGLE_H diff --git a/scene/resources/skeleton_modification_3d_lookat.cpp b/scene/resources/skeleton_modification_3d_lookat.cpp new file mode 100644 index 0000000000..afdb077e71 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_lookat.cpp @@ -0,0 +1,265 @@ +/*************************************************************************/ +/* skeleton_modification_3d_lookat.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_lookat.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DLookAt::_set(const StringName &p_path, const Variant &p_value) { + if (p_path == "lock_rotation_to_plane") { + set_lock_rotation_to_plane(p_value); + } else if (p_path == "lock_rotation_plane") { + set_lock_rotation_plane(p_value); + } else if (p_path == "additional_rotation") { + Vector3 tmp = p_value; + tmp.x = Math::deg2rad(tmp.x); + tmp.y = Math::deg2rad(tmp.y); + tmp.z = Math::deg2rad(tmp.z); + set_additional_rotation(tmp); + } + + return true; +} + +bool SkeletonModification3DLookAt::_get(const StringName &p_path, Variant &r_ret) const { + if (p_path == "lock_rotation_to_plane") { + r_ret = get_lock_rotation_to_plane(); + } else if (p_path == "lock_rotation_plane") { + r_ret = get_lock_rotation_plane(); + } else if (p_path == "additional_rotation") { + Vector3 tmp = get_additional_rotation(); + tmp.x = Math::rad2deg(tmp.x); + tmp.y = Math::rad2deg(tmp.y); + tmp.z = Math::rad2deg(tmp.z); + r_ret = tmp; + } + + return true; +} + +void SkeletonModification3DLookAt::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "lock_rotation_to_plane", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (lock_rotation_to_plane) { + p_list->push_back(PropertyInfo(Variant::INT, "lock_rotation_plane", PROPERTY_HINT_ENUM, "X plane, Y plane, Z plane", PROPERTY_USAGE_DEFAULT)); + } + p_list->push_back(PropertyInfo(Variant::VECTOR3, "additional_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); +} + +void SkeletonModification3DLookAt::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + _print_execution_error(true, "Target cache is out of date. Attempting to update..."); + update_cache(); + return; + } + + if (bone_idx <= -2) { + bone_idx = stack->skeleton->find_bone(bone_name); + } + + Node3D *target = Object::cast_to<Node3D>(ObjectDB::get_instance(target_node_cache)); + if (_print_execution_error(!target || !target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { + return; + } + if (_print_execution_error(bone_idx <= -1, "Bone index is invalid. Cannot execute modification!")) { + return; + } + + Transform3D new_bone_trans = stack->skeleton->get_bone_local_pose_override(bone_idx); + Vector3 target_pos = stack->skeleton->global_pose_to_local_pose(bone_idx, stack->skeleton->world_transform_to_global_pose(target->get_global_transform())).origin; + + // Lock the rotation to a plane relative to the bone by changing the target position + if (lock_rotation_to_plane) { + if (lock_rotation_plane == ROTATION_PLANE::ROTATION_PLANE_X) { + target_pos.x = new_bone_trans.origin.x; + } else if (lock_rotation_plane == ROTATION_PLANE::ROTATION_PLANE_Y) { + target_pos.y = new_bone_trans.origin.y; + } else if (lock_rotation_plane == ROTATION_PLANE::ROTATION_PLANE_Z) { + target_pos.z = new_bone_trans.origin.z; + } + } + + // Look at the target! + new_bone_trans = new_bone_trans.looking_at(target_pos, Vector3(0, 1, 0)); + // Convert from Z-forward to whatever direction the bone faces. + stack->skeleton->update_bone_rest_forward_vector(bone_idx); + new_bone_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(bone_idx, new_bone_trans.basis); + + // Apply additional rotation + new_bone_trans.basis.rotate_local(Vector3(1, 0, 0), additional_rotation.x); + new_bone_trans.basis.rotate_local(Vector3(0, 1, 0), additional_rotation.y); + new_bone_trans.basis.rotate_local(Vector3(0, 0, 1), additional_rotation.z); + + stack->skeleton->set_bone_local_pose_override(bone_idx, new_bone_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(bone_idx); + + // If we completed it successfully, then we can set execution_error_found to false + execution_error_found = false; +} + +void SkeletonModification3DLookAt::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + execution_error_found = false; + update_cache(); + } +} + +void SkeletonModification3DLookAt::set_bone_name(String p_name) { + bone_name = p_name; + if (stack) { + if (stack->skeleton) { + bone_idx = stack->skeleton->find_bone(bone_name); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +String SkeletonModification3DLookAt::get_bone_name() const { + return bone_name; +} + +int SkeletonModification3DLookAt::get_bone_index() const { + return bone_idx; +} + +void SkeletonModification3DLookAt::set_bone_index(int p_bone_idx) { + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + bone_idx = p_bone_idx; + + if (stack) { + if (stack->skeleton) { + bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +void SkeletonModification3DLookAt::update_cache() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: Node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: Node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DLookAt::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_cache(); +} + +NodePath SkeletonModification3DLookAt::get_target_node() const { + return target_node; +} + +Vector3 SkeletonModification3DLookAt::get_additional_rotation() const { + return additional_rotation; +} + +void SkeletonModification3DLookAt::set_additional_rotation(Vector3 p_offset) { + additional_rotation = p_offset; +} + +bool SkeletonModification3DLookAt::get_lock_rotation_to_plane() const { + return lock_rotation_plane; +} + +void SkeletonModification3DLookAt::set_lock_rotation_to_plane(bool p_lock_rotation) { + lock_rotation_to_plane = p_lock_rotation; + notify_property_list_changed(); +} + +int SkeletonModification3DLookAt::get_lock_rotation_plane() const { + return lock_rotation_plane; +} + +void SkeletonModification3DLookAt::set_lock_rotation_plane(int p_plane) { + lock_rotation_plane = p_plane; +} + +void SkeletonModification3DLookAt::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_bone_name", "name"), &SkeletonModification3DLookAt::set_bone_name); + ClassDB::bind_method(D_METHOD("get_bone_name"), &SkeletonModification3DLookAt::get_bone_name); + + ClassDB::bind_method(D_METHOD("set_bone_index", "bone_idx"), &SkeletonModification3DLookAt::set_bone_index); + ClassDB::bind_method(D_METHOD("get_bone_index"), &SkeletonModification3DLookAt::get_bone_index); + + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DLookAt::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DLookAt::get_target_node); + + ClassDB::bind_method(D_METHOD("set_additional_rotation", "additional_rotation"), &SkeletonModification3DLookAt::set_additional_rotation); + ClassDB::bind_method(D_METHOD("get_additional_rotation"), &SkeletonModification3DLookAt::get_additional_rotation); + + ClassDB::bind_method(D_METHOD("set_lock_rotation_to_plane", "lock_to_plane"), &SkeletonModification3DLookAt::set_lock_rotation_to_plane); + ClassDB::bind_method(D_METHOD("get_lock_rotation_to_plane"), &SkeletonModification3DLookAt::get_lock_rotation_to_plane); + ClassDB::bind_method(D_METHOD("set_lock_rotation_plane", "plane"), &SkeletonModification3DLookAt::set_lock_rotation_plane); + ClassDB::bind_method(D_METHOD("get_lock_rotation_plane"), &SkeletonModification3DLookAt::get_lock_rotation_plane); + + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_index"), "set_bone_index", "get_bone_index"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); +} + +SkeletonModification3DLookAt::SkeletonModification3DLookAt() { + stack = nullptr; + is_setup = false; + bone_name = ""; + bone_idx = -2; + additional_rotation = Vector3(); + lock_rotation_to_plane = false; + enabled = true; +} + +SkeletonModification3DLookAt::~SkeletonModification3DLookAt() { +} diff --git a/scene/resources/skeleton_modification_3d_lookat.h b/scene/resources/skeleton_modification_3d_lookat.h new file mode 100644 index 0000000000..5971e3f647 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_lookat.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* skeleton_modification_3d_lookat.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. */ +/*************************************************************************/ + +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DLOOKAT_H +#define SKELETONMODIFICATION3DLOOKAT_H + +class SkeletonModification3DLookAt : public SkeletonModification3D { + GDCLASS(SkeletonModification3DLookAt, SkeletonModification3D); + +private: + String bone_name = ""; + int bone_idx = -1; + NodePath target_node; + ObjectID target_node_cache; + + Vector3 additional_rotation = Vector3(1, 0, 0); + bool lock_rotation_to_plane = false; + int lock_rotation_plane = ROTATION_PLANE_X; + + void update_cache(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + enum ROTATION_PLANE { + ROTATION_PLANE_X, + ROTATION_PLANE_Y, + ROTATION_PLANE_Z + }; + + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_bone_name(String p_name); + String get_bone_name() const; + + void set_bone_index(int p_idx); + int get_bone_index() const; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_additional_rotation(Vector3 p_offset); + Vector3 get_additional_rotation() const; + + void set_lock_rotation_to_plane(bool p_lock_to_plane); + bool get_lock_rotation_to_plane() const; + void set_lock_rotation_plane(int p_plane); + int get_lock_rotation_plane() const; + + SkeletonModification3DLookAt(); + ~SkeletonModification3DLookAt(); +}; + +#endif //SKELETONMODIFICATION3DLOOKAT_H diff --git a/scene/resources/skeleton_modification_3d_stackholder.cpp b/scene/resources/skeleton_modification_3d_stackholder.cpp new file mode 100644 index 0000000000..56035a4def --- /dev/null +++ b/scene/resources/skeleton_modification_3d_stackholder.cpp @@ -0,0 +1,104 @@ +/*************************************************************************/ +/* skeleton_modification_3d_stackholder.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_stackholder.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DStackHolder::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path == "held_modification_stack") { + set_held_modification_stack(p_value); + } + return true; +} + +bool SkeletonModification3DStackHolder::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path == "held_modification_stack") { + r_ret = get_held_modification_stack(); + } + return true; +} + +void SkeletonModification3DStackHolder::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::OBJECT, "held_modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack3D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); +} + +void SkeletonModification3DStackHolder::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + + if (held_modification_stack.is_valid()) { + held_modification_stack->execute(p_delta, execution_mode); + } +} + +void SkeletonModification3DStackHolder::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + + if (held_modification_stack.is_valid()) { + held_modification_stack->set_skeleton(stack->get_skeleton()); + held_modification_stack->setup(); + } + } +} + +void SkeletonModification3DStackHolder::set_held_modification_stack(Ref<SkeletonModificationStack3D> p_held_stack) { + held_modification_stack = p_held_stack; + + if (is_setup && held_modification_stack.is_valid()) { + held_modification_stack->set_skeleton(stack->get_skeleton()); + held_modification_stack->setup(); + } +} + +Ref<SkeletonModificationStack3D> SkeletonModification3DStackHolder::get_held_modification_stack() const { + return held_modification_stack; +} + +void SkeletonModification3DStackHolder::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_held_modification_stack", "held_modification_stack"), &SkeletonModification3DStackHolder::set_held_modification_stack); + ClassDB::bind_method(D_METHOD("get_held_modification_stack"), &SkeletonModification3DStackHolder::get_held_modification_stack); +} + +SkeletonModification3DStackHolder::SkeletonModification3DStackHolder() { + stack = nullptr; + is_setup = false; + enabled = true; +} + +SkeletonModification3DStackHolder::~SkeletonModification3DStackHolder() { +} diff --git a/scene/resources/skeleton_modification_3d_stackholder.h b/scene/resources/skeleton_modification_3d_stackholder.h new file mode 100644 index 0000000000..c765cd8de3 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_stackholder.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* skeleton_modification_3d_stackholder.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. */ +/*************************************************************************/ + +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DSTACKHOLDER_H +#define SKELETONMODIFICATION3DSTACKHOLDER_H + +class SkeletonModification3DStackHolder : public SkeletonModification3D { + GDCLASS(SkeletonModification3DStackHolder, SkeletonModification3D); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + Ref<SkeletonModificationStack3D> held_modification_stack; + + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_held_modification_stack(Ref<SkeletonModificationStack3D> p_held_stack); + Ref<SkeletonModificationStack3D> get_held_modification_stack() const; + + SkeletonModification3DStackHolder(); + ~SkeletonModification3DStackHolder(); +}; + +#endif //SKELETONMODIFICATION3DSTACKHOLDER_H diff --git a/scene/resources/skeleton_modification_3d_twoboneik.cpp b/scene/resources/skeleton_modification_3d_twoboneik.cpp new file mode 100644 index 0000000000..ae7a3bab7e --- /dev/null +++ b/scene/resources/skeleton_modification_3d_twoboneik.cpp @@ -0,0 +1,599 @@ +/*************************************************************************/ +/* skeleton_modification_3d_twoboneik.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_twoboneik.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DTwoBoneIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path == "use_tip_node") { + set_use_tip_node(p_value); + } else if (path == "tip_node") { + set_tip_node(p_value); + } else if (path == "auto_calculate_joint_length") { + set_auto_calculate_joint_length(p_value); + } else if (path == "use_pole_node") { + set_use_pole_node(p_value); + } else if (path == "pole_node") { + set_pole_node(p_value); + } else if (path == "joint_one_length") { + set_joint_one_length(p_value); + } else if (path == "joint_two_length") { + set_joint_two_length(p_value); + } else if (path == "joint_one/bone_name") { + set_joint_one_bone_name(p_value); + } else if (path == "joint_one/bone_idx") { + set_joint_one_bone_idx(p_value); + } else if (path == "joint_one/roll") { + set_joint_one_roll(Math::deg2rad(real_t(p_value))); + } else if (path == "joint_two/bone_name") { + set_joint_two_bone_name(p_value); + } else if (path == "joint_two/bone_idx") { + set_joint_two_bone_idx(p_value); + } else if (path == "joint_two/roll") { + set_joint_two_roll(Math::deg2rad(real_t(p_value))); + } + + return true; +} + +bool SkeletonModification3DTwoBoneIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path == "use_tip_node") { + r_ret = get_use_tip_node(); + } else if (path == "tip_node") { + r_ret = get_tip_node(); + } else if (path == "auto_calculate_joint_length") { + r_ret = get_auto_calculate_joint_length(); + } else if (path == "use_pole_node") { + r_ret = get_use_pole_node(); + } else if (path == "pole_node") { + r_ret = get_pole_node(); + } else if (path == "joint_one_length") { + r_ret = get_joint_one_length(); + } else if (path == "joint_two_length") { + r_ret = get_joint_two_length(); + } else if (path == "joint_one/bone_name") { + r_ret = get_joint_one_bone_name(); + } else if (path == "joint_one/bone_idx") { + r_ret = get_joint_one_bone_idx(); + } else if (path == "joint_one/roll") { + r_ret = Math::rad2deg(get_joint_one_roll()); + } else if (path == "joint_two/bone_name") { + r_ret = get_joint_two_bone_name(); + } else if (path == "joint_two/bone_idx") { + r_ret = get_joint_two_bone_idx(); + } else if (path == "joint_two/roll") { + r_ret = Math::rad2deg(get_joint_two_roll()); + } + + return true; +} + +void SkeletonModification3DTwoBoneIK::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "use_tip_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (use_tip_node) { + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "tip_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT)); + } + + p_list->push_back(PropertyInfo(Variant::BOOL, "auto_calculate_joint_length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (!auto_calculate_joint_length) { + p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_one_length", PROPERTY_HINT_RANGE, "-1, 10000, 0.001", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_two_length", PROPERTY_HINT_RANGE, "-1, 10000, 0.001", PROPERTY_USAGE_DEFAULT)); + } + + p_list->push_back(PropertyInfo(Variant::BOOL, "use_pole_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (use_pole_node) { + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "pole_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT)); + } + + p_list->push_back(PropertyInfo(Variant::STRING_NAME, "joint_one/bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, "joint_one/bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_one/roll", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + + p_list->push_back(PropertyInfo(Variant::STRING_NAME, "joint_two/bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, "joint_two/bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_two/roll", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); +} + +void SkeletonModification3DTwoBoneIK::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + + if (!enabled) { + return; + } + + if (_print_execution_error(joint_one_bone_idx < 0 || joint_two_bone_idx < 0, + "One (or more) of the bones in the modification have invalid bone indexes. Cannot execute modification!")) { + return; + } + + if (target_node_cache.is_null()) { + _print_execution_error(true, "Target cache is out of date. Attempting to update..."); + update_cache_target(); + return; + } + + // Update joint lengths (if needed) + if (auto_calculate_joint_length && (joint_one_length < 0 || joint_two_length < 0)) { + calculate_joint_lengths(); + } + + // Adopted from the links below: + // http://theorangeduck.com/page/simple-two-joint + // https://www.alanzucconi.com/2018/05/02/ik-2d-2/ + // With modifications by TwistedTwigleg + Node3D *target = Object::cast_to<Node3D>(ObjectDB::get_instance(target_node_cache)); + if (_print_execution_error(!target || !target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { + return; + } + Transform3D target_trans = stack->skeleton->world_transform_to_global_pose(target->get_global_transform()); + + Transform3D bone_one_trans; + Transform3D bone_two_trans; + + // Make the first joint look at the pole, and the second look at the target. That way, the + // TwoBoneIK solver has to really only handle extension/contraction, which should make it align with the pole. + if (use_pole_node) { + if (pole_node_cache.is_null()) { + _print_execution_error(true, "Pole cache is out of date. Attempting to update..."); + update_cache_pole(); + return; + } + + Node3D *pole = Object::cast_to<Node3D>(ObjectDB::get_instance(pole_node_cache)); + if (_print_execution_error(!pole || !pole->is_inside_tree(), "Pole node is not in the scene tree. Cannot execute modification!")) { + return; + } + Transform3D pole_trans = stack->skeleton->world_transform_to_global_pose(pole->get_global_transform()); + + bone_one_trans = stack->skeleton->local_pose_to_global_pose(joint_one_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_one_bone_idx)); + bone_one_trans = bone_one_trans.looking_at(pole_trans.origin, Vector3(0, 1, 0)); + bone_one_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(joint_one_bone_idx, bone_one_trans.basis); + stack->skeleton->update_bone_rest_forward_vector(joint_one_bone_idx); + bone_one_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_one_bone_idx), joint_one_roll); + stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, stack->skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_trans), stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(joint_one_bone_idx); + + bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx)); + bone_two_trans = bone_two_trans.looking_at(target_trans.origin, Vector3(0, 1, 0)); + bone_two_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(joint_two_bone_idx, bone_two_trans.basis); + stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); + bone_two_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll); + stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, stack->skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans), stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(joint_two_bone_idx); + } else { + bone_one_trans = stack->skeleton->local_pose_to_global_pose(joint_one_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_one_bone_idx)); + bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx)); + } + + Transform3D bone_two_tip_trans; + if (use_tip_node) { + if (tip_node_cache.is_null()) { + _print_execution_error(true, "Tip cache is out of date. Attempting to update..."); + update_cache_tip(); + return; + } + Node3D *tip = Object::cast_to<Node3D>(ObjectDB::get_instance(tip_node_cache)); + if (_print_execution_error(!tip || !tip->is_inside_tree(), "Tip node is not in the scene tree. Cannot execute modification!")) { + return; + } + bone_two_tip_trans = stack->skeleton->world_transform_to_global_pose(tip->get_global_transform()); + } else { + stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); + bone_two_tip_trans = bone_two_trans; + bone_two_tip_trans.origin += bone_two_trans.basis.xform(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx)).normalized() * joint_two_length; + } + + real_t joint_one_to_target_length = bone_one_trans.origin.distance_to(target_trans.origin); + if (joint_one_length + joint_two_length < joint_one_to_target_length) { + // Set the target *just* out of reach to straighten the bones + joint_one_to_target_length = joint_one_length + joint_two_length + 0.01; + } else if (joint_one_to_target_length < joint_one_length) { + // Place the target in reach so the solver doesn't do crazy things + joint_one_to_target_length = joint_one_length; + } + + // Get the square lengths for all three sides of the triangle we'll use to calculate the angles + real_t sqr_one_length = joint_one_length * joint_one_length; + real_t sqr_two_length = joint_two_length * joint_two_length; + real_t sqr_three_length = joint_one_to_target_length * joint_one_to_target_length; + + // Calculate the angles for the first joint using the law of cosigns + real_t ac_ab_0 = Math::acos(CLAMP(bone_two_tip_trans.origin.direction_to(bone_one_trans.origin).dot(bone_two_trans.origin.direction_to(bone_one_trans.origin)), -1, 1)); + real_t ac_at_0 = Math::acos(CLAMP(bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).dot(bone_one_trans.origin.direction_to(target_trans.origin)), -1, 1)); + real_t ac_ab_1 = Math::acos(CLAMP((sqr_two_length - sqr_one_length - sqr_three_length) / (-2.0 * joint_one_length * joint_one_to_target_length), -1, 1)); + + // Calculate the angles of rotation. Angle 0 is the extension/contraction axis, while angle 1 is the rotation axis to align the triangle to the target + Vector3 axis_0 = bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).cross(bone_one_trans.origin.direction_to(bone_two_trans.origin)); + Vector3 axis_1 = bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).cross(bone_one_trans.origin.direction_to(target_trans.origin)); + + // Make a quaternion with the delta rotation needed to rotate the first joint into alignment and apply it to the transform. + Quaternion bone_one_quat = bone_one_trans.basis.get_rotation_quaternion(); + Quaternion rot_0 = Quaternion(bone_one_quat.inverse().xform(axis_0).normalized(), (ac_ab_1 - ac_ab_0)); + Quaternion rot_2 = Quaternion(bone_one_quat.inverse().xform(axis_1).normalized(), ac_at_0); + bone_one_trans.basis.set_quaternion(bone_one_quat * (rot_0 * rot_2)); + + stack->skeleton->update_bone_rest_forward_vector(joint_one_bone_idx); + bone_one_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_one_bone_idx), joint_one_roll); + + // Apply the rotation to the first joint + bone_one_trans = stack->skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_trans); + bone_one_trans.origin = Vector3(0, 0, 0); + stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, bone_one_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(joint_one_bone_idx); + + if (use_pole_node) { + // Update bone_two_trans so its at the latest position, with the rotation of bone_one_trans taken into account, then look at the target. + bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx)); + stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); + Vector3 forward_vector = stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx); + bone_two_trans.basis.rotate_to_align(forward_vector, bone_two_trans.origin.direction_to(target_trans.origin)); + + stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); + bone_two_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll); + + bone_two_trans = stack->skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans); + stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, bone_two_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(joint_two_bone_idx); + } else { + // Calculate the angles for the second joint using the law of cosigns, make a quaternion with the delta rotation needed to rotate the joint into + // alignment, and then apply it to the second joint. + real_t ba_bc_0 = Math::acos(CLAMP(bone_two_trans.origin.direction_to(bone_one_trans.origin).dot(bone_two_trans.origin.direction_to(bone_two_tip_trans.origin)), -1, 1)); + real_t ba_bc_1 = Math::acos(CLAMP((sqr_three_length - sqr_one_length - sqr_two_length) / (-2.0 * joint_one_length * joint_two_length), -1, 1)); + Quaternion bone_two_quat = bone_two_trans.basis.get_rotation_quaternion(); + Quaternion rot_1 = Quaternion(bone_two_quat.inverse().xform(axis_0).normalized(), (ba_bc_1 - ba_bc_0)); + bone_two_trans.basis.set_quaternion(bone_two_quat * rot_1); + + stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); + bone_two_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll); + + bone_two_trans = stack->skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans); + bone_two_trans.origin = Vector3(0, 0, 0); + stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, bone_two_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(joint_two_bone_idx); + } +} + +void SkeletonModification3DTwoBoneIK::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + execution_error_found = false; + update_cache_target(); + update_cache_tip(); + } +} + +void SkeletonModification3DTwoBoneIK::update_cache_target() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree() && target_node.is_empty() == false) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: Target node is this modification's skeleton or cannot be found. Cannot execute modification"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: Target node is not in the scene tree. Cannot execute modification!"); + target_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DTwoBoneIK::update_cache_tip() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update tip cache: modification is not properly setup!"); + return; + } + + tip_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(tip_node)) { + Node *node = stack->skeleton->get_node(tip_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update tip cache: Tip node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update tip cache: Tip node is not in the scene tree. Cannot execute modification!"); + tip_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DTwoBoneIK::update_cache_pole() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update pole cache: modification is not properly setup!"); + return; + } + + pole_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(pole_node)) { + Node *node = stack->skeleton->get_node(pole_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update pole cache: Pole node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update pole cache: Pole node is not in the scene tree. Cannot execute modification!"); + pole_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DTwoBoneIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_cache_target(); +} + +NodePath SkeletonModification3DTwoBoneIK::get_target_node() const { + return target_node; +} + +void SkeletonModification3DTwoBoneIK::set_use_tip_node(const bool p_use_tip_node) { + use_tip_node = p_use_tip_node; + notify_property_list_changed(); +} + +bool SkeletonModification3DTwoBoneIK::get_use_tip_node() const { + return use_tip_node; +} + +void SkeletonModification3DTwoBoneIK::set_tip_node(const NodePath &p_tip_node) { + tip_node = p_tip_node; + update_cache_tip(); +} + +NodePath SkeletonModification3DTwoBoneIK::get_tip_node() const { + return tip_node; +} + +void SkeletonModification3DTwoBoneIK::set_use_pole_node(const bool p_use_pole_node) { + use_pole_node = p_use_pole_node; + notify_property_list_changed(); +} + +bool SkeletonModification3DTwoBoneIK::get_use_pole_node() const { + return use_pole_node; +} + +void SkeletonModification3DTwoBoneIK::set_pole_node(const NodePath &p_pole_node) { + pole_node = p_pole_node; + update_cache_pole(); +} + +NodePath SkeletonModification3DTwoBoneIK::get_pole_node() const { + return pole_node; +} + +void SkeletonModification3DTwoBoneIK::set_auto_calculate_joint_length(bool p_calculate) { + auto_calculate_joint_length = p_calculate; + if (p_calculate) { + calculate_joint_lengths(); + } + notify_property_list_changed(); +} + +bool SkeletonModification3DTwoBoneIK::get_auto_calculate_joint_length() const { + return auto_calculate_joint_length; +} + +void SkeletonModification3DTwoBoneIK::calculate_joint_lengths() { + if (!is_setup) { + return; // fail silently, as we likely just loaded the scene. + } + ERR_FAIL_COND_MSG(!stack || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot calculate joint lengths!"); + ERR_FAIL_COND_MSG(joint_one_bone_idx <= -1 || joint_two_bone_idx <= -1, + "One of the bones in the TwoBoneIK modification are not set! Cannot calculate joint lengths!"); + + Transform3D bone_one_rest_trans = stack->skeleton->get_bone_global_pose(joint_one_bone_idx); + Transform3D bone_two_rest_trans = stack->skeleton->get_bone_global_pose(joint_two_bone_idx); + + joint_one_length = bone_one_rest_trans.origin.distance_to(bone_two_rest_trans.origin); + + if (use_tip_node) { + if (tip_node_cache.is_null()) { + update_cache_tip(); + WARN_PRINT("Tip cache is out of date. Updating..."); + } + + Node3D *tip = Object::cast_to<Node3D>(ObjectDB::get_instance(tip_node_cache)); + if (tip) { + Transform3D bone_tip_trans = stack->skeleton->world_transform_to_global_pose(tip->get_global_transform()); + joint_two_length = bone_two_rest_trans.origin.distance_to(bone_tip_trans.origin); + } + } else { + // Attempt to use children bones to get the length + Vector<int> bone_two_children = stack->skeleton->get_bone_children(joint_two_bone_idx); + if (bone_two_children.size() > 0) { + joint_two_length = 0; + for (int i = 0; i < bone_two_children.size(); i++) { + joint_two_length += bone_two_rest_trans.origin.distance_to( + stack->skeleton->local_pose_to_global_pose(bone_two_children[i], stack->skeleton->get_bone_rest(bone_two_children[i])).origin); + } + joint_two_length = joint_two_length / bone_two_children.size(); + } else { + WARN_PRINT("TwoBoneIK modification: Cannot auto calculate length for joint 2! Auto setting the length to 1..."); + joint_two_length = 1.0; + } + } + execution_error_found = false; +} + +void SkeletonModification3DTwoBoneIK::set_joint_one_bone_name(String p_bone_name) { + joint_one_bone_name = p_bone_name; + if (stack && stack->skeleton) { + joint_one_bone_idx = stack->skeleton->find_bone(p_bone_name); + } + execution_error_found = false; + notify_property_list_changed(); +} + +String SkeletonModification3DTwoBoneIK::get_joint_one_bone_name() const { + return joint_one_bone_name; +} + +void SkeletonModification3DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) { + joint_one_bone_idx = p_bone_idx; + if (stack && stack->skeleton) { + joint_one_bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + execution_error_found = false; + notify_property_list_changed(); +} + +int SkeletonModification3DTwoBoneIK::get_joint_one_bone_idx() const { + return joint_one_bone_idx; +} + +void SkeletonModification3DTwoBoneIK::set_joint_one_length(real_t p_length) { + joint_one_length = p_length; +} + +real_t SkeletonModification3DTwoBoneIK::get_joint_one_length() const { + return joint_one_length; +} + +void SkeletonModification3DTwoBoneIK::set_joint_two_bone_name(String p_bone_name) { + joint_two_bone_name = p_bone_name; + if (stack && stack->skeleton) { + joint_two_bone_idx = stack->skeleton->find_bone(p_bone_name); + } + execution_error_found = false; + notify_property_list_changed(); +} + +String SkeletonModification3DTwoBoneIK::get_joint_two_bone_name() const { + return joint_two_bone_name; +} + +void SkeletonModification3DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) { + joint_two_bone_idx = p_bone_idx; + if (stack && stack->skeleton) { + joint_two_bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + execution_error_found = false; + notify_property_list_changed(); +} + +int SkeletonModification3DTwoBoneIK::get_joint_two_bone_idx() const { + return joint_two_bone_idx; +} + +void SkeletonModification3DTwoBoneIK::set_joint_two_length(real_t p_length) { + joint_two_length = p_length; +} + +real_t SkeletonModification3DTwoBoneIK::get_joint_two_length() const { + return joint_two_length; +} + +void SkeletonModification3DTwoBoneIK::set_joint_one_roll(real_t p_roll) { + joint_one_roll = p_roll; +} + +real_t SkeletonModification3DTwoBoneIK::get_joint_one_roll() const { + return joint_one_roll; +} + +void SkeletonModification3DTwoBoneIK::set_joint_two_roll(real_t p_roll) { + joint_two_roll = p_roll; +} + +real_t SkeletonModification3DTwoBoneIK::get_joint_two_roll() const { + return joint_two_roll; +} + +void SkeletonModification3DTwoBoneIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DTwoBoneIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DTwoBoneIK::get_target_node); + + ClassDB::bind_method(D_METHOD("set_use_pole_node", "use_pole_node"), &SkeletonModification3DTwoBoneIK::set_use_pole_node); + ClassDB::bind_method(D_METHOD("get_use_pole_node"), &SkeletonModification3DTwoBoneIK::get_use_pole_node); + ClassDB::bind_method(D_METHOD("set_pole_node", "pole_nodepath"), &SkeletonModification3DTwoBoneIK::set_pole_node); + ClassDB::bind_method(D_METHOD("get_pole_node"), &SkeletonModification3DTwoBoneIK::get_pole_node); + + ClassDB::bind_method(D_METHOD("set_use_tip_node", "use_tip_node"), &SkeletonModification3DTwoBoneIK::set_use_tip_node); + ClassDB::bind_method(D_METHOD("get_use_tip_node"), &SkeletonModification3DTwoBoneIK::get_use_tip_node); + ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification3DTwoBoneIK::set_tip_node); + ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification3DTwoBoneIK::get_tip_node); + + ClassDB::bind_method(D_METHOD("set_auto_calculate_joint_length", "auto_calculate_joint_length"), &SkeletonModification3DTwoBoneIK::set_auto_calculate_joint_length); + ClassDB::bind_method(D_METHOD("get_auto_calculate_joint_length"), &SkeletonModification3DTwoBoneIK::get_auto_calculate_joint_length); + + ClassDB::bind_method(D_METHOD("set_joint_one_bone_name", "bone_name"), &SkeletonModification3DTwoBoneIK::set_joint_one_bone_name); + ClassDB::bind_method(D_METHOD("get_joint_one_bone_name"), &SkeletonModification3DTwoBoneIK::get_joint_one_bone_name); + ClassDB::bind_method(D_METHOD("set_joint_one_bone_idx", "bone_idx"), &SkeletonModification3DTwoBoneIK::set_joint_one_bone_idx); + ClassDB::bind_method(D_METHOD("get_joint_one_bone_idx"), &SkeletonModification3DTwoBoneIK::get_joint_one_bone_idx); + ClassDB::bind_method(D_METHOD("set_joint_one_length", "bone_length"), &SkeletonModification3DTwoBoneIK::set_joint_one_length); + ClassDB::bind_method(D_METHOD("get_joint_one_length"), &SkeletonModification3DTwoBoneIK::get_joint_one_length); + + ClassDB::bind_method(D_METHOD("set_joint_two_bone_name", "bone_name"), &SkeletonModification3DTwoBoneIK::set_joint_two_bone_name); + ClassDB::bind_method(D_METHOD("get_joint_two_bone_name"), &SkeletonModification3DTwoBoneIK::get_joint_two_bone_name); + ClassDB::bind_method(D_METHOD("set_joint_two_bone_idx", "bone_idx"), &SkeletonModification3DTwoBoneIK::set_joint_two_bone_idx); + ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification3DTwoBoneIK::get_joint_two_bone_idx); + ClassDB::bind_method(D_METHOD("set_joint_two_length", "bone_length"), &SkeletonModification3DTwoBoneIK::set_joint_two_length); + ClassDB::bind_method(D_METHOD("get_joint_two_length"), &SkeletonModification3DTwoBoneIK::get_joint_two_length); + + ClassDB::bind_method(D_METHOD("set_joint_one_roll", "roll"), &SkeletonModification3DTwoBoneIK::set_joint_one_roll); + ClassDB::bind_method(D_METHOD("get_joint_one_roll"), &SkeletonModification3DTwoBoneIK::get_joint_one_roll); + ClassDB::bind_method(D_METHOD("set_joint_two_roll", "roll"), &SkeletonModification3DTwoBoneIK::set_joint_two_roll); + ClassDB::bind_method(D_METHOD("get_joint_two_roll"), &SkeletonModification3DTwoBoneIK::get_joint_two_roll); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_GROUP("", ""); +} + +SkeletonModification3DTwoBoneIK::SkeletonModification3DTwoBoneIK() { + stack = nullptr; + is_setup = false; +} + +SkeletonModification3DTwoBoneIK::~SkeletonModification3DTwoBoneIK() { +} diff --git a/scene/resources/skeleton_modification_3d_twoboneik.h b/scene/resources/skeleton_modification_3d_twoboneik.h new file mode 100644 index 0000000000..e62d6cc497 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_twoboneik.h @@ -0,0 +1,118 @@ +/*************************************************************************/ +/* skeleton_modification_3d_twoboneik.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. */ +/*************************************************************************/ + +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DTWOBONEIK_H +#define SKELETONMODIFICATION3DTWOBONEIK_H + +class SkeletonModification3DTwoBoneIK : public SkeletonModification3D { + GDCLASS(SkeletonModification3DTwoBoneIK, SkeletonModification3D); + +private: + NodePath target_node; + ObjectID target_node_cache; + + bool use_tip_node = false; + NodePath tip_node; + ObjectID tip_node_cache; + + bool use_pole_node = false; + NodePath pole_node; + ObjectID pole_node_cache; + + String joint_one_bone_name = ""; + int joint_one_bone_idx = -1; + String joint_two_bone_name = ""; + int joint_two_bone_idx = -1; + + bool auto_calculate_joint_length = false; + real_t joint_one_length = -1; + real_t joint_two_length = -1; + + real_t joint_one_roll = 0; + real_t joint_two_roll = 0; + + void update_cache_target(); + void update_cache_tip(); + void update_cache_pole(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_use_tip_node(const bool p_use_tip_node); + bool get_use_tip_node() const; + void set_tip_node(const NodePath &p_tip_node); + NodePath get_tip_node() const; + + void set_use_pole_node(const bool p_use_pole_node); + bool get_use_pole_node() const; + void set_pole_node(const NodePath &p_pole_node); + NodePath get_pole_node() const; + + void set_auto_calculate_joint_length(bool p_calculate); + bool get_auto_calculate_joint_length() const; + void calculate_joint_lengths(); + + void set_joint_one_bone_name(String p_bone_name); + String get_joint_one_bone_name() const; + void set_joint_one_bone_idx(int p_bone_idx); + int get_joint_one_bone_idx() const; + void set_joint_one_length(real_t p_length); + real_t get_joint_one_length() const; + + void set_joint_two_bone_name(String p_bone_name); + String get_joint_two_bone_name() const; + void set_joint_two_bone_idx(int p_bone_idx); + int get_joint_two_bone_idx() const; + void set_joint_two_length(real_t p_length); + real_t get_joint_two_length() const; + + void set_joint_one_roll(real_t p_roll); + real_t get_joint_one_roll() const; + void set_joint_two_roll(real_t p_roll); + real_t get_joint_two_roll() const; + + SkeletonModification3DTwoBoneIK(); + ~SkeletonModification3DTwoBoneIK(); +}; + +#endif //SKELETONMODIFICATION3DTWOBONEIK_H diff --git a/scene/resources/skeleton_modification_stack_3d.cpp b/scene/resources/skeleton_modification_stack_3d.cpp new file mode 100644 index 0000000000..3fce0e5dbd --- /dev/null +++ b/scene/resources/skeleton_modification_stack_3d.cpp @@ -0,0 +1,222 @@ +/*************************************************************************/ +/* skeleton_modification_stack_3d.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "skeleton_modification_stack_3d.h" +#include "scene/3d/skeleton_3d.h" + +/////////////////////////////////////// +// ModificationStack3D +/////////////////////////////////////// + +void SkeletonModificationStack3D::_get_property_list(List<PropertyInfo> *p_list) const { + for (uint32_t i = 0; i < modifications.size(); i++) { + p_list->push_back( + PropertyInfo(Variant::OBJECT, "modifications/" + itos(i), + PROPERTY_HINT_RESOURCE_TYPE, + "SkeletonModification3D", + PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + } +} + +bool SkeletonModificationStack3D::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("modifications/")) { + int mod_idx = path.get_slicec('/', 1).to_int(); + set_modification(mod_idx, p_value); + return true; + } + return true; +} + +bool SkeletonModificationStack3D::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("modifications/")) { + int mod_idx = path.get_slicec('/', 1).to_int(); + r_ret = get_modification(mod_idx); + return true; + } + return true; +} + +void SkeletonModificationStack3D::setup() { + if (is_setup) { + return; + } + + if (skeleton != nullptr) { + is_setup = true; + for (uint32_t i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + modifications[i]->_setup_modification(this); + } + } else { + WARN_PRINT("Cannot setup SkeletonModificationStack3D: no skeleton set!"); + } +} + +void SkeletonModificationStack3D::execute(real_t p_delta, int p_execution_mode) { + ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr || is_queued_for_deletion(), + "Modification stack is not properly setup and therefore cannot execute!"); + + if (!skeleton->is_inside_tree()) { + ERR_PRINT_ONCE("Skeleton is not inside SceneTree! Cannot execute modification!"); + return; + } + + if (!enabled) { + return; + } + + for (uint32_t i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + + if (modifications[i]->get_execution_mode() == p_execution_mode) { + modifications[i]->_execute(p_delta); + } + } +} + +void SkeletonModificationStack3D::enable_all_modifications(bool p_enabled) { + for (uint32_t i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + modifications[i]->set_enabled(p_enabled); + } +} + +Ref<SkeletonModification3D> SkeletonModificationStack3D::get_modification(int p_mod_idx) const { + const int modifications_size = modifications.size(); + ERR_FAIL_INDEX_V(p_mod_idx, modifications_size, nullptr); + return modifications[p_mod_idx]; +} + +void SkeletonModificationStack3D::add_modification(Ref<SkeletonModification3D> p_mod) { + p_mod->_setup_modification(this); + modifications.push_back(p_mod); +} + +void SkeletonModificationStack3D::delete_modification(int p_mod_idx) { + const int modifications_size = modifications.size(); + ERR_FAIL_INDEX(p_mod_idx, modifications_size); + modifications.remove(p_mod_idx); +} + +void SkeletonModificationStack3D::set_modification(int p_mod_idx, Ref<SkeletonModification3D> p_mod) { + const int modifications_size = modifications.size(); + ERR_FAIL_INDEX(p_mod_idx, modifications_size); + + if (p_mod == nullptr) { + modifications.remove(p_mod_idx); + } else { + p_mod->_setup_modification(this); + modifications[p_mod_idx] = p_mod; + } +} + +void SkeletonModificationStack3D::set_modification_count(int p_count) { + modifications.resize(p_count); + notify_property_list_changed(); +} + +int SkeletonModificationStack3D::get_modification_count() const { + return modifications.size(); +} + +void SkeletonModificationStack3D::set_skeleton(Skeleton3D *p_skeleton) { + skeleton = p_skeleton; +} + +Skeleton3D *SkeletonModificationStack3D::get_skeleton() const { + return skeleton; +} + +bool SkeletonModificationStack3D::get_is_setup() const { + return is_setup; +} + +void SkeletonModificationStack3D::set_enabled(bool p_enabled) { + enabled = p_enabled; + + if (!enabled && is_setup && skeleton != nullptr) { + skeleton->clear_bones_local_pose_override(); + } +} + +bool SkeletonModificationStack3D::get_enabled() const { + return enabled; +} + +void SkeletonModificationStack3D::set_strength(real_t p_strength) { + ERR_FAIL_COND_MSG(p_strength < 0, "Strength cannot be less than zero!"); + ERR_FAIL_COND_MSG(p_strength > 1, "Strength cannot be more than one!"); + strength = p_strength; +} + +real_t SkeletonModificationStack3D::get_strength() const { + return strength; +} + +void SkeletonModificationStack3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("setup"), &SkeletonModificationStack3D::setup); + ClassDB::bind_method(D_METHOD("execute", "delta", "execution_mode"), &SkeletonModificationStack3D::execute); + + ClassDB::bind_method(D_METHOD("enable_all_modifications", "enabled"), &SkeletonModificationStack3D::enable_all_modifications); + ClassDB::bind_method(D_METHOD("get_modification", "mod_idx"), &SkeletonModificationStack3D::get_modification); + ClassDB::bind_method(D_METHOD("add_modification", "modification"), &SkeletonModificationStack3D::add_modification); + ClassDB::bind_method(D_METHOD("delete_modification", "mod_idx"), &SkeletonModificationStack3D::delete_modification); + ClassDB::bind_method(D_METHOD("set_modification", "mod_idx", "modification"), &SkeletonModificationStack3D::set_modification); + + ClassDB::bind_method(D_METHOD("set_modification_count"), &SkeletonModificationStack3D::set_modification_count); + ClassDB::bind_method(D_METHOD("get_modification_count"), &SkeletonModificationStack3D::get_modification_count); + + ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModificationStack3D::get_is_setup); + + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModificationStack3D::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModificationStack3D::get_enabled); + + ClassDB::bind_method(D_METHOD("set_strength", "strength"), &SkeletonModificationStack3D::set_strength); + ClassDB::bind_method(D_METHOD("get_strength"), &SkeletonModificationStack3D::get_strength); + + ClassDB::bind_method(D_METHOD("get_skeleton"), &SkeletonModificationStack3D::get_skeleton); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0, 1, 0.001"), "set_strength", "get_strength"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_modification_count", "get_modification_count"); +} + +SkeletonModificationStack3D::SkeletonModificationStack3D() { +} diff --git a/scene/resources/skeleton_modification_stack_3d.h b/scene/resources/skeleton_modification_stack_3d.h new file mode 100644 index 0000000000..cbc8d4e0b9 --- /dev/null +++ b/scene/resources/skeleton_modification_stack_3d.h @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* skeleton_modification_stack_3d.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. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATIONSTACK3D_H +#define SKELETONMODIFICATIONSTACK3D_H + +#include "core/templates/local_vector.h" +#include "scene/3d/skeleton_3d.h" + +class Skeleton3D; +class SkeletonModification3D; + +class SkeletonModificationStack3D : public Resource { + GDCLASS(SkeletonModificationStack3D, Resource); + friend class Skeleton3D; + friend class SkeletonModification3D; + +protected: + static void _bind_methods(); + virtual void _get_property_list(List<PropertyInfo> *p_list) const; + virtual bool _set(const StringName &p_path, const Variant &p_value); + virtual bool _get(const StringName &p_path, Variant &r_ret) const; + +public: + Skeleton3D *skeleton = nullptr; + bool is_setup = false; + bool enabled = false; + real_t strength = 1.0; + + enum EXECUTION_MODE { + execution_mode_process, + execution_mode_physics_process, + }; + + LocalVector<Ref<SkeletonModification3D>> modifications = LocalVector<Ref<SkeletonModification3D>>(); + int modifications_count = 0; + + virtual void setup(); + virtual void execute(real_t p_delta, int p_execution_mode); + + void enable_all_modifications(bool p_enable); + Ref<SkeletonModification3D> get_modification(int p_mod_idx) const; + void add_modification(Ref<SkeletonModification3D> p_mod); + void delete_modification(int p_mod_idx); + void set_modification(int p_mod_idx, Ref<SkeletonModification3D> p_mod); + + void set_modification_count(int p_count); + int get_modification_count() const; + + void set_skeleton(Skeleton3D *p_skeleton); + Skeleton3D *get_skeleton() const; + + bool get_is_setup() const; + + void set_enabled(bool p_enabled); + bool get_enabled() const; + + void set_strength(real_t p_strength); + real_t get_strength() const; + + SkeletonModificationStack3D(); +}; + +#endif // SKELETONMODIFICATIONSTACK3D_H diff --git a/scene/resources/sky_material.cpp b/scene/resources/sky_material.cpp index ec00f9d7b7..39082b6f7a 100644 --- a/scene/resources/sky_material.cpp +++ b/scene/resources/sky_material.cpp @@ -30,6 +30,8 @@ #include "sky_material.h" +#include "core/version.h" + Mutex ProceduralSkyMaterial::shader_mutex; RID ProceduralSkyMaterial::shader; @@ -204,7 +206,10 @@ void ProceduralSkyMaterial::_update_shader() { if (shader.is_null()) { shader = RS::get_singleton()->shader_create(); + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). RS::get_singleton()->shader_set_code(shader, R"( +// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s ProceduralSkyMaterial. + shader_type sky; uniform vec4 sky_top_color : hint_color = vec4(0.35, 0.46, 0.71, 1.0); @@ -350,10 +355,13 @@ void PanoramaSkyMaterial::_update_shader() { if (shader.is_null()) { shader = RS::get_singleton()->shader_create(); + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). RS::get_singleton()->shader_set_code(shader, R"( +// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s PanoramaSkyMaterial. + shader_type sky; -uniform sampler2D source_panorama : filter_linear; +uniform sampler2D source_panorama : filter_linear, hint_albedo; void sky() { COLOR = texture(source_panorama, SKY_COORDS).rgb; @@ -561,7 +569,10 @@ void PhysicalSkyMaterial::_update_shader() { if (shader.is_null()) { shader = RS::get_singleton()->shader_create(); + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). RS::get_singleton()->shader_set_code(shader, R"( +// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s PhysicalSkyMaterial. + shader_type sky; uniform float rayleigh : hint_range(0, 64) = 2.0; @@ -576,7 +587,7 @@ uniform vec4 ground_color : hint_color = vec4(1.0); uniform float exposure : hint_range(0, 128) = 0.1; uniform float dither_strength : hint_range(0, 10) = 1.0; -uniform sampler2D night_sky : hint_black; +uniform sampler2D night_sky : hint_black_albedo; const vec3 UP = vec3( 0.0, 1.0, 0.0 ); diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index 173ce2adce..e1c5ebb005 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -43,12 +43,8 @@ Dictionary SyntaxHighlighter::get_line_syntax_highlighting(int p_line) { return color_map; } - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_get_line_syntax_highlighting")) { - color_map = si->call("_get_line_syntax_highlighting", p_line); - } else { - color_map = _get_line_syntax_highlighting(p_line); - } + GDVIRTUAL_CALL(_get_line_syntax_highlighting, p_line, color_map); + highlighting_cache[p_line] = color_map; return color_map; } @@ -69,9 +65,7 @@ void SyntaxHighlighter::_lines_edited_from(int p_from_line, int p_to_line) { void SyntaxHighlighter::clear_highlighting_cache() { highlighting_cache.clear(); - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_clear_highlighting_cache")) { - si->call("_clear_highlighting_cache"); + if (GDVIRTUAL_CALL(_clear_highlighting_cache)) { return; } _clear_highlighting_cache(); @@ -83,9 +77,7 @@ void SyntaxHighlighter::update_cache() { if (text_edit == nullptr) { return; } - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_update_cache")) { - si->call("_update_cache"); + if (GDVIRTUAL_CALL(_update_cache)) { return; } _update_cache(); @@ -115,9 +107,9 @@ void SyntaxHighlighter::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_highlighting_cache"), &SyntaxHighlighter::clear_highlighting_cache); ClassDB::bind_method(D_METHOD("get_text_edit"), &SyntaxHighlighter::get_text_edit); - BIND_VMETHOD(MethodInfo(Variant::DICTIONARY, "_get_line_syntax_highlighting", PropertyInfo(Variant::INT, "line"))); - BIND_VMETHOD(MethodInfo("_clear_highlighting_cache")); - BIND_VMETHOD(MethodInfo("_update_cache")); + GDVIRTUAL_BIND(_get_line_syntax_highlighting, "line") + GDVIRTUAL_BIND(_clear_highlighting_cache) + GDVIRTUAL_BIND(_update_cache) } //////////////////////////////////////////////////////////////////////////////// @@ -130,7 +122,7 @@ static bool _is_hex_symbol(char32_t c) { return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } -Dictionary CodeHighlighter::_get_line_syntax_highlighting(int p_line) { +Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; bool prev_is_char = false; diff --git a/scene/resources/syntax_highlighter.h b/scene/resources/syntax_highlighter.h index f3964b0c8f..0fe39ccff6 100644 --- a/scene/resources/syntax_highlighter.h +++ b/scene/resources/syntax_highlighter.h @@ -32,6 +32,8 @@ #define SYNTAX_HIGHLIGHTER_H #include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" class TextEdit; @@ -48,9 +50,12 @@ protected: static void _bind_methods(); + GDVIRTUAL1RC(Dictionary, _get_line_syntax_highlighting, int) + GDVIRTUAL0(_clear_highlighting_cache) + GDVIRTUAL0(_update_cache) public: Dictionary get_line_syntax_highlighting(int p_line); - virtual Dictionary _get_line_syntax_highlighting(int p_line) { return Dictionary(); } + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) { return Dictionary(); } void clear_highlighting_cache(); virtual void _clear_highlighting_cache() {} @@ -93,7 +98,7 @@ protected: static void _bind_methods(); public: - virtual Dictionary _get_line_syntax_highlighting(int p_line) override; + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override; virtual void _clear_highlighting_cache() override; virtual void _update_cache() override; diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 357411ae04..763033354a 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -437,7 +437,8 @@ Size2 TextParagraph::get_non_wraped_size() const { Size2 TextParagraph::get_size() const { const_cast<TextParagraph *>(this)->_shape_lines(); Size2 size; - for (int i = 0; i < lines_rid.size(); i++) { + int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size(); + for (int i = 0; i < visible_lines; i++) { Size2 lsize = TS->shaped_text_get_size(lines_rid[i]); if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { size.x = MAX(size.x, lsize.x); @@ -587,15 +588,15 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo l_width -= h_offset; } } - float length = TS->shaped_text_get_width(lines_rid[i]); + float line_width = TS->shaped_text_get_width(lines_rid[i]); if (width > 0) { switch (align) { case HALIGN_FILL: if (TS->shaped_text_get_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) { if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.x += l_width - length; + ofs.x += l_width - line_width; } else { - ofs.y += l_width - length; + ofs.y += l_width - line_width; } } break; @@ -603,16 +604,16 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo break; case HALIGN_CENTER: { if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.x += Math::floor((l_width - length) / 2.0); + ofs.x += Math::floor((l_width - line_width) / 2.0); } else { - ofs.y += Math::floor((l_width - length) / 2.0); + ofs.y += Math::floor((l_width - line_width) / 2.0); } } break; case HALIGN_RIGHT: { if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.x += l_width - length; + ofs.x += l_width - line_width; } else { - ofs.y += l_width - length; + ofs.y += l_width - line_width; } } break; } diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index e4a731c7f7..e49d883ba4 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -1350,40 +1350,36 @@ void Theme::clear() { _emit_theme_changed(); } -void Theme::copy_default_theme() { - Ref<Theme> default_theme2 = get_default(); - copy_theme(default_theme2); -} - -void Theme::copy_theme(const Ref<Theme> &p_other) { +void Theme::merge_with(const Ref<Theme> &p_other) { if (p_other.is_null()) { - clear(); return; } _freeze_change_propagation(); - // These items need reconnecting, so add them normally. + // Colors. { const StringName *K = nullptr; - while ((K = p_other->icon_map.next(K))) { + while ((K = p_other->color_map.next(K))) { const StringName *L = nullptr; - while ((L = p_other->icon_map[*K].next(L))) { - set_icon(*L, *K, p_other->icon_map[*K][*L]); + while ((L = p_other->color_map[*K].next(L))) { + set_color(*L, *K, p_other->color_map[*K][*L]); } } } + // Constants. { const StringName *K = nullptr; - while ((K = p_other->style_map.next(K))) { + while ((K = p_other->constant_map.next(K))) { const StringName *L = nullptr; - while ((L = p_other->style_map[*K].next(L))) { - set_stylebox(*L, *K, p_other->style_map[*K][*L]); + while ((L = p_other->constant_map[*K].next(L))) { + set_constant(*L, *K, p_other->constant_map[*K][*L]); } } } + // Fonts. { const StringName *K = nullptr; while ((K = p_other->font_map.next(K))) { @@ -1394,13 +1390,46 @@ void Theme::copy_theme(const Ref<Theme> &p_other) { } } - // These items can be simply copied. - font_size_map = p_other->font_size_map; - color_map = p_other->color_map; - constant_map = p_other->constant_map; + // Font sizes. + { + const StringName *K = nullptr; + while ((K = p_other->font_size_map.next(K))) { + const StringName *L = nullptr; + while ((L = p_other->font_size_map[*K].next(L))) { + set_font_size(*L, *K, p_other->font_size_map[*K][*L]); + } + } + } - variation_map = p_other->variation_map; - variation_base_map = p_other->variation_base_map; + // Icons. + { + const StringName *K = nullptr; + while ((K = p_other->icon_map.next(K))) { + const StringName *L = nullptr; + while ((L = p_other->icon_map[*K].next(L))) { + set_icon(*L, *K, p_other->icon_map[*K][*L]); + } + } + } + + // Styleboxes. + { + const StringName *K = nullptr; + while ((K = p_other->style_map.next(K))) { + const StringName *L = nullptr; + while ((L = p_other->style_map[*K].next(L))) { + set_stylebox(*L, *K, p_other->style_map[*K][*L]); + } + } + } + + // Type variations. + { + const StringName *K = nullptr; + while ((K = p_other->variation_map.next(K))) { + set_type_variation(*K, p_other->variation_map[*K]); + } + } _unfreeze_and_propagate_changes(); } @@ -1534,8 +1563,6 @@ void Theme::_bind_methods() { ClassDB::bind_method(D_METHOD("get_constant_list", "theme_type"), &Theme::_get_constant_list); ClassDB::bind_method(D_METHOD("get_constant_type_list"), &Theme::_get_constant_type_list); - ClassDB::bind_method(D_METHOD("clear"), &Theme::clear); - ClassDB::bind_method(D_METHOD("set_default_font", "font"), &Theme::set_default_theme_font); ClassDB::bind_method(D_METHOD("get_default_font"), &Theme::get_default_theme_font); @@ -1558,8 +1585,8 @@ void Theme::_bind_methods() { ClassDB::bind_method(D_METHOD("get_type_list"), &Theme::_get_type_list); - ClassDB::bind_method("copy_default_theme", &Theme::copy_default_theme); - ClassDB::bind_method(D_METHOD("copy_theme", "other"), &Theme::copy_theme); + ClassDB::bind_method(D_METHOD("merge_with", "other"), &Theme::merge_with); + ClassDB::bind_method(D_METHOD("clear"), &Theme::clear); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_default_font", "get_default_font"); ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size"), "set_default_font_size", "get_default_font_size"); diff --git a/scene/resources/theme.h b/scene/resources/theme.h index 8a8fc28be1..15f21b91b8 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -210,8 +210,7 @@ public: void get_type_list(List<StringName> *p_list) const; void get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variant, List<StringName> *p_list); - void copy_default_theme(); - void copy_theme(const Ref<Theme> &p_other); + void merge_with(const Ref<Theme> &p_other); void clear(); Theme(); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index b00dcca004..a7f99a2113 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -261,54 +261,47 @@ VisualShaderNode::VisualShaderNode() { ///////////////////////////////////////////////////////// void VisualShaderNodeCustom::update_ports() { - ERR_FAIL_COND(!get_script_instance()); + { + input_ports.clear(); + int input_port_count; + if (GDVIRTUAL_CALL(_get_input_port_count, input_port_count)) { + for (int i = 0; i < input_port_count; i++) { + Port port; + if (!GDVIRTUAL_CALL(_get_input_port_name, i, port.name)) { + port.name = "in" + itos(i); + } + if (!GDVIRTUAL_CALL(_get_input_port_type, i, port.type)) { + port.type = (int)PortType::PORT_TYPE_SCALAR; + } - input_ports.clear(); - if (get_script_instance()->has_method("_get_input_port_count")) { - int input_port_count = (int)get_script_instance()->call("_get_input_port_count"); - bool has_name = get_script_instance()->has_method("_get_input_port_name"); - bool has_type = get_script_instance()->has_method("_get_input_port_type"); - for (int i = 0; i < input_port_count; i++) { - Port port; - if (has_name) { - port.name = (String)get_script_instance()->call("_get_input_port_name", i); - } else { - port.name = "in" + itos(i); + input_ports.push_back(port); } - if (has_type) { - port.type = (int)get_script_instance()->call("_get_input_port_type", i); - } else { - port.type = (int)PortType::PORT_TYPE_SCALAR; - } - input_ports.push_back(port); } } - output_ports.clear(); - if (get_script_instance()->has_method("_get_output_port_count")) { - int output_port_count = (int)get_script_instance()->call("_get_output_port_count"); - bool has_name = get_script_instance()->has_method("_get_output_port_name"); - bool has_type = get_script_instance()->has_method("_get_output_port_type"); - for (int i = 0; i < output_port_count; i++) { - Port port; - if (has_name) { - port.name = (String)get_script_instance()->call("_get_output_port_name", i); - } else { - port.name = "out" + itos(i); - } - if (has_type) { - port.type = (int)get_script_instance()->call("_get_output_port_type", i); - } else { - port.type = (int)PortType::PORT_TYPE_SCALAR; + + { + output_ports.clear(); + int output_port_count; + if (GDVIRTUAL_CALL(_get_output_port_count, output_port_count)) { + for (int i = 0; i < output_port_count; i++) { + Port port; + if (!GDVIRTUAL_CALL(_get_output_port_name, i, port.name)) { + port.name = "out" + itos(i); + } + if (!GDVIRTUAL_CALL(_get_output_port_type, i, port.type)) { + port.type = (int)PortType::PORT_TYPE_SCALAR; + } + + output_ports.push_back(port); } - output_ports.push_back(port); } } } String VisualShaderNodeCustom::get_caption() const { - ERR_FAIL_COND_V(!get_script_instance(), ""); - if (get_script_instance()->has_method("_get_name")) { - return (String)get_script_instance()->call("_get_name"); + String ret; + if (GDVIRTUAL_CALL(_get_name, ret)) { + return ret; } return "Unnamed"; } @@ -342,9 +335,8 @@ String VisualShaderNodeCustom::get_output_port_name(int p_port) const { } String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - ERR_FAIL_COND_V(!get_script_instance(), ""); - ERR_FAIL_COND_V(!get_script_instance()->has_method("_get_code"), ""); - Array input_vars; + ERR_FAIL_COND_V(!GDVIRTUAL_IS_OVERRIDEN(_get_code), ""); + Vector<String> input_vars; for (int i = 0; i < get_input_port_count(); i++) { input_vars.push_back(p_input_vars[i]); } @@ -353,7 +345,8 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader:: output_vars.push_back(p_output_vars[i]); } String code = " {\n"; - String _code = (String)get_script_instance()->call("_get_code", input_vars, output_vars, (int)p_mode, (int)p_type); + String _code; + GDVIRTUAL_CALL(_get_code, input_vars, output_vars, (int)p_mode, (int)p_type, _code); bool nend = _code.ends_with("\n"); _code = _code.insert(0, " "); _code = _code.replace("\n", "\n "); @@ -369,10 +362,10 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader:: } String VisualShaderNodeCustom::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { - ERR_FAIL_COND_V(!get_script_instance(), ""); - if (get_script_instance()->has_method("_get_global_code")) { + String ret; + if (GDVIRTUAL_CALL(_get_global_code, (int)p_mode, ret)) { String code = "// " + get_caption() + "\n"; - code += (String)get_script_instance()->call("_get_global_code", (int)p_mode); + code += ret; code += "\n"; return code; } @@ -416,19 +409,19 @@ void VisualShaderNodeCustom::_set_initialized(bool p_enabled) { } void VisualShaderNodeCustom::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_name")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_description")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_category")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_return_icon_type")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_port_count")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_port_type", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::STRING_NAME, "_get_input_port_name", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_port_count")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_port_type", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::STRING_NAME, "_get_output_port_name", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_code", PropertyInfo(Variant::ARRAY, "input_vars"), PropertyInfo(Variant::ARRAY, "output_vars"), PropertyInfo(Variant::INT, "mode"), PropertyInfo(Variant::INT, "type"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_global_code", PropertyInfo(Variant::INT, "mode"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_highend")); + GDVIRTUAL_BIND(_get_name); + GDVIRTUAL_BIND(_get_description); + GDVIRTUAL_BIND(_get_category); + GDVIRTUAL_BIND(_get_return_icon_type); + GDVIRTUAL_BIND(_get_input_port_count); + GDVIRTUAL_BIND(_get_input_port_type, "port"); + GDVIRTUAL_BIND(_get_input_port_name, "port"); + GDVIRTUAL_BIND(_get_output_port_count); + GDVIRTUAL_BIND(_get_output_port_type, "port"); + GDVIRTUAL_BIND(_get_output_port_name, "port"); + GDVIRTUAL_BIND(_get_code, "input_vars", "output_vars", "mode", "type"); + GDVIRTUAL_BIND(_get_global_code, "mode"); + GDVIRTUAL_BIND(_is_highend); ClassDB::bind_method(D_METHOD("_set_initialized", "enabled"), &VisualShaderNodeCustom::_set_initialized); ClassDB::bind_method(D_METHOD("_is_initialized"), &VisualShaderNodeCustom::_is_initialized); diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 31651318ae..b3efac02aa 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -314,6 +314,20 @@ protected: virtual void remove_input_port_default_value(int p_port) override; virtual void clear_default_input_values() override; + GDVIRTUAL0RC(String, _get_name) + GDVIRTUAL0RC(String, _get_description) + GDVIRTUAL0RC(String, _get_category) + GDVIRTUAL0RC(int, _get_return_icon_type) + GDVIRTUAL0RC(int, _get_input_port_count) + GDVIRTUAL1RC(int, _get_input_port_type, int) + GDVIRTUAL1RC(String, _get_input_port_name, int) + GDVIRTUAL0RC(int, _get_output_port_count) + GDVIRTUAL1RC(int, _get_output_port_type, int) + GDVIRTUAL1RC(String, _get_output_port_name, int) + GDVIRTUAL4RC(String, _get_code, Vector<String>, TypedArray<String>, int, int) + GDVIRTUAL1RC(String, _get_global_code, int) + GDVIRTUAL0RC(bool, _is_highend) + protected: void _set_input_port_default_value(int p_port, const Variant &p_value); diff --git a/scene/resources/visual_shader_particle_nodes.h b/scene/resources/visual_shader_particle_nodes.h index ecd187a885..f5435c3511 100644 --- a/scene/resources/visual_shader_particle_nodes.h +++ b/scene/resources/visual_shader_particle_nodes.h @@ -181,8 +181,8 @@ VARIANT_ENUM_CAST(VisualShaderNodeParticleRandomness::OpType) // Process nodes -class VisualShaderNodeParticleAccelerator : public VisualShaderNodeOutput { - GDCLASS(VisualShaderNodeParticleAccelerator, VisualShaderNodeOutput); +class VisualShaderNodeParticleAccelerator : public VisualShaderNode { + GDCLASS(VisualShaderNodeParticleAccelerator, VisualShaderNode); public: enum Mode { diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index b8173c9623..5d89d295c2 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -63,6 +63,7 @@ SceneStringNames::SceneStringNames() { animation_started = StaticCString::create("animation_started"); pose_updated = StaticCString::create("pose_updated"); + bone_pose_changed = StaticCString::create("bone_pose_changed"); mouse_entered = StaticCString::create("mouse_entered"); mouse_exited = StaticCString::create("mouse_exited"); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index eddb0c33eb..01f427ecd1 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -98,6 +98,7 @@ public: StringName animation_started; StringName pose_updated; + StringName bone_pose_changed; StringName body_shape_entered; StringName body_entered; |