summaryrefslogtreecommitdiffstats
path: root/scene/3d
diff options
context:
space:
mode:
Diffstat (limited to 'scene/3d')
-rw-r--r--scene/3d/audio_stream_player_3d.cpp13
-rw-r--r--scene/3d/bone_attachment_3d.cpp20
-rw-r--r--scene/3d/bone_attachment_3d.h3
-rw-r--r--scene/3d/camera_3d.cpp138
-rw-r--r--scene/3d/camera_3d.h32
-rw-r--r--scene/3d/cpu_particles_3d.cpp29
-rw-r--r--scene/3d/cpu_particles_3d.h3
-rw-r--r--scene/3d/decal.cpp2
-rw-r--r--scene/3d/fog_volume.cpp2
-rw-r--r--scene/3d/gpu_particles_3d.cpp22
-rw-r--r--scene/3d/gpu_particles_collision_3d.cpp2
-rw-r--r--scene/3d/label_3d.cpp22
-rw-r--r--scene/3d/lightmap_gi.cpp24
-rw-r--r--scene/3d/lightmap_gi.h5
-rw-r--r--scene/3d/mesh_instance_3d.cpp171
-rw-r--r--scene/3d/mesh_instance_3d.h1
-rw-r--r--scene/3d/multimesh_instance_3d.cpp19
-rw-r--r--scene/3d/multimesh_instance_3d.h5
-rw-r--r--scene/3d/navigation_link_3d.cpp108
-rw-r--r--scene/3d/navigation_link_3d.h9
-rw-r--r--scene/3d/navigation_region_3d.cpp19
-rw-r--r--scene/3d/node_3d.cpp147
-rw-r--r--scene/3d/node_3d.h45
-rw-r--r--scene/3d/occluder_instance_3d.cpp5
-rw-r--r--scene/3d/occluder_instance_3d.h1
-rw-r--r--scene/3d/path_3d.cpp61
-rw-r--r--scene/3d/path_3d.h3
-rw-r--r--scene/3d/physical_bone_simulator_3d.cpp57
-rw-r--r--scene/3d/physical_bone_simulator_3d.h2
-rw-r--r--scene/3d/physics/collision_object_3d.cpp2
-rw-r--r--scene/3d/physics/collision_polygon_3d.cpp2
-rw-r--r--scene/3d/physics/collision_shape_3d.cpp2
-rw-r--r--scene/3d/physics/shape_cast_3d.cpp6
-rw-r--r--scene/3d/physics/shape_cast_3d.h3
-rw-r--r--scene/3d/physics/vehicle_body_3d.cpp18
-rw-r--r--scene/3d/physics/vehicle_body_3d.h4
-rw-r--r--scene/3d/remote_transform_3d.cpp12
-rw-r--r--scene/3d/skeleton_3d.cpp79
-rw-r--r--scene/3d/skeleton_3d.h9
-rw-r--r--scene/3d/skeleton_ik_3d.cpp6
-rw-r--r--scene/3d/skeleton_modifier_3d.cpp2
-rw-r--r--scene/3d/soft_body_3d.compat.inc41
-rw-r--r--scene/3d/soft_body_3d.cpp38
-rw-r--r--scene/3d/soft_body_3d.h9
-rw-r--r--scene/3d/sprite_3d.cpp6
-rw-r--r--scene/3d/visual_instance_3d.cpp80
-rw-r--r--scene/3d/visual_instance_3d.h3
-rw-r--r--scene/3d/voxel_gi.cpp2
-rw-r--r--scene/3d/voxelizer.h3
-rw-r--r--scene/3d/xr_hand_modifier_3d.cpp17
-rw-r--r--scene/3d/xr_hand_modifier_3d.h2
-rw-r--r--scene/3d/xr_nodes.cpp26
-rw-r--r--scene/3d/xr_nodes.h2
53 files changed, 1109 insertions, 235 deletions
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 6888462876..591528b915 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -562,7 +562,7 @@ void AudioStreamPlayer3D::seek(float p_seconds) {
void AudioStreamPlayer3D::stop() {
setplay.set(-1);
- internal->stop();
+ internal->stop_basic();
}
bool AudioStreamPlayer3D::is_playing() const {
@@ -596,10 +596,6 @@ void AudioStreamPlayer3D::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
-bool AudioStreamPlayer3D::_is_active() const {
- return internal->is_active();
-}
-
void AudioStreamPlayer3D::_validate_property(PropertyInfo &p_property) const {
internal->validate_property(p_property);
}
@@ -779,8 +775,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer3D::set_autoplay);
ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer3D::is_autoplay_enabled);
- ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer3D::_set_playing);
- ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer3D::_is_active);
+ ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer3D::_set_playing);
ClassDB::bind_method(D_METHOD("set_max_distance", "meters"), &AudioStreamPlayer3D::set_max_distance);
ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer3D::get_max_distance);
@@ -830,7 +825,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater"), "set_unit_size", "get_unit_size");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6,suffix:dB"), "set_max_db", "get_max_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,or_greater,suffix:m"), "set_max_distance", "get_max_distance");
@@ -862,7 +857,7 @@ void AudioStreamPlayer3D::_bind_methods() {
}
AudioStreamPlayer3D::AudioStreamPlayer3D() {
- internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer3D::play), true));
+ internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer3D::play), callable_mp(this, &AudioStreamPlayer3D::stop), true));
velocity_tracker.instantiate();
set_disable_scale(true);
cached_global_panning_strength = GLOBAL_GET("audio/general/3d_panning_strength");
diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp
index 4889512037..7fe1b7079a 100644
--- a/scene/3d/bone_attachment_3d.cpp
+++ b/scene/3d/bone_attachment_3d.cpp
@@ -33,7 +33,7 @@
void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "bone_name") {
- // Because it is a constant function, we cannot use the _get_skeleton_3d function.
+ // Because it is a constant function, we cannot use the get_skeleton function.
const Skeleton3D *parent = nullptr;
if (use_external_skeleton) {
if (external_skeleton_node_cache.is_valid()) {
@@ -134,7 +134,7 @@ void BoneAttachment3D::_update_external_skeleton_cache() {
}
void BoneAttachment3D::_check_bind() {
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk && !bound) {
if (bone_idx <= -1) {
@@ -148,7 +148,7 @@ void BoneAttachment3D::_check_bind() {
}
}
-Skeleton3D *BoneAttachment3D::_get_skeleton3d() {
+Skeleton3D *BoneAttachment3D::get_skeleton() {
if (use_external_skeleton) {
if (external_skeleton_node_cache.is_valid()) {
return Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache));
@@ -166,7 +166,7 @@ Skeleton3D *BoneAttachment3D::_get_skeleton3d() {
void BoneAttachment3D::_check_unbind() {
if (bound) {
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk) {
sk->disconnect(SceneStringName(skeleton_updated), callable_mp(this, &BoneAttachment3D::on_skeleton_update));
@@ -181,7 +181,7 @@ void BoneAttachment3D::_transform_changed() {
}
if (override_pose && !overriding) {
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
ERR_FAIL_NULL_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!");
@@ -200,7 +200,7 @@ void BoneAttachment3D::_transform_changed() {
void BoneAttachment3D::set_bone_name(const String &p_name) {
bone_name = p_name;
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk) {
set_bone_idx(sk->find_bone(bone_name));
}
@@ -217,7 +217,7 @@ void BoneAttachment3D::set_bone_idx(const int &p_idx) {
bone_idx = p_idx;
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk) {
if (bone_idx <= -1 || bone_idx >= sk->get_bone_count()) {
WARN_PRINT("Bone index out of range! Cannot connect BoneAttachment to node!");
@@ -247,7 +247,7 @@ void BoneAttachment3D::set_override_pose(bool p_override) {
set_notify_transform(override_pose);
set_process_internal(override_pose);
if (!override_pose && bone_idx >= 0) {
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk) {
sk->reset_bone_pose(bone_idx);
}
@@ -318,7 +318,7 @@ void BoneAttachment3D::on_skeleton_update() {
}
updating = true;
if (bone_idx >= 0) {
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk) {
if (!override_pose) {
if (use_external_skeleton) {
@@ -369,6 +369,8 @@ BoneAttachment3D::BoneAttachment3D() {
}
void BoneAttachment3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_skeleton"), &BoneAttachment3D::get_skeleton);
+
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);
diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h
index e19180b0ea..5435c4ad0c 100644
--- a/scene/3d/bone_attachment_3d.h
+++ b/scene/3d/bone_attachment_3d.h
@@ -57,7 +57,6 @@ class BoneAttachment3D : public Node3D {
bool updating = false;
void _transform_changed();
void _update_external_skeleton_cache();
- Skeleton3D *_get_skeleton3d();
protected:
void _validate_property(PropertyInfo &p_property) const;
@@ -79,6 +78,8 @@ public:
virtual PackedStringArray get_configuration_warnings() const override;
+ Skeleton3D *get_skeleton();
+
void set_bone_name(const String &p_name);
String get_bone_name() const;
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index 8515aacba7..c70fa3ca2e 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -31,7 +31,9 @@
#include "camera_3d.h"
#include "core/math/projection.h"
+#include "core/math/transform_interpolator.h"
#include "scene/main/viewport.h"
+#include "servers/rendering/rendering_server_constants.h"
void Camera3D::_update_audio_listener_state() {
}
@@ -88,7 +90,16 @@ void Camera3D::_update_camera() {
return;
}
- RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform());
+ if (!is_physics_interpolated_and_enabled()) {
+ RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform());
+ } else {
+ // Ideally we shouldn't be moving a physics interpolated camera within a frame,
+ // because it will break smooth interpolation, but it may occur on e.g. level load.
+ if (!Engine::get_singleton()->is_in_physics_frame() && camera.is_valid()) {
+ _physics_interpolation_ensure_transform_calculated(true);
+ RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
+ }
+ }
if (is_part_of_edited_scene() || !is_current()) {
return;
@@ -97,6 +108,64 @@ void Camera3D::_update_camera() {
get_viewport()->_camera_3d_transform_changed_notify();
}
+void Camera3D::_physics_interpolated_changed() {
+ _update_process_mode();
+}
+
+void Camera3D::_physics_interpolation_ensure_data_flipped() {
+ // The curr -> previous update can either occur
+ // on the INTERNAL_PHYSICS_PROCESS OR
+ // on NOTIFICATION_TRANSFORM_CHANGED,
+ // if NOTIFICATION_TRANSFORM_CHANGED takes place
+ // earlier than INTERNAL_PHYSICS_PROCESS on a tick.
+ // This is to ensure that the data keeps flowing, but the new data
+ // doesn't overwrite before prev has been set.
+
+ // Keep the data flowing.
+ uint64_t tick = Engine::get_singleton()->get_physics_frames();
+ if (_interpolation_data.last_update_physics_tick != tick) {
+ _interpolation_data.xform_prev = _interpolation_data.xform_curr;
+ _interpolation_data.last_update_physics_tick = tick;
+ physics_interpolation_flip_data();
+ }
+}
+
+void Camera3D::_physics_interpolation_ensure_transform_calculated(bool p_force) const {
+ DEV_CHECK_ONCE(!Engine::get_singleton()->is_in_physics_frame());
+
+ InterpolationData &id = _interpolation_data;
+ uint64_t frame = Engine::get_singleton()->get_frames_drawn();
+
+ if (id.last_update_frame != frame || p_force) {
+ id.last_update_frame = frame;
+
+ TransformInterpolator::interpolate_transform_3d(id.xform_prev, id.xform_curr, id.xform_interpolated, Engine::get_singleton()->get_physics_interpolation_fraction());
+
+ Transform3D &tr = id.camera_xform_interpolated;
+ tr = _get_adjusted_camera_transform(id.xform_interpolated);
+ }
+}
+
+void Camera3D::set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal) {
+ _desired_process_internal = p_process_internal;
+ _desired_physics_process_internal = p_physics_process_internal;
+ _update_process_mode();
+}
+
+void Camera3D::_update_process_mode() {
+ bool process = _desired_process_internal;
+ bool physics_process = _desired_physics_process_internal;
+
+ if (is_physics_interpolated_and_enabled()) {
+ if (is_current()) {
+ process = true;
+ physics_process = true;
+ }
+ }
+ set_process_internal(process);
+ set_physics_process_internal(physics_process);
+}
+
void Camera3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_WORLD: {
@@ -118,11 +187,58 @@ void Camera3D::_notification(int p_what) {
#endif
} break;
+ case NOTIFICATION_INTERNAL_PROCESS: {
+ if (is_physics_interpolated_and_enabled() && camera.is_valid()) {
+ _physics_interpolation_ensure_transform_calculated();
+
+#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
+ print_line("\t\tinterpolated Camera3D: " + rtos(_interpolation_data.xform_interpolated.origin.x) + "\t( prev " + rtos(_interpolation_data.xform_prev.origin.x) + ", curr " + rtos(_interpolation_data.xform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames()));
+#endif
+
+ RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
+ }
+ } break;
+
+ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ if (is_physics_interpolated_and_enabled()) {
+ _physics_interpolation_ensure_data_flipped();
+ _interpolation_data.xform_curr = get_global_transform();
+ }
+ } break;
+
case NOTIFICATION_TRANSFORM_CHANGED: {
+ if (is_physics_interpolated_and_enabled()) {
+ _physics_interpolation_ensure_data_flipped();
+ _interpolation_data.xform_curr = get_global_transform();
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+ if (!Engine::get_singleton()->is_in_physics_frame()) {
+ PHYSICS_INTERPOLATION_NODE_WARNING(get_instance_id(), "Interpolated Camera3D triggered from outside physics process");
+ }
+#endif
+ }
_request_camera_update();
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
velocity_tracker->update_position(get_global_transform().origin);
}
+ // Allow auto-reset when first adding to the tree, as a convenience.
+ if (_is_physics_interpolation_reset_requested() && is_inside_tree()) {
+ _notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+ _set_physics_interpolation_reset_requested(false);
+ }
+ } break;
+
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (is_inside_tree()) {
+ _interpolation_data.xform_curr = get_global_transform();
+ _interpolation_data.xform_prev = _interpolation_data.xform_curr;
+ }
+ } break;
+
+ case NOTIFICATION_PAUSED: {
+ if (is_physics_interpolated_and_enabled() && is_inside_tree() && is_visible_in_tree()) {
+ _physics_interpolation_ensure_transform_calculated(true);
+ RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
+ }
} break;
case NOTIFICATION_EXIT_WORLD: {
@@ -151,23 +267,34 @@ void Camera3D::_notification(int p_what) {
if (viewport) {
viewport->find_world_3d()->_register_camera(this);
}
+ _update_process_mode();
} break;
case NOTIFICATION_LOST_CURRENT: {
if (viewport) {
viewport->find_world_3d()->_remove_camera(this);
}
+ _update_process_mode();
} break;
}
}
-Transform3D Camera3D::get_camera_transform() const {
- Transform3D tr = get_global_transform().orthonormalized();
+Transform3D Camera3D::_get_adjusted_camera_transform(const Transform3D &p_xform) const {
+ Transform3D tr = p_xform.orthonormalized();
tr.origin += tr.basis.get_column(1) * v_offset;
tr.origin += tr.basis.get_column(0) * h_offset;
return tr;
}
+Transform3D Camera3D::get_camera_transform() const {
+ if (is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame()) {
+ _physics_interpolation_ensure_transform_calculated();
+ return _interpolation_data.camera_xform_interpolated;
+ }
+
+ return _get_adjusted_camera_transform(get_global_transform());
+}
+
Projection Camera3D::_get_camera_projection(real_t p_near) const {
Size2 viewport_size = get_viewport()->get_visible_rect().size;
Projection cm;
@@ -379,6 +506,11 @@ Point2 Camera3D::unproject_position(const Vector3 &p_pos) const {
Plane p(get_camera_transform().xform_inv(p_pos), 1.0);
p = cm.xform4(p);
+
+ // Prevent divide by zero.
+ // TODO: Investigate, this was causing NaNs.
+ ERR_FAIL_COND_V(p.d == 0, Point2());
+
p.normal /= p.d;
Point2 res;
diff --git a/scene/3d/camera_3d.h b/scene/3d/camera_3d.h
index dbf2ffc1dd..3e9f940ad6 100644
--- a/scene/3d/camera_3d.h
+++ b/scene/3d/camera_3d.h
@@ -98,7 +98,39 @@ private:
RID pyramid_shape;
Vector<Vector3> pyramid_shape_points;
+ ///////////////////////////////////////////////////////
+ // INTERPOLATION FUNCTIONS
+ void _physics_interpolation_ensure_transform_calculated(bool p_force = false) const;
+ void _physics_interpolation_ensure_data_flipped();
+
+ // These can be set by derived Camera3Ds, if they wish to do processing
+ // (while still allowing physics interpolation to function).
+ bool _desired_process_internal = false;
+ bool _desired_physics_process_internal = false;
+
+ mutable struct InterpolationData {
+ Transform3D xform_curr;
+ Transform3D xform_prev;
+ Transform3D xform_interpolated;
+ Transform3D camera_xform_interpolated; // After modification according to camera type.
+ uint32_t last_update_physics_tick = 0;
+ uint32_t last_update_frame = UINT32_MAX;
+ } _interpolation_data;
+
+ void _update_process_mode();
+
protected:
+ // Use from derived classes to set process modes instead of setting directly.
+ // This is because physics interpolation may need to request process modes additionally.
+ void set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal);
+
+ // Opportunity for derived classes to interpolate extra attributes.
+ virtual void physics_interpolation_flip_data() {}
+
+ virtual void _physics_interpolated_changed() override;
+ virtual Transform3D _get_adjusted_camera_transform(const Transform3D &p_xform) const;
+ ///////////////////////////////////////////////////////
+
void _update_camera();
virtual void _request_camera_update();
void _update_camera_mode();
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index 03fe5e1fad..acbc443a93 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -448,6 +448,10 @@ void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) {
emission_ring_inner_radius = p_radius;
}
+void CPUParticles3D::set_emission_ring_cone_angle(real_t p_angle) {
+ emission_ring_cone_angle = p_angle;
+}
+
void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) {
scale_curve_x = p_scale_curve;
}
@@ -501,6 +505,10 @@ real_t CPUParticles3D::get_emission_ring_inner_radius() const {
return emission_ring_inner_radius;
}
+real_t CPUParticles3D::get_emission_ring_cone_angle() const {
+ return emission_ring_cone_angle;
+}
+
CPUParticles3D::EmissionShape CPUParticles3D::get_emission_shape() const {
return emission_shape;
}
@@ -878,8 +886,14 @@ void CPUParticles3D::_particles_process(double p_delta) {
}
} break;
case EMISSION_SHAPE_RING: {
+ real_t radius_clamped = MAX(0.001, emission_ring_radius);
+ real_t top_radius = MAX(radius_clamped - Math::tan(Math::deg_to_rad(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0);
+ real_t y_pos = Math::randf();
+ real_t skew = MAX(MIN(radius_clamped, top_radius) / MAX(radius_clamped, top_radius), 0.5);
+ y_pos = radius_clamped < top_radius ? Math::pow(y_pos, skew) : 1.0 - Math::pow(y_pos, skew);
real_t ring_random_angle = Math::randf() * Math_TAU;
- real_t ring_random_radius = Math::sqrt(Math::randf() * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
+ real_t ring_random_radius = Math::sqrt(Math::randf() * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
+ ring_random_radius = Math::lerp(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos);
Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized();
Vector3 ortho_axis;
if (axis.abs() == Vector3(1.0, 0.0, 0.0)) {
@@ -890,7 +904,7 @@ void CPUParticles3D::_particles_process(double p_delta) {
ortho_axis = ortho_axis.normalized();
ortho_axis.rotate(axis, ring_random_angle);
ortho_axis = ortho_axis.normalized();
- p.transform.origin = ortho_axis * ring_random_radius + (Math::randf() * emission_ring_height - emission_ring_height / 2.0) * axis;
+ p.transform.origin = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis;
} break;
case EMISSION_SHAPE_MAX: { // Max value for validity check.
break;
@@ -1550,6 +1564,9 @@ void CPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &CPUParticles3D::set_emission_ring_inner_radius);
ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &CPUParticles3D::get_emission_ring_inner_radius);
+ ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &CPUParticles3D::set_emission_ring_cone_angle);
+ ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &CPUParticles3D::get_emission_ring_cone_angle);
+
ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity);
ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity);
@@ -1577,9 +1594,10 @@ void CPUParticles3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle");
ADD_GROUP("Particle Flags", "particle_flag_");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y);
@@ -1716,6 +1734,7 @@ CPUParticles3D::CPUParticles3D() {
set_emission_ring_height(1);
set_emission_ring_radius(1);
set_emission_ring_inner_radius(0);
+ set_emission_ring_cone_angle(90);
set_gravity(Vector3(0, -9.8, 0));
diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h
index 82ea4bbef3..978bb64e71 100644
--- a/scene/3d/cpu_particles_3d.h
+++ b/scene/3d/cpu_particles_3d.h
@@ -178,6 +178,7 @@ private:
real_t emission_ring_height = 0.0;
real_t emission_ring_radius = 0.0;
real_t emission_ring_inner_radius = 0.0;
+ real_t emission_ring_cone_angle = 0.0;
Ref<Curve> scale_curve_x;
Ref<Curve> scale_curve_y;
@@ -282,6 +283,7 @@ 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_emission_ring_cone_angle(real_t p_angle);
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);
@@ -297,6 +299,7 @@ public:
real_t get_emission_ring_height() const;
real_t get_emission_ring_radius() const;
real_t get_emission_ring_inner_radius() const;
+ real_t get_emission_ring_cone_angle() const;
Ref<Curve> get_scale_curve_x() const;
Ref<Curve> get_scale_curve_y() const;
Ref<Curve> get_scale_curve_z() const;
diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp
index 485599d0fb..8702b1d3da 100644
--- a/scene/3d/decal.cpp
+++ b/scene/3d/decal.cpp
@@ -163,7 +163,7 @@ void Decal::_validate_property(PropertyInfo &p_property) const {
}
PackedStringArray Decal::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile rendering backends."));
diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp
index 54631a8dff..195074ba2f 100644
--- a/scene/3d/fog_volume.cpp
+++ b/scene/3d/fog_volume.cpp
@@ -116,7 +116,7 @@ AABB FogVolume::get_aabb() const {
}
PackedStringArray FogVolume::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
Ref<Environment> environment = get_viewport()->find_world_3d()->get_environment();
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index 3771b385e5..2cef607d29 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -459,14 +459,6 @@ void GPUParticles3D::_notification(int p_what) {
// Use internal process when emitting and one_shot is on so that when
// the shot ends the editor can properly update.
case NOTIFICATION_INTERNAL_PROCESS: {
- const Vector3 velocity = (get_global_position() - previous_position) / get_process_delta_time();
-
- if (velocity != previous_velocity) {
- RS::get_singleton()->particles_set_emitter_velocity(particles, velocity);
- previous_velocity = velocity;
- }
- previous_position = get_global_position();
-
if (one_shot) {
time += get_process_delta_time();
if (time > emission_time) {
@@ -487,8 +479,21 @@ void GPUParticles3D::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ // Update velocity in physics process, so that velocity calculations remain correct
+ // if the physics tick rate is lower than the rendered framerate (especially without physics interpolation).
+ const Vector3 velocity = (get_global_position() - previous_position) / get_physics_process_delta_time();
+
+ if (velocity != previous_velocity) {
+ RS::get_singleton()->particles_set_emitter_velocity(particles, velocity);
+ previous_velocity = velocity;
+ }
+ previous_position = get_global_position();
+ } break;
+
case NOTIFICATION_ENTER_TREE: {
set_process_internal(false);
+ set_physics_process_internal(false);
if (sub_emitter != NodePath()) {
_attach_sub_emitter();
}
@@ -499,6 +504,7 @@ void GPUParticles3D::_notification(int p_what) {
}
previous_position = get_global_transform().origin;
set_process_internal(true);
+ set_physics_process_internal(true);
} break;
case NOTIFICATION_EXIT_TREE: {
diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp
index 3a05ec9c9e..9791f23bc3 100644
--- a/scene/3d/gpu_particles_collision_3d.cpp
+++ b/scene/3d/gpu_particles_collision_3d.cpp
@@ -524,7 +524,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() {
}
PackedStringArray GPUParticlesCollisionSDF3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = GPUParticlesCollision3D::get_configuration_warnings();
if (bake_mask == 0) {
warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any collision for this GPUParticlesCollisionSDF3D.\nTo resolve this, enable at least one bit in the Bake Mask property."));
diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp
index 54adafbefb..6b3510a72a 100644
--- a/scene/3d/label_3d.cpp
+++ b/scene/3d/label_3d.cpp
@@ -30,7 +30,7 @@
#include "label_3d.h"
-#include "scene/main/viewport.h"
+#include "scene/main/window.h"
#include "scene/resources/theme.h"
#include "scene/theme/theme_db.h"
@@ -197,14 +197,14 @@ void Label3D::_notification(int p_what) {
if (!pending_update) {
_im_update();
}
- Viewport *viewport = get_viewport();
- ERR_FAIL_NULL(viewport);
- viewport->connect("size_changed", callable_mp(this, &Label3D::_font_changed));
+ Window *window = get_window();
+ ERR_FAIL_NULL(window);
+ window->connect("size_changed", callable_mp(this, &Label3D::_font_changed));
} break;
case NOTIFICATION_EXIT_TREE: {
- Viewport *viewport = get_viewport();
- ERR_FAIL_NULL(viewport);
- viewport->disconnect("size_changed", callable_mp(this, &Label3D::_font_changed));
+ Window *window = get_window();
+ ERR_FAIL_NULL(window);
+ window->disconnect("size_changed", callable_mp(this, &Label3D::_font_changed));
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
String new_text = atr(text);
@@ -797,13 +797,13 @@ Ref<Font> Label3D::_get_font_or_default() const {
}
const StringName theme_name = SceneStringName(font);
- List<StringName> theme_types;
- ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types);
+ Vector<StringName> theme_types;
+ ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), theme_types);
ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context();
- List<Ref<Theme>> themes = global_context->get_themes();
+ Vector<Ref<Theme>> themes = global_context->get_themes();
if (Engine::get_singleton()->is_editor_hint()) {
- themes.push_front(ThemeDB::get_singleton()->get_project_theme());
+ themes.insert(0, ThemeDB::get_singleton()->get_project_theme());
}
for (const Ref<Theme> &theme : themes) {
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index 038a78609f..26a574cd26 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -151,6 +151,14 @@ bool LightmapGIData::is_using_spherical_harmonics() const {
return uses_spherical_harmonics;
}
+void LightmapGIData::_set_uses_packed_directional(bool p_enable) {
+ _uses_packed_directional = p_enable;
+}
+
+bool LightmapGIData::_is_using_packed_directional() const {
+ return _uses_packed_directional;
+}
+
void LightmapGIData::set_capture_data(const AABB &p_bounds, bool p_interior, const PackedVector3Array &p_points, const PackedColorArray &p_point_sh, const PackedInt32Array &p_tetrahedra, const PackedInt32Array &p_bsp_tree, float p_baked_exposure) {
if (p_points.size()) {
int pc = p_points.size();
@@ -255,6 +263,9 @@ void LightmapGIData::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics);
ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics);
+ ClassDB::bind_method(D_METHOD("_set_uses_packed_directional", "_uses_packed_directional"), &LightmapGIData::_set_uses_packed_directional);
+ ClassDB::bind_method(D_METHOD("_is_using_packed_directional"), &LightmapGIData::_is_using_packed_directional);
+
ClassDB::bind_method(D_METHOD("add_user", "path", "uv_scale", "slice_index", "sub_instance"), &LightmapGIData::add_user);
ClassDB::bind_method(D_METHOD("get_user_count"), &LightmapGIData::get_user_count);
ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &LightmapGIData::get_user_path);
@@ -267,6 +278,7 @@ void LightmapGIData::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "_uses_packed_directional", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_uses_packed_directional", "_is_using_packed_directional");
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_light_texture", "light_texture"), &LightmapGIData::set_light_texture);
@@ -709,7 +721,7 @@ void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, f
const Vector3 *pp = probe_positions.ptr();
bool exists = false;
for (int j = 0; j < ppcount; j++) {
- if (pp[j].is_equal_approx(real_pos)) {
+ if (pp[j].distance_to(real_pos) < (p_cell_size * 0.5f)) {
exists = true;
break;
}
@@ -1072,6 +1084,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
if (env.is_valid()) {
environment_image = RS::get_singleton()->environment_bake_panorama(env->get_rid(), true, Size2i(128, 64));
+ environment_transform = Basis::from_euler(env->get_sky_rotation()).inverse();
}
}
} break;
@@ -1186,6 +1199,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
}
gi_data->set_lightmap_textures(textures);
+ gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically.
gi_data->set_uses_spherical_harmonics(directional);
for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) {
@@ -1351,6 +1365,12 @@ void LightmapGI::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POST_ENTER_TREE: {
if (light_data.is_valid()) {
+ ERR_FAIL_COND_MSG(
+ light_data->is_using_spherical_harmonics() && !light_data->_is_using_packed_directional(),
+ vformat(
+ "%s (%s): The directional lightmap textures are stored in a format that isn't supported anymore. Please bake lightmaps again to make lightmaps display from this node again.",
+ get_light_data()->get_path(), get_name()));
+
_assign_lightmaps();
}
} break;
@@ -1580,7 +1600,7 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const {
}
PackedStringArray LightmapGI::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail."));
diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h
index 67480132b6..6377c420d1 100644
--- a/scene/3d/lightmap_gi.h
+++ b/scene/3d/lightmap_gi.h
@@ -49,6 +49,8 @@ class LightmapGIData : public Resource {
bool uses_spherical_harmonics = false;
bool interior = false;
+ bool _uses_packed_directional = false;
+
RID lightmap;
AABB bounds;
float baked_exposure = 1.0;
@@ -92,6 +94,9 @@ public:
void set_uses_spherical_harmonics(bool p_enable);
bool is_using_spherical_harmonics() const;
+ void _set_uses_packed_directional(bool p_enable);
+ bool _is_using_packed_directional() const;
+
bool is_interior() const;
float get_baked_exposure() const;
diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp
index 85bf8846b9..f551cb401c 100644
--- a/scene/3d/mesh_instance_3d.cpp
+++ b/scene/3d/mesh_instance_3d.cpp
@@ -517,12 +517,12 @@ bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_p
Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing) {
Ref<ArrayMesh> source_mesh = get_mesh();
- ERR_FAIL_NULL_V_MSG(source_mesh, Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
+ ERR_FAIL_COND_V_MSG(source_mesh.is_null(), Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
Ref<ArrayMesh> bake_mesh;
if (p_existing.is_valid()) {
- ERR_FAIL_NULL_V_MSG(p_existing, Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh.");
+ ERR_FAIL_COND_V_MSG(p_existing.is_null(), Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh.");
ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh.");
bake_mesh = p_existing;
@@ -671,6 +671,172 @@ Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayM
return bake_mesh;
}
+Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_skeleton_pose(Ref<ArrayMesh> p_existing) {
+ Ref<ArrayMesh> source_mesh = get_mesh();
+ ERR_FAIL_COND_V_MSG(source_mesh.is_null(), Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
+
+ Ref<ArrayMesh> bake_mesh;
+
+ if (p_existing.is_valid()) {
+ ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh.");
+
+ bake_mesh = p_existing;
+ } else {
+ bake_mesh.instantiate();
+ }
+
+ ERR_FAIL_COND_V_MSG(skin_ref.is_null(), Ref<ArrayMesh>(), "The source mesh must have a valid skin.");
+ ERR_FAIL_COND_V_MSG(skin_internal.is_null(), Ref<ArrayMesh>(), "The source mesh must have a valid skin.");
+ RID skeleton = skin_ref->get_skeleton();
+ ERR_FAIL_COND_V_MSG(!skeleton.is_valid(), Ref<ArrayMesh>(), "The source mesh must have its skin registered with a valid skeleton.");
+
+ const int bone_count = RenderingServer::get_singleton()->skeleton_get_bone_count(skeleton);
+ ERR_FAIL_COND_V(bone_count <= 0, Ref<ArrayMesh>());
+ ERR_FAIL_COND_V(bone_count < skin_internal->get_bind_count(), Ref<ArrayMesh>());
+
+ LocalVector<Transform3D> bone_transforms;
+ bone_transforms.resize(bone_count);
+ for (int bone_index = 0; bone_index < bone_count; bone_index++) {
+ bone_transforms[bone_index] = RenderingServer::get_singleton()->skeleton_bone_get_transform(skeleton, bone_index);
+ }
+
+ bake_mesh->clear_surfaces();
+
+ int mesh_surface_count = source_mesh->get_surface_count();
+
+ for (int surface_index = 0; surface_index < mesh_surface_count; surface_index++) {
+ ERR_CONTINUE(source_mesh->surface_get_primitive_type(surface_index) != Mesh::PRIMITIVE_TRIANGLES);
+
+ uint32_t surface_format = source_mesh->surface_get_format(surface_index);
+
+ ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_VERTEX));
+ ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_BONES));
+ ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_WEIGHTS));
+
+ unsigned int bones_per_vertex = surface_format & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS ? 8 : 4;
+
+ surface_format &= ~Mesh::ARRAY_FORMAT_BONES;
+ surface_format &= ~Mesh::ARRAY_FORMAT_WEIGHTS;
+
+ const Array &source_mesh_arrays = source_mesh->surface_get_arrays(surface_index);
+
+ ERR_FAIL_COND_V(source_mesh_arrays.size() != RS::ARRAY_MAX, Ref<ArrayMesh>());
+
+ const Vector<Vector3> &source_mesh_vertex_array = source_mesh_arrays[Mesh::ARRAY_VERTEX];
+ const Vector<Vector3> &source_mesh_normal_array = source_mesh_arrays[Mesh::ARRAY_NORMAL];
+ const Vector<float> &source_mesh_tangent_array = source_mesh_arrays[Mesh::ARRAY_TANGENT];
+ const Vector<int> &source_mesh_bones_array = source_mesh_arrays[Mesh::ARRAY_BONES];
+ const Vector<float> &source_mesh_weights_array = source_mesh_arrays[Mesh::ARRAY_WEIGHTS];
+
+ unsigned int vertex_count = source_mesh_vertex_array.size();
+ int expected_bone_array_size = vertex_count * bones_per_vertex;
+ ERR_CONTINUE(source_mesh_bones_array.size() != expected_bone_array_size);
+ ERR_CONTINUE(source_mesh_weights_array.size() != expected_bone_array_size);
+
+ Array new_mesh_arrays;
+ new_mesh_arrays.resize(Mesh::ARRAY_MAX);
+ for (int i = 0; i < source_mesh_arrays.size(); i++) {
+ if (i == Mesh::ARRAY_VERTEX || i == Mesh::ARRAY_NORMAL || i == Mesh::ARRAY_TANGENT || i == Mesh::ARRAY_BONES || i == Mesh::ARRAY_WEIGHTS) {
+ continue;
+ }
+ new_mesh_arrays[i] = source_mesh_arrays[i];
+ }
+
+ bool use_normal_array = source_mesh_normal_array.size() == source_mesh_vertex_array.size();
+ bool use_tangent_array = source_mesh_tangent_array.size() / 4 == source_mesh_vertex_array.size();
+
+ Vector<Vector3> lerped_vertex_array = source_mesh_vertex_array;
+ Vector<Vector3> lerped_normal_array = source_mesh_normal_array;
+ Vector<float> lerped_tangent_array = source_mesh_tangent_array;
+
+ const Vector3 *source_vertices_ptr = source_mesh_vertex_array.ptr();
+ const Vector3 *source_normals_ptr = source_mesh_normal_array.ptr();
+ const float *source_tangents_ptr = source_mesh_tangent_array.ptr();
+ const int *source_bones_ptr = source_mesh_bones_array.ptr();
+ const float *source_weights_ptr = source_mesh_weights_array.ptr();
+
+ Vector3 *lerped_vertices_ptrw = lerped_vertex_array.ptrw();
+ Vector3 *lerped_normals_ptrw = lerped_normal_array.ptrw();
+ float *lerped_tangents_ptrw = lerped_tangent_array.ptrw();
+
+ for (unsigned int vertex_index = 0; vertex_index < vertex_count; vertex_index++) {
+ Vector3 lerped_vertex;
+ Vector3 lerped_normal;
+ Vector3 lerped_tangent;
+
+ const Vector3 &source_vertex = source_vertices_ptr[vertex_index];
+
+ Vector3 source_normal;
+ if (use_normal_array) {
+ source_normal = source_normals_ptr[vertex_index];
+ }
+
+ int tangent_index = vertex_index * 4;
+ Vector4 source_tangent;
+ Vector3 source_tangent_vec3;
+ if (use_tangent_array) {
+ source_tangent = Vector4(
+ source_tangents_ptr[tangent_index],
+ source_tangents_ptr[tangent_index + 1],
+ source_tangents_ptr[tangent_index + 2],
+ source_tangents_ptr[tangent_index + 3]);
+
+ DEV_ASSERT(source_tangent.w == 1.0 || source_tangent.w == -1.0);
+
+ source_tangent_vec3 = Vector3(source_tangent.x, source_tangent.y, source_tangent.z);
+ }
+
+ for (unsigned int weight_index = 0; weight_index < bones_per_vertex; weight_index++) {
+ float bone_weight = source_weights_ptr[vertex_index * bones_per_vertex + weight_index];
+ if (bone_weight < FLT_EPSILON) {
+ continue;
+ }
+ int vertex_bone_index = source_bones_ptr[vertex_index * bones_per_vertex + weight_index];
+ const Transform3D &bone_transform = bone_transforms[vertex_bone_index];
+ const Basis bone_basis = bone_transform.basis.orthonormalized();
+
+ ERR_FAIL_INDEX_V(vertex_bone_index, static_cast<int>(bone_transforms.size()), Ref<ArrayMesh>());
+
+ lerped_vertex += source_vertex.lerp(bone_transform.xform(source_vertex), bone_weight) - source_vertex;
+ ;
+
+ if (use_normal_array) {
+ lerped_normal += source_normal.lerp(bone_basis.xform(source_normal), bone_weight) - source_normal;
+ }
+
+ if (use_tangent_array) {
+ lerped_tangent += source_tangent_vec3.lerp(bone_basis.xform(source_tangent_vec3), bone_weight) - source_tangent_vec3;
+ }
+ }
+
+ lerped_vertices_ptrw[vertex_index] += lerped_vertex;
+
+ if (use_normal_array) {
+ lerped_normals_ptrw[vertex_index] = (source_normal + lerped_normal).normalized();
+ }
+
+ if (use_tangent_array) {
+ lerped_tangent = (source_tangent_vec3 + lerped_tangent).normalized();
+ lerped_tangents_ptrw[tangent_index] = lerped_tangent.x;
+ lerped_tangents_ptrw[tangent_index + 1] = lerped_tangent.y;
+ lerped_tangents_ptrw[tangent_index + 2] = lerped_tangent.z;
+ }
+ }
+
+ new_mesh_arrays[Mesh::ARRAY_VERTEX] = lerped_vertex_array;
+ if (use_normal_array) {
+ new_mesh_arrays[Mesh::ARRAY_NORMAL] = lerped_normal_array;
+ }
+ if (use_tangent_array) {
+ new_mesh_arrays[Mesh::ARRAY_TANGENT] = lerped_tangent_array;
+ }
+
+ bake_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, new_mesh_arrays, Array(), Dictionary(), surface_format);
+ }
+
+ return bake_mesh;
+}
+
void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance3D::set_mesh);
ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance3D::get_mesh);
@@ -700,6 +866,7 @@ void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents);
ClassDB::bind_method(D_METHOD("bake_mesh_from_current_blend_shape_mix", "existing"), &MeshInstance3D::bake_mesh_from_current_blend_shape_mix, DEFVAL(Ref<ArrayMesh>()));
+ ClassDB::bind_method(D_METHOD("bake_mesh_from_current_skeleton_pose", "existing"), &MeshInstance3D::bake_mesh_from_current_skeleton_pose, DEFVAL(Ref<ArrayMesh>()));
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
ADD_GROUP("Skeleton", "");
diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h
index 8a7e03c5b3..0eff12762d 100644
--- a/scene/3d/mesh_instance_3d.h
+++ b/scene/3d/mesh_instance_3d.h
@@ -102,6 +102,7 @@ public:
virtual AABB get_aabb() const override;
Ref<ArrayMesh> bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>());
+ Ref<ArrayMesh> bake_mesh_from_current_skeleton_pose(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>());
MeshInstance3D();
~MeshInstance3D();
diff --git a/scene/3d/multimesh_instance_3d.cpp b/scene/3d/multimesh_instance_3d.cpp
index 55d6e49e6c..2eef1dbbf4 100644
--- a/scene/3d/multimesh_instance_3d.cpp
+++ b/scene/3d/multimesh_instance_3d.cpp
@@ -30,16 +30,35 @@
#include "multimesh_instance_3d.h"
+void MultiMeshInstance3D::_refresh_interpolated() {
+ if (is_inside_tree() && multimesh.is_valid()) {
+ bool interpolated = is_physics_interpolated_and_enabled();
+ multimesh->set_physics_interpolated(interpolated);
+ }
+}
+
+void MultiMeshInstance3D::_physics_interpolated_changed() {
+ VisualInstance3D::_physics_interpolated_changed();
+ _refresh_interpolated();
+}
+
void MultiMeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_multimesh", "multimesh"), &MultiMeshInstance3D::set_multimesh);
ClassDB::bind_method(D_METHOD("get_multimesh"), &MultiMeshInstance3D::get_multimesh);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multimesh", PROPERTY_HINT_RESOURCE_TYPE, "MultiMesh"), "set_multimesh", "get_multimesh");
}
+void MultiMeshInstance3D::_notification(int p_what) {
+ if (p_what == NOTIFICATION_ENTER_TREE) {
+ _refresh_interpolated();
+ }
+}
+
void MultiMeshInstance3D::set_multimesh(const Ref<MultiMesh> &p_multimesh) {
multimesh = p_multimesh;
if (multimesh.is_valid()) {
set_base(multimesh->get_rid());
+ _refresh_interpolated();
} else {
set_base(RID());
}
diff --git a/scene/3d/multimesh_instance_3d.h b/scene/3d/multimesh_instance_3d.h
index 404f31d1e3..c9507b1047 100644
--- a/scene/3d/multimesh_instance_3d.h
+++ b/scene/3d/multimesh_instance_3d.h
@@ -39,9 +39,12 @@ class MultiMeshInstance3D : public GeometryInstance3D {
Ref<MultiMesh> multimesh;
+ void _refresh_interpolated();
+
protected:
+ virtual void _physics_interpolated_changed() override;
static void _bind_methods();
- // bind helpers
+ void _notification(int p_what);
public:
void set_multimesh(const Ref<MultiMesh> &p_multimesh);
diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp
index dc776ebea2..0cce21b9d0 100644
--- a/scene/3d/navigation_link_3d.cpp
+++ b/scene/3d/navigation_link_3d.cpp
@@ -152,6 +152,9 @@ void NavigationLink3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationLink3D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationLink3D::is_enabled);
+ ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationLink3D::set_navigation_map);
+ ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationLink3D::get_navigation_map);
+
ClassDB::bind_method(D_METHOD("set_bidirectional", "bidirectional"), &NavigationLink3D::set_bidirectional);
ClassDB::bind_method(D_METHOD("is_bidirectional"), &NavigationLink3D::is_bidirectional);
@@ -217,16 +220,7 @@ bool NavigationLink3D::_get(const StringName &p_name, Variant &r_ret) const {
void NavigationLink3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- if (enabled) {
- NavigationServer3D::get_singleton()->link_set_map(link, get_world_3d()->get_navigation_map());
- }
- current_global_transform = get_global_transform();
- NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
- NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
-
-#ifdef DEBUG_ENABLED
- _update_debug_mesh();
-#endif // DEBUG_ENABLED
+ _link_enter_navigation_map();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
@@ -235,30 +229,11 @@ void NavigationLink3D::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
set_physics_process_internal(false);
- if (is_inside_tree()) {
- Transform3D new_global_transform = get_global_transform();
- if (current_global_transform != new_global_transform) {
- current_global_transform = new_global_transform;
- NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
- NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
-#ifdef DEBUG_ENABLED
- if (debug_instance.is_valid()) {
- RS::get_singleton()->instance_set_transform(debug_instance, current_global_transform);
- }
-#endif // DEBUG_ENABLED
- }
- }
+ _link_update_transform();
} break;
case NOTIFICATION_EXIT_TREE: {
- NavigationServer3D::get_singleton()->link_set_map(link, RID());
-
-#ifdef DEBUG_ENABLED
- if (debug_instance.is_valid()) {
- RS::get_singleton()->instance_set_scenario(debug_instance, RID());
- RS::get_singleton()->instance_set_visible(debug_instance, false);
- }
-#endif // DEBUG_ENABLED
+ _link_exit_navigation_map();
} break;
}
}
@@ -320,6 +295,25 @@ void NavigationLink3D::set_enabled(bool p_enabled) {
update_gizmos();
}
+void NavigationLink3D::set_navigation_map(RID p_navigation_map) {
+ if (map_override == p_navigation_map) {
+ return;
+ }
+
+ map_override = p_navigation_map;
+
+ NavigationServer3D::get_singleton()->link_set_map(link, map_override);
+}
+
+RID NavigationLink3D::get_navigation_map() const {
+ if (map_override.is_valid()) {
+ return map_override;
+ } else if (is_inside_tree()) {
+ return get_world_3d()->get_navigation_map();
+ }
+ return RID();
+}
+
void NavigationLink3D::set_bidirectional(bool p_bidirectional) {
if (bidirectional == p_bidirectional) {
return;
@@ -459,7 +453,7 @@ void NavigationLink3D::set_travel_cost(real_t p_travel_cost) {
}
PackedStringArray NavigationLink3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (start_position.is_equal_approx(end_position)) {
warnings.push_back(RTR("NavigationLink3D start position should be different than the end position to be useful."));
@@ -467,3 +461,53 @@ PackedStringArray NavigationLink3D::get_configuration_warnings() const {
return warnings;
}
+
+void NavigationLink3D::_link_enter_navigation_map() {
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ if (map_override.is_valid()) {
+ NavigationServer3D::get_singleton()->link_set_map(link, map_override);
+ } else {
+ NavigationServer3D::get_singleton()->link_set_map(link, get_world_3d()->get_navigation_map());
+ }
+
+ current_global_transform = get_global_transform();
+ NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
+ NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
+ NavigationServer3D::get_singleton()->link_set_enabled(link, enabled);
+
+#ifdef DEBUG_ENABLED
+ if (NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
+ _update_debug_mesh();
+ }
+#endif // DEBUG_ENABLED
+}
+
+void NavigationLink3D::_link_exit_navigation_map() {
+ NavigationServer3D::get_singleton()->link_set_map(link, RID());
+#ifdef DEBUG_ENABLED
+ if (debug_instance.is_valid()) {
+ RS::get_singleton()->instance_set_visible(debug_instance, false);
+ }
+#endif // DEBUG_ENABLED
+}
+
+void NavigationLink3D::_link_update_transform() {
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ Transform3D new_global_transform = get_global_transform();
+ if (current_global_transform != new_global_transform) {
+ current_global_transform = new_global_transform;
+ NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
+ NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
+#ifdef DEBUG_ENABLED
+ if (NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
+ _update_debug_mesh();
+ }
+#endif // DEBUG_ENABLED
+ }
+}
diff --git a/scene/3d/navigation_link_3d.h b/scene/3d/navigation_link_3d.h
index 1867082811..e894761f40 100644
--- a/scene/3d/navigation_link_3d.h
+++ b/scene/3d/navigation_link_3d.h
@@ -38,6 +38,7 @@ class NavigationLink3D : public Node3D {
bool enabled = true;
RID link;
+ RID map_override;
bool bidirectional = true;
uint32_t navigation_layers = 1;
Vector3 end_position;
@@ -72,6 +73,9 @@ public:
void set_enabled(bool p_enabled);
bool is_enabled() const { return enabled; }
+ void set_navigation_map(RID p_navigation_map);
+ RID get_navigation_map() const;
+
void set_bidirectional(bool p_bidirectional);
bool is_bidirectional() const { return bidirectional; }
@@ -100,6 +104,11 @@ public:
real_t get_travel_cost() const { return travel_cost; }
PackedStringArray get_configuration_warnings() const override;
+
+private:
+ void _link_enter_navigation_map();
+ void _link_exit_navigation_map();
+ void _link_update_transform();
};
#endif // NAVIGATION_LINK_3D_H
diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp
index 40e04f0fb4..c0c254e7ed 100644
--- a/scene/3d/navigation_region_3d.cpp
+++ b/scene/3d/navigation_region_3d.cpp
@@ -271,7 +271,7 @@ bool NavigationRegion3D::is_baking() const {
}
PackedStringArray NavigationRegion3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!navigation_mesh.is_valid()) {
@@ -686,6 +686,8 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
Vector<Vector3> vertex_array;
vertex_array.resize(connections_count * 6);
+ Vector3 *vertex_array_ptrw = vertex_array.ptrw();
+ int vertex_array_index = 0;
for (int i = 0; i < connections_count; i++) {
Vector3 connection_pathway_start = NavigationServer3D::get_singleton()->region_get_connection_pathway_start(region, i);
@@ -705,13 +707,12 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
Vector3 left_end_pos = connection_pathway_end + (end_right_dir * half_edge_connection_margin);
Vector3 right_end_pos = connection_pathway_end + (end_left_dir * half_edge_connection_margin);
- vertex_array.push_back(right_end_pos);
- vertex_array.push_back(left_start_pos);
- vertex_array.push_back(right_start_pos);
-
- vertex_array.push_back(left_end_pos);
- vertex_array.push_back(right_end_pos);
- vertex_array.push_back(right_start_pos);
+ vertex_array_ptrw[vertex_array_index++] = connection_pathway_start;
+ vertex_array_ptrw[vertex_array_index++] = connection_pathway_end;
+ vertex_array_ptrw[vertex_array_index++] = left_start_pos;
+ vertex_array_ptrw[vertex_array_index++] = right_start_pos;
+ vertex_array_ptrw[vertex_array_index++] = left_end_pos;
+ vertex_array_ptrw[vertex_array_index++] = right_end_pos;
}
if (vertex_array.size() == 0) {
@@ -724,7 +725,7 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
mesh_array.resize(Mesh::ARRAY_MAX);
mesh_array[Mesh::ARRAY_VERTEX] = vertex_array;
- debug_edge_connections_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, mesh_array);
+ debug_edge_connections_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, mesh_array);
debug_edge_connections_mesh->surface_set_material(0, edge_connections_material);
RS::get_singleton()->instance_set_base(debug_edge_connections_instance, debug_edge_connections_mesh->get_rid());
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 2e08afb30d..86ce8a881a 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -30,6 +30,7 @@
#include "node_3d.h"
+#include "core/math/transform_interpolator.h"
#include "scene/3d/visual_instance_3d.h"
#include "scene/main/viewport.h"
#include "scene/property_utils.h"
@@ -176,6 +177,7 @@ void Node3D::_notification(int p_what) {
data.parent = nullptr;
data.C = nullptr;
_update_visibility_parent(true);
+ _disable_client_physics_interpolation();
} break;
case NOTIFICATION_ENTER_WORLD: {
@@ -226,6 +228,12 @@ void Node3D::_notification(int p_what) {
}
#endif
} break;
+
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (data.client_physics_interpolation_data) {
+ data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr;
+ }
+ } break;
}
}
@@ -341,6 +349,119 @@ Transform3D Node3D::get_transform() const {
return data.local_transform;
}
+// Return false to timeout and remove from the client interpolation list.
+bool Node3D::update_client_physics_interpolation_data() {
+ if (!is_inside_tree() || !_is_physics_interpolated_client_side()) {
+ return false;
+ }
+
+ ERR_FAIL_NULL_V(data.client_physics_interpolation_data, false);
+ ClientPhysicsInterpolationData &pid = *data.client_physics_interpolation_data;
+
+ uint64_t tick = Engine::get_singleton()->get_physics_frames();
+
+ // Has this update been done already this tick?
+ // (For instance, get_global_transform_interpolated() could be called multiple times.)
+ if (pid.current_physics_tick != tick) {
+ // Timeout?
+ if (tick >= pid.timeout_physics_tick) {
+ return false;
+ }
+
+ if (pid.current_physics_tick == (tick - 1)) {
+ // Normal interpolation situation, there is a continuous flow of data
+ // from one tick to the next...
+ pid.global_xform_prev = pid.global_xform_curr;
+ } else {
+ // There has been a gap, we cannot sensibly offer interpolation over
+ // a multitick gap, so we will teleport.
+ pid.global_xform_prev = get_global_transform();
+ }
+ pid.current_physics_tick = tick;
+ }
+
+ pid.global_xform_curr = get_global_transform();
+ return true;
+}
+
+void Node3D::_disable_client_physics_interpolation() {
+ // Disable any current client side interpolation.
+ // (This can always restart as normal if you later re-attach the node to the SceneTree.)
+ if (data.client_physics_interpolation_data) {
+ memdelete(data.client_physics_interpolation_data);
+ data.client_physics_interpolation_data = nullptr;
+
+ SceneTree *tree = get_tree();
+ if (tree && _client_physics_interpolation_node_3d_list.in_list()) {
+ tree->client_physics_interpolation_remove_node_3d(&_client_physics_interpolation_node_3d_list);
+ }
+ }
+ _set_physics_interpolated_client_side(false);
+}
+
+Transform3D Node3D::_get_global_transform_interpolated(real_t p_interpolation_fraction) {
+ ERR_FAIL_COND_V(!is_inside_tree(), Transform3D());
+
+ // Set in motion the mechanisms for client side interpolation if not already active.
+ if (!_is_physics_interpolated_client_side()) {
+ _set_physics_interpolated_client_side(true);
+
+ ERR_FAIL_COND_V(data.client_physics_interpolation_data != nullptr, Transform3D());
+ data.client_physics_interpolation_data = memnew(ClientPhysicsInterpolationData);
+ data.client_physics_interpolation_data->global_xform_curr = get_global_transform();
+ data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr;
+ data.client_physics_interpolation_data->current_physics_tick = Engine::get_singleton()->get_physics_frames();
+ }
+
+ // Storing the last tick we requested client interpolation allows us to timeout
+ // and remove client interpolated nodes from the list to save processing.
+ // We use some arbitrary timeout here, but this could potentially be user defined.
+
+ // Note: This timeout has to be larger than the number of ticks in a frame, otherwise the interpolated
+ // data will stop flowing before the next frame is drawn. This should only be relevant at high tick rates.
+ // We could alternatively do this by frames rather than ticks and avoid this problem, but then the behavior
+ // would be machine dependent.
+ data.client_physics_interpolation_data->timeout_physics_tick = Engine::get_singleton()->get_physics_frames() + 256;
+
+ // Make sure data is up to date.
+ update_client_physics_interpolation_data();
+
+ // Interpolate the current data.
+ const Transform3D &xform_curr = data.client_physics_interpolation_data->global_xform_curr;
+ const Transform3D &xform_prev = data.client_physics_interpolation_data->global_xform_prev;
+
+ Transform3D res;
+ TransformInterpolator::interpolate_transform_3d(xform_prev, xform_curr, res, p_interpolation_fraction);
+
+ SceneTree *tree = get_tree();
+
+ // This should not happen, as is_inside_tree() is checked earlier.
+ ERR_FAIL_NULL_V(tree, res);
+ if (!_client_physics_interpolation_node_3d_list.in_list()) {
+ tree->client_physics_interpolation_add_node_3d(&_client_physics_interpolation_node_3d_list);
+ }
+
+ return res;
+}
+
+Transform3D Node3D::get_global_transform_interpolated() {
+ // Pass through if physics interpolation is switched off.
+ // This is a convenience, as it allows you to easy turn off interpolation
+ // without changing any code.
+ if (!is_physics_interpolated_and_enabled()) {
+ return get_global_transform();
+ }
+
+ // If we are in the physics frame, the interpolated global transform is meaningless.
+ // However, there is an exception, we may want to use this as a means of starting off the client
+ // interpolation pump if not already started (when _is_physics_interpolated_client_side() is false).
+ if (Engine::get_singleton()->is_in_physics_frame() && _is_physics_interpolated_client_side()) {
+ return get_global_transform();
+ }
+
+ return _get_global_transform_interpolated(Engine::get_singleton()->get_physics_interpolation_fraction());
+}
+
Transform3D Node3D::get_global_transform() const {
ERR_FAIL_COND_V(!is_inside_tree(), Transform3D());
@@ -1140,6 +1261,7 @@ void Node3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Node3D::set_global_transform);
ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform);
+ ClassDB::bind_method(D_METHOD("get_global_transform_interpolated"), &Node3D::get_global_transform_interpolated);
ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Node3D::set_global_position);
ClassDB::bind_method(D_METHOD("get_global_position"), &Node3D::get_global_position);
ClassDB::bind_method(D_METHOD("set_global_basis", "basis"), &Node3D::set_global_basis);
@@ -1236,4 +1358,27 @@ void Node3D::_bind_methods() {
}
Node3D::Node3D() :
- xform_change(this) {}
+ xform_change(this), _client_physics_interpolation_node_3d_list(this) {
+ // Default member initializer for bitfield is a C++20 extension, so:
+
+ data.top_level = false;
+ data.inside_world = false;
+
+ data.ignore_notification = false;
+ data.notify_local_transform = false;
+ data.notify_transform = false;
+
+ data.visible = true;
+ data.disable_scale = false;
+ data.vi_visible = true;
+
+#ifdef TOOLS_ENABLED
+ data.gizmos_disabled = false;
+ data.gizmos_dirty = false;
+ data.transform_gizmo_visible = true;
+#endif
+}
+
+Node3D::~Node3D() {
+ _disable_client_physics_interpolation();
+}
diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h
index c1667221df..217ee28cf1 100644
--- a/scene/3d/node_3d.h
+++ b/scene/3d/node_3d.h
@@ -85,7 +85,15 @@ private:
DIRTY_GLOBAL_TRANSFORM = 4
};
+ struct ClientPhysicsInterpolationData {
+ Transform3D global_xform_curr;
+ Transform3D global_xform_prev;
+ uint64_t current_physics_tick = 0;
+ uint64_t timeout_physics_tick = 0;
+ };
+
mutable SelfList<Node> xform_change;
+ SelfList<Node3D> _client_physics_interpolation_node_3d_list;
// This Data struct is to avoid namespace pollution in derived classes.
@@ -101,8 +109,19 @@ private:
Viewport *viewport = nullptr;
- bool top_level = false;
- bool inside_world = false;
+ bool top_level : 1;
+ bool inside_world : 1;
+
+ // This is cached, and only currently kept up to date in visual instances.
+ // This is set if a visual instance is (a) in the tree AND (b) visible via is_visible_in_tree() call.
+ bool vi_visible : 1;
+
+ bool ignore_notification : 1;
+ bool notify_local_transform : 1;
+ bool notify_transform : 1;
+
+ bool visible : 1;
+ bool disable_scale : 1;
RID visibility_parent;
@@ -110,18 +129,13 @@ private:
List<Node3D *> children;
List<Node3D *>::Element *C = nullptr;
- bool ignore_notification = false;
- bool notify_local_transform = false;
- bool notify_transform = false;
-
- bool visible = true;
- bool disable_scale = false;
+ ClientPhysicsInterpolationData *client_physics_interpolation_data = nullptr;
#ifdef TOOLS_ENABLED
Vector<Ref<Node3DGizmo>> gizmos;
- bool gizmos_disabled = false;
- bool gizmos_dirty = false;
- bool transform_gizmo_visible = true;
+ bool gizmos_disabled : 1;
+ bool gizmos_dirty : 1;
+ bool transform_gizmo_visible : 1;
#endif
} data;
@@ -150,6 +164,11 @@ protected:
_FORCE_INLINE_ void _update_local_transform() const;
_FORCE_INLINE_ void _update_rotation_and_scale() const;
+ void _set_vi_visible(bool p_visible) { data.vi_visible = p_visible; }
+ bool _is_vi_visible() const { return data.vi_visible; }
+ Transform3D _get_global_transform_interpolated(real_t p_interpolation_fraction);
+ void _disable_client_physics_interpolation();
+
void _notification(int p_what);
static void _bind_methods();
@@ -208,6 +227,9 @@ public:
Quaternion get_quaternion() const;
Transform3D get_global_transform() const;
+ Transform3D get_global_transform_interpolated();
+ bool update_client_physics_interpolation_data();
+
#ifdef TOOLS_ENABLED
virtual Transform3D get_global_gizmo_transform() const;
virtual Transform3D get_local_gizmo_transform() const;
@@ -279,6 +301,7 @@ public:
NodePath get_visibility_parent() const;
Node3D();
+ ~Node3D();
};
VARIANT_ENUM_CAST(Node3D::RotationEditMode)
diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp
index 150771545b..6d88323c76 100644
--- a/scene/3d/occluder_instance_3d.cpp
+++ b/scene/3d/occluder_instance_3d.cpp
@@ -129,9 +129,6 @@ void Occluder3D::_notification(int p_what) {
}
}
-void Occluder3D::_bind_methods() {
-}
-
Occluder3D::Occluder3D() {
occluder = RS::get_singleton()->occluder_create();
}
@@ -694,7 +691,7 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node,
}
PackedStringArray OccluderInstance3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (!bool(GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"))) {
warnings.push_back(RTR("Occlusion culling is disabled in the Project Settings, which means occlusion culling won't be performed in the root viewport.\nTo resolve this, open the Project Settings and enable Rendering > Occlusion Culling > Use Occlusion Culling."));
diff --git a/scene/3d/occluder_instance_3d.h b/scene/3d/occluder_instance_3d.h
index 91445710b3..62e9478527 100644
--- a/scene/3d/occluder_instance_3d.h
+++ b/scene/3d/occluder_instance_3d.h
@@ -49,7 +49,6 @@ protected:
void _update();
virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) = 0;
- static void _bind_methods();
void _notification(int p_what);
public:
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index 1f8f7cd54c..64259a24b0 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -216,24 +216,7 @@ void Path3D::_bind_methods() {
ADD_SIGNAL(MethodInfo("curve_changed"));
}
-// Update transform, in deferred mode by default to avoid superfluity.
-void PathFollow3D::update_transform(bool p_immediate) {
- transform_dirty = true;
-
- if (p_immediate) {
- _update_transform();
- } else {
- callable_mp(this, &PathFollow3D::_update_transform).call_deferred();
- }
-}
-
-// Update transform immediately .
-void PathFollow3D::_update_transform() {
- if (!transform_dirty) {
- return;
- }
- transform_dirty = false;
-
+void PathFollow3D::update_transform() {
if (!path) {
return;
}
@@ -286,9 +269,7 @@ void PathFollow3D::_notification(int p_what) {
Node *parent = get_parent();
if (parent) {
path = Object::cast_to<Path3D>(parent);
- if (path) {
- update_transform();
- }
+ update_transform();
}
} break;
@@ -318,7 +299,7 @@ void PathFollow3D::_validate_property(PropertyInfo &p_property) const {
}
PackedStringArray PathFollow3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!Object::cast_to<Path3D>(get_parent())) {
@@ -414,6 +395,9 @@ void PathFollow3D::_bind_methods() {
void PathFollow3D::set_progress(real_t p_progress) {
ERR_FAIL_COND(!isfinite(p_progress));
+ if (progress == p_progress) {
+ return;
+ }
progress = p_progress;
if (path) {
@@ -435,10 +419,11 @@ void PathFollow3D::set_progress(real_t p_progress) {
}
void PathFollow3D::set_h_offset(real_t p_h_offset) {
- h_offset = p_h_offset;
- if (path) {
- update_transform();
+ if (h_offset == p_h_offset) {
+ return;
}
+ h_offset = p_h_offset;
+ update_transform();
}
real_t PathFollow3D::get_h_offset() const {
@@ -446,10 +431,11 @@ real_t PathFollow3D::get_h_offset() const {
}
void PathFollow3D::set_v_offset(real_t p_v_offset) {
- v_offset = p_v_offset;
- if (path) {
- update_transform();
+ if (v_offset == p_v_offset) {
+ return;
}
+ v_offset = p_v_offset;
+ update_transform();
}
real_t PathFollow3D::get_v_offset() const {
@@ -461,9 +447,10 @@ real_t PathFollow3D::get_progress() const {
}
void PathFollow3D::set_progress_ratio(real_t p_ratio) {
- if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) {
- set_progress(p_ratio * path->get_curve()->get_baked_length());
- }
+ ERR_FAIL_NULL_MSG(path, "Can only set progress ratio on a PathFollow3D that is the child of a Path3D which is itself part of the scene tree.");
+ ERR_FAIL_COND_MSG(path->get_curve().is_null(), "Can't set progress ratio on a PathFollow3D that does not have a Curve.");
+ ERR_FAIL_COND_MSG(!path->get_curve()->get_baked_length(), "Can't set progress ratio on a PathFollow3D that has a 0 length curve.");
+ set_progress(p_ratio * path->get_curve()->get_baked_length());
}
real_t PathFollow3D::get_progress_ratio() const {
@@ -475,6 +462,9 @@ real_t PathFollow3D::get_progress_ratio() const {
}
void PathFollow3D::set_rotation_mode(RotationMode p_rotation_mode) {
+ if (rotation_mode == p_rotation_mode) {
+ return;
+ }
rotation_mode = p_rotation_mode;
update_configuration_warnings();
@@ -486,6 +476,9 @@ PathFollow3D::RotationMode PathFollow3D::get_rotation_mode() const {
}
void PathFollow3D::set_use_model_front(bool p_use_model_front) {
+ if (use_model_front == p_use_model_front) {
+ return;
+ }
use_model_front = p_use_model_front;
update_transform();
}
@@ -495,6 +488,9 @@ bool PathFollow3D::is_using_model_front() const {
}
void PathFollow3D::set_loop(bool p_loop) {
+ if (loop == p_loop) {
+ return;
+ }
loop = p_loop;
update_transform();
}
@@ -504,6 +500,9 @@ bool PathFollow3D::has_loop() const {
}
void PathFollow3D::set_tilt_enabled(bool p_enabled) {
+ if (tilt_enabled == p_enabled) {
+ return;
+ }
tilt_enabled = p_enabled;
update_transform();
}
diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h
index 0c9111bb8e..fb4f301375 100644
--- a/scene/3d/path_3d.h
+++ b/scene/3d/path_3d.h
@@ -90,7 +90,6 @@ protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
- void _update_transform();
static void _bind_methods();
@@ -124,7 +123,7 @@ public:
PackedStringArray get_configuration_warnings() const override;
- void update_transform(bool p_immediate = false);
+ void update_transform();
static Transform3D correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode);
diff --git a/scene/3d/physical_bone_simulator_3d.cpp b/scene/3d/physical_bone_simulator_3d.cpp
index ffe79e0892..8874c9cfc6 100644
--- a/scene/3d/physical_bone_simulator_3d.cpp
+++ b/scene/3d/physical_bone_simulator_3d.cpp
@@ -73,10 +73,15 @@ void PhysicalBoneSimulator3D::_pose_updated() {
}
ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
for (int i = 0; i < skeleton->get_bone_count(); i++) {
- bones.write[i].global_pose = skeleton->get_bone_global_pose(i);
+ _bone_pose_updated(skeleton, i);
}
}
+void PhysicalBoneSimulator3D::_bone_pose_updated(Skeleton3D *p_skeleton, int p_bone_id) {
+ ERR_FAIL_INDEX(p_bone_id, bones.size());
+ bones.write[p_bone_id].global_pose = p_skeleton->get_bone_global_pose(p_bone_id);
+}
+
void PhysicalBoneSimulator3D::_set_active(bool p_active) {
if (!Engine::get_singleton()->is_editor_hint()) {
_reset_physical_bones_state();
@@ -285,11 +290,11 @@ void _pb_start_simulation(const PhysicalBoneSimulator3D *p_simulator, Node *p_no
}
void PhysicalBoneSimulator3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) {
+ _pose_updated();
+
simulating = true;
_reset_physical_bones_state();
- _pose_updated();
-
Vector<int> sim_bones;
if (p_bones.size() > 0) {
sim_bones.resize(p_bones.size());
@@ -357,47 +362,17 @@ void PhysicalBoneSimulator3D::_process_modification() {
if (!skeleton) {
return;
}
- if (!enabled) {
- for (int i = 0; i < bones.size(); i++) {
- if (bones[i].physical_bone) {
- if (bones[i].physical_bone->is_simulating_physics() == false) {
- bones[i].physical_bone->reset_to_rest_position();
- }
- }
+ ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
+ for (int i = 0; i < skeleton->get_bone_count(); i++) {
+ if (!bones[i].physical_bone) {
+ continue;
}
- } else {
- ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
- for (int i = 0; i < skeleton->get_bone_count(); i++) {
- if (!bones[i].physical_bone) {
- continue;
- }
+ if (bones[i].physical_bone->is_simulating_physics() == false) {
+ _bone_pose_updated(skeleton, i);
+ bones[i].physical_bone->reset_to_rest_position();
+ } else if (simulating) {
skeleton->set_bone_global_pose(i, bones[i].global_pose);
}
-
- // TODO:
- // The above method is performance heavy and needs to be improved.
- // Ideally, the processing of set_bone_global_pose within Skeleton3D should be improved,
- // but the workaround available now is to convert the global pose to a local pose on the SkeletonModifier side.
- // However, the follow method needs recursive processing for deformations within PhysicalBoneSimulator3D to account for update order.
- /*
- ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
- LocalVector<Transform3D> local_poses;
- for (int i = 0; i < skeleton->get_bone_count(); i++) {
- Transform3D pt;
- if (skeleton->get_bone_parent(i) >= 0) {
- pt = get_bone_global_pose(skeleton->get_bone_parent(i));
- }
- local_poses.push_back(pt.affine_inverse() * bones[i].global_pose);
- }
- for (int i = 0; i < skeleton->get_bone_count(); i++) {
- if (!bones[i].physical_bone) {
- continue;
- }
- skeleton->set_bone_pose_position(i, local_poses[i].origin);
- skeleton->set_bone_pose_rotation(i, local_poses[i].basis.get_rotation_quaternion());
- skeleton->set_bone_pose_scale(i, local_poses[i].basis.get_scale());
- }
- */
}
}
diff --git a/scene/3d/physical_bone_simulator_3d.h b/scene/3d/physical_bone_simulator_3d.h
index ee900e0e77..24136be2b8 100644
--- a/scene/3d/physical_bone_simulator_3d.h
+++ b/scene/3d/physical_bone_simulator_3d.h
@@ -41,7 +41,6 @@ class PhysicalBoneSimulator3D : public SkeletonModifier3D {
GDCLASS(PhysicalBoneSimulator3D, SkeletonModifier3D);
bool simulating = false;
- bool enabled = true;
struct SimulatedBone {
int parent;
@@ -74,6 +73,7 @@ protected:
void _bone_list_changed();
void _pose_updated();
+ void _bone_pose_updated(Skeleton3D *skeleton, int p_bone_id);
virtual void _process_modification() override;
diff --git a/scene/3d/physics/collision_object_3d.cpp b/scene/3d/physics/collision_object_3d.cpp
index f11aa7012a..f0a5013ca2 100644
--- a/scene/3d/physics/collision_object_3d.cpp
+++ b/scene/3d/physics/collision_object_3d.cpp
@@ -731,7 +731,7 @@ bool CollisionObject3D::get_capture_input_on_drag() const {
}
PackedStringArray CollisionObject3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (shapes.is_empty()) {
warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape3D or CollisionPolygon3D as a child to define its shape."));
diff --git a/scene/3d/physics/collision_polygon_3d.cpp b/scene/3d/physics/collision_polygon_3d.cpp
index 76cd4db779..bf8dec7b54 100644
--- a/scene/3d/physics/collision_polygon_3d.cpp
+++ b/scene/3d/physics/collision_polygon_3d.cpp
@@ -169,7 +169,7 @@ void CollisionPolygon3D::set_margin(real_t p_margin) {
}
PackedStringArray CollisionPolygon3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (!Object::cast_to<CollisionObject3D>(get_parent())) {
warnings.push_back(RTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node.\nPlease only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape."));
diff --git a/scene/3d/physics/collision_shape_3d.cpp b/scene/3d/physics/collision_shape_3d.cpp
index f3492a3cf3..304fa74b06 100644
--- a/scene/3d/physics/collision_shape_3d.cpp
+++ b/scene/3d/physics/collision_shape_3d.cpp
@@ -120,7 +120,7 @@ void CollisionShape3D::resource_changed(Ref<Resource> res) {
#endif
PackedStringArray CollisionShape3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
CollisionObject3D *col_object = Object::cast_to<CollisionObject3D>(get_parent());
if (col_object == nullptr) {
diff --git a/scene/3d/physics/shape_cast_3d.cpp b/scene/3d/physics/shape_cast_3d.cpp
index ada238c7f2..8ad651fdf5 100644
--- a/scene/3d/physics/shape_cast_3d.cpp
+++ b/scene/3d/physics/shape_cast_3d.cpp
@@ -157,7 +157,7 @@ void ShapeCast3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &ShapeCast3D::set_collide_with_bodies);
ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &ShapeCast3D::is_collide_with_bodies_enabled);
- ClassDB::bind_method(D_METHOD("_get_collision_result"), &ShapeCast3D::_get_collision_result);
+ ClassDB::bind_method(D_METHOD("get_collision_result"), &ShapeCast3D::get_collision_result);
ClassDB::bind_method(D_METHOD("set_debug_shape_custom_color", "debug_shape_custom_color"), &ShapeCast3D::set_debug_shape_custom_color);
ClassDB::bind_method(D_METHOD("get_debug_shape_custom_color"), &ShapeCast3D::get_debug_shape_custom_color);
@@ -169,7 +169,7 @@ void ShapeCast3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01,suffix:m"), "set_margin", "get_margin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_results"), "set_max_results", "get_max_results");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "_get_collision_result");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "get_collision_result");
ADD_GROUP("Collide With", "collide_with");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled");
@@ -475,7 +475,7 @@ bool ShapeCast3D::is_collide_with_bodies_enabled() const {
return collide_with_bodies;
}
-Array ShapeCast3D::_get_collision_result() const {
+Array ShapeCast3D::get_collision_result() const {
Array ret;
for (int i = 0; i < result.size(); ++i) {
diff --git a/scene/3d/physics/shape_cast_3d.h b/scene/3d/physics/shape_cast_3d.h
index 19b73e3f72..9fc5e71670 100644
--- a/scene/3d/physics/shape_cast_3d.h
+++ b/scene/3d/physics/shape_cast_3d.h
@@ -73,8 +73,6 @@ class ShapeCast3D : public Node3D {
real_t collision_safe_fraction = 1.0;
real_t collision_unsafe_fraction = 1.0;
- Array _get_collision_result() const;
-
RID debug_instance;
Ref<ArrayMesh> debug_mesh;
@@ -123,6 +121,7 @@ public:
Ref<StandardMaterial3D> get_debug_material();
+ Array get_collision_result() const;
int get_collision_count() const;
Object *get_collider(int p_idx) const;
RID get_collider_rid(int p_idx) const;
diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp
index c23032d3b9..981e872af2 100644
--- a/scene/3d/physics/vehicle_body_3d.cpp
+++ b/scene/3d/physics/vehicle_body_3d.cpp
@@ -106,7 +106,7 @@ void VehicleWheel3D::_notification(int p_what) {
}
PackedStringArray VehicleWheel3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (!Object::cast_to<VehicleBody3D>(get_parent())) {
warnings.push_back(RTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D."));
@@ -219,6 +219,14 @@ bool VehicleWheel3D::is_in_contact() const {
return m_raycastInfo.m_isInContact;
}
+Vector3 VehicleWheel3D::get_contact_point() const {
+ return m_raycastInfo.m_contactPointWS;
+}
+
+Vector3 VehicleWheel3D::get_contact_normal() const {
+ return m_raycastInfo.m_contactNormalWS;
+}
+
Node3D *VehicleWheel3D::get_contact_body() const {
return m_raycastInfo.m_groundObject;
}
@@ -256,6 +264,8 @@ void VehicleWheel3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_in_contact"), &VehicleWheel3D::is_in_contact);
ClassDB::bind_method(D_METHOD("get_contact_body"), &VehicleWheel3D::get_contact_body);
+ ClassDB::bind_method(D_METHOD("get_contact_point"), &VehicleWheel3D::get_contact_point);
+ ClassDB::bind_method(D_METHOD("get_contact_normal"), &VehicleWheel3D::get_contact_normal);
ClassDB::bind_method(D_METHOD("set_roll_influence", "roll_influence"), &VehicleWheel3D::set_roll_influence);
ClassDB::bind_method(D_METHOD("get_roll_influence"), &VehicleWheel3D::get_roll_influence);
@@ -287,11 +297,11 @@ void VehicleWheel3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wheel_friction_slip"), "set_friction_slip", "get_friction_slip");
ADD_GROUP("Suspension", "suspension_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_travel", PROPERTY_HINT_NONE, "suffix:m"), "set_suspension_travel", "get_suspension_travel");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_stiffness"), "set_suspension_stiffness", "get_suspension_stiffness");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_stiffness", PROPERTY_HINT_NONE, U"suffix:N/mm"), "set_suspension_stiffness", "get_suspension_stiffness");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_max_force", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m/s\u00B2 (N)"), "set_suspension_max_force", "get_suspension_max_force");
ADD_GROUP("Damping", "damping_");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_compression"), "set_damping_compression", "get_damping_compression");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_relaxation"), "set_damping_relaxation", "get_damping_relaxation");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_compression", PROPERTY_HINT_NONE, U"suffix:N\u22C5s/mm"), "set_damping_compression", "get_damping_compression");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_relaxation", PROPERTY_HINT_NONE, U"suffix:N\u22C5s/mm"), "set_damping_relaxation", "get_damping_relaxation");
}
void VehicleWheel3D::set_engine_force(real_t p_engine_force) {
diff --git a/scene/3d/physics/vehicle_body_3d.h b/scene/3d/physics/vehicle_body_3d.h
index def9984440..24f120ed26 100644
--- a/scene/3d/physics/vehicle_body_3d.h
+++ b/scene/3d/physics/vehicle_body_3d.h
@@ -130,6 +130,10 @@ public:
bool is_in_contact() const;
+ Vector3 get_contact_point() const;
+
+ Vector3 get_contact_normal() const;
+
Node3D *get_contact_body() const;
void set_roll_influence(real_t p_value);
diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp
index 8d6e717132..f970879aa4 100644
--- a/scene/3d/remote_transform_3d.cpp
+++ b/scene/3d/remote_transform_3d.cpp
@@ -113,6 +113,16 @@ void RemoteTransform3D::_notification(int p_what) {
_update_cache();
} break;
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (cache.is_valid()) {
+ _update_remote();
+ Node3D *n = Object::cast_to<Node3D>(ObjectDB::get_instance(cache));
+ if (n) {
+ n->reset_physics_interpolation();
+ }
+ }
+ } break;
+
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
case NOTIFICATION_TRANSFORM_CHANGED: {
if (!is_inside_tree()) {
@@ -201,7 +211,7 @@ void RemoteTransform3D::force_update_cache() {
}
PackedStringArray RemoteTransform3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (!has_node(remote_node) || !Object::cast_to<Node3D>(get_node(remote_node))) {
warnings.push_back(RTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work."));
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 3d24b3bbe9..db9c4db30d 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -69,13 +69,13 @@ SkinReference::~SkinReference() {
///////////////////////////////////////
bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
- String path = p_path;
-
#ifndef DISABLE_DEPRECATED
- if (path.begins_with("animate_physical_bones")) {
+ if (p_path == SNAME("animate_physical_bones")) {
set_animate_physical_bones(p_value);
+ return true;
}
#endif
+ String path = p_path;
if (!path.begins_with("bones/")) {
return false;
@@ -103,6 +103,8 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
set_bone_pose_rotation(which, p_value);
} else if (what == "scale") {
set_bone_pose_scale(which, p_value);
+ } else if (what == "bone_meta") {
+ set_bone_meta(which, path.get_slicec('/', 3), p_value);
#ifndef DISABLE_DEPRECATED
} else if (what == "pose" || what == "bound_children") {
// Kept for compatibility from 3.x to 4.x.
@@ -139,13 +141,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 DISABLE_DEPRECATED
- if (path.begins_with("animate_physical_bones")) {
+ if (p_path == SNAME("animate_physical_bones")) {
r_ret = get_animate_physical_bones();
+ return true;
}
#endif
+ String path = p_path;
if (!path.begins_with("bones/")) {
return false;
@@ -170,6 +172,8 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
r_ret = get_bone_pose_rotation(which);
} else if (what == "scale") {
r_ret = get_bone_pose_scale(which);
+ } else if (what == "bone_meta") {
+ r_ret = get_bone_meta(which, path.get_slicec('/', 3));
} else {
return false;
}
@@ -187,6 +191,11 @@ void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("position"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + PNAME("rotation"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("scale"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+
+ for (const KeyValue<StringName, Variant> &K : bones[i].metadata) {
+ PropertyInfo pi = PropertyInfo(bones[i].metadata[K.key].get_type(), prep + PNAME("bone_meta/") + K.key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
+ p_list->push_back(pi);
+ }
}
for (PropertyInfo &E : *p_list) {
@@ -300,7 +309,7 @@ void Skeleton3D::setup_simulator() {
simulator = sim;
sim->is_compat = true;
sim->set_active(false); // Don't run unneeded process.
- add_child(simulator);
+ add_child(simulator, false, INTERNAL_MODE_BACK);
set_animate_physical_bones(animate_physical_bones);
}
#endif // _DISABLE_DEPRECATED
@@ -531,6 +540,57 @@ void Skeleton3D::set_bone_name(int p_bone, const String &p_name) {
version++;
}
+Variant Skeleton3D::get_bone_meta(int p_bone, const StringName &p_key) const {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX_V(p_bone, bone_size, Variant());
+
+ if (!bones[p_bone].metadata.has(p_key)) {
+ return Variant();
+ }
+ return bones[p_bone].metadata[p_key];
+}
+
+TypedArray<StringName> Skeleton3D::_get_bone_meta_list_bind(int p_bone) const {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX_V(p_bone, bone_size, TypedArray<StringName>());
+
+ TypedArray<StringName> _metaret;
+ for (const KeyValue<StringName, Variant> &K : bones[p_bone].metadata) {
+ _metaret.push_back(K.key);
+ }
+ return _metaret;
+}
+
+void Skeleton3D::get_bone_meta_list(int p_bone, List<StringName> *p_list) const {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX(p_bone, bone_size);
+
+ for (const KeyValue<StringName, Variant> &K : bones[p_bone].metadata) {
+ p_list->push_back(K.key);
+ }
+}
+
+bool Skeleton3D::has_bone_meta(int p_bone, const StringName &p_key) const {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX_V(p_bone, bone_size, false);
+
+ return bones[p_bone].metadata.has(p_key);
+}
+
+void Skeleton3D::set_bone_meta(int p_bone, const StringName &p_key, const Variant &p_value) {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX(p_bone, bone_size);
+
+ if (p_value.get_type() == Variant::NIL) {
+ if (bones.write[p_bone].metadata.has(p_key)) {
+ bones.write[p_bone].metadata.erase(p_key);
+ }
+ return;
+ }
+
+ bones.write[p_bone].metadata.insert(p_key, p_value, false);
+}
+
bool Skeleton3D::is_bone_parent_of(int p_bone, int p_parent_bone_id) const {
int parent_of_bone = get_bone_parent(p_bone);
@@ -1014,6 +1074,11 @@ void Skeleton3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &Skeleton3D::get_bone_name);
ClassDB::bind_method(D_METHOD("set_bone_name", "bone_idx", "name"), &Skeleton3D::set_bone_name);
+ ClassDB::bind_method(D_METHOD("get_bone_meta", "bone_idx", "key"), &Skeleton3D::get_bone_meta);
+ ClassDB::bind_method(D_METHOD("get_bone_meta_list", "bone_idx"), &Skeleton3D::_get_bone_meta_list_bind);
+ ClassDB::bind_method(D_METHOD("has_bone_meta", "bone_idx", "key"), &Skeleton3D::has_bone_meta);
+ ClassDB::bind_method(D_METHOD("set_bone_meta", "bone_idx", "key", "value"), &Skeleton3D::set_bone_meta);
+
ClassDB::bind_method(D_METHOD("get_concatenated_bone_names"), &Skeleton3D::get_concatenated_bone_names);
ClassDB::bind_method(D_METHOD("get_bone_parent", "bone_idx"), &Skeleton3D::get_bone_parent);
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index a009383f45..07bdeccf2f 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -116,6 +116,8 @@ private:
}
}
+ HashMap<StringName, Variant> metadata;
+
#ifndef DISABLE_DEPRECATED
Transform3D pose_global_no_override;
real_t global_pose_override_amount = 0.0;
@@ -193,6 +195,7 @@ protected:
void _get_property_list(List<PropertyInfo> *p_list) const;
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
+ TypedArray<StringName> _get_bone_meta_list_bind(int p_bone) const;
static void _bind_methods();
virtual void add_child_notify(Node *p_child) override;
@@ -238,6 +241,12 @@ public:
void set_motion_scale(float p_motion_scale);
float get_motion_scale() const;
+ // bone metadata
+ Variant get_bone_meta(int p_bone, const StringName &p_key) const;
+ void get_bone_meta_list(int p_bone, List<StringName> *p_list) const;
+ bool has_bone_meta(int p_bone, const StringName &p_key) const;
+ void set_bone_meta(int p_bone, const StringName &p_key, const Variant &p_value);
+
// Posing API
Transform3D get_bone_pose(int p_bone) const;
Vector3 get_bone_pose_position(int p_bone) const;
diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp
index 0d6316ee35..2476e7d5cd 100644
--- a/scene/3d/skeleton_ik_3d.cpp
+++ b/scene/3d/skeleton_ik_3d.cpp
@@ -503,7 +503,11 @@ Transform3D SkeletonIK3D::_get_target_transform() {
Node3D *target_node_override = cast_to<Node3D>(target_node_override_ref.get_validated_object());
if (target_node_override && target_node_override->is_inside_tree()) {
- return target_node_override->get_global_transform();
+ // Make sure to use the interpolated transform as target.
+ // When physics interpolation is off this will pass through to get_global_transform().
+ // When using interpolation, ensure that the target matches the interpolated visual position
+ // of the target when updating the IK each frame.
+ return target_node_override->get_global_transform_interpolated();
} else {
return target;
}
diff --git a/scene/3d/skeleton_modifier_3d.cpp b/scene/3d/skeleton_modifier_3d.cpp
index 9851214194..d5c603112e 100644
--- a/scene/3d/skeleton_modifier_3d.cpp
+++ b/scene/3d/skeleton_modifier_3d.cpp
@@ -37,7 +37,7 @@ void SkeletonModifier3D::_validate_property(PropertyInfo &p_property) const {
PackedStringArray SkeletonModifier3D::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();
if (skeleton_id.is_null()) {
- warnings.push_back(RTR("Skeleton3D node not set! SkeletonModifier3D must be child of Skeleton3D or set a path to an external skeleton."));
+ warnings.push_back(RTR("Skeleton3D node not set! SkeletonModifier3D must be child of Skeleton3D."));
}
return warnings;
}
diff --git a/scene/3d/soft_body_3d.compat.inc b/scene/3d/soft_body_3d.compat.inc
new file mode 100644
index 0000000000..0b01bfeb1f
--- /dev/null
+++ b/scene/3d/soft_body_3d.compat.inc
@@ -0,0 +1,41 @@
+/**************************************************************************/
+/* soft_body_3d.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 DISABLE_DEPRECATED
+
+void SoftBody3D::_pin_point_bind_compat_94684(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path) {
+ pin_point(p_point_index, pin, p_spatial_attachment_path, -1);
+}
+
+void SoftBody3D::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path"), &SoftBody3D::_pin_point_bind_compat_94684, DEFVAL(NodePath()));
+}
+
+#endif // DISABLE_DEPRECATED
diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp
index f02cd9b700..b0fc94d75f 100644
--- a/scene/3d/soft_body_3d.cpp
+++ b/scene/3d/soft_body_3d.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "soft_body_3d.h"
+#include "soft_body_3d.compat.inc"
#include "scene/3d/physics/physics_body_3d.h"
@@ -200,12 +201,18 @@ bool SoftBody3D::_set_property_pinned_points_indices(const Array &p_indices) {
int point_index;
for (int i = 0; i < p_indices_size; ++i) {
point_index = p_indices.get(i);
- if (w[i].point_index != point_index) {
- if (-1 != w[i].point_index) {
+ if (w[i].point_index != point_index || pinned_points.size() < p_indices_size) {
+ bool insert = false;
+ if (w[i].point_index != -1 && p_indices.find(w[i].point_index) == -1) {
pin_point(w[i].point_index, false);
+ insert = true;
}
w[i].point_index = point_index;
- pin_point(w[i].point_index, true);
+ if (insert) {
+ pin_point(w[i].point_index, true, NodePath(), i);
+ } else {
+ pin_point(w[i].point_index, true);
+ }
}
}
return true;
@@ -218,7 +225,13 @@ bool SoftBody3D::_set_property_pinned_points_attachment(int p_item, const String
if ("spatial_attachment_path" == p_what) {
PinnedPoint *w = pinned_points.ptrw();
- callable_mp(this, &SoftBody3D::_pin_point_deferred).call_deferred(Variant(w[p_item].point_index), true, p_value);
+
+ if (is_inside_tree()) {
+ callable_mp(this, &SoftBody3D::_pin_point_deferred).call_deferred(Variant(w[p_item].point_index), true, p_value);
+ } else {
+ pin_point(w[p_item].point_index, true, p_value);
+ _make_cache_dirty();
+ }
} else if ("offset" == p_what) {
PinnedPoint *w = pinned_points.ptrw();
w[p_item].offset = p_value;
@@ -350,7 +363,7 @@ void SoftBody3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_point_transform", "point_index"), &SoftBody3D::get_point_transform);
- ClassDB::bind_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path"), &SoftBody3D::pin_point, DEFVAL(NodePath()));
+ ClassDB::bind_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path", "insert_at"), &SoftBody3D::pin_point, DEFVAL(NodePath()), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("is_point_pinned", "point_index"), &SoftBody3D::is_point_pinned);
ClassDB::bind_method(D_METHOD("set_ray_pickable", "ray_pickable"), &SoftBody3D::set_ray_pickable);
@@ -377,7 +390,7 @@ void SoftBody3D::_bind_methods() {
}
PackedStringArray SoftBody3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = MeshInstance3D::get_configuration_warnings();
if (mesh.is_null()) {
warnings.push_back(RTR("This body will be ignored until you set a mesh."));
@@ -662,10 +675,11 @@ void SoftBody3D::pin_point_toggle(int p_point_index) {
pin_point(p_point_index, !(-1 != _has_pinned_point(p_point_index)));
}
-void SoftBody3D::pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path) {
+void SoftBody3D::pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path, int p_insert_at) {
+ ERR_FAIL_COND_MSG(p_insert_at < -1 || p_insert_at >= pinned_points.size(), "Invalid index for pin point insertion position.");
_pin_point_on_physics_server(p_point_index, pin);
if (pin) {
- _add_pinned_point(p_point_index, p_spatial_attachment_path);
+ _add_pinned_point(p_point_index, p_spatial_attachment_path, p_insert_at);
} else {
_remove_pinned_point(p_point_index);
}
@@ -724,7 +738,7 @@ void SoftBody3D::_pin_point_on_physics_server(int p_point_index, bool pin) {
PhysicsServer3D::get_singleton()->soft_body_pin_point(physics_rid, p_point_index, pin);
}
-void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path) {
+void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path, int p_insert_at) {
SoftBody3D::PinnedPoint *pinned_point;
if (-1 == _get_pinned_point(p_point_index, pinned_point)) {
// Create new
@@ -737,7 +751,11 @@ void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_
pp.offset = (pp.spatial_attachment->get_global_transform().affine_inverse() * get_global_transform()).xform(PhysicsServer3D::get_singleton()->soft_body_get_point_global_position(physics_rid, pp.point_index));
}
- pinned_points.push_back(pp);
+ if (p_insert_at != -1) {
+ pinned_points.insert(p_insert_at, pp);
+ } else {
+ pinned_points.push_back(pp);
+ }
} else {
pinned_point->point_index = p_point_index;
diff --git a/scene/3d/soft_body_3d.h b/scene/3d/soft_body_3d.h
index ab30f7e654..b01d462d9f 100644
--- a/scene/3d/soft_body_3d.h
+++ b/scene/3d/soft_body_3d.h
@@ -126,6 +126,11 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ void _pin_point_bind_compat_94684(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath());
+ static void _bind_compatibility_methods();
+#endif
+
PackedStringArray get_configuration_warnings() const override;
public:
@@ -177,7 +182,7 @@ public:
Vector3 get_point_transform(int p_point_index);
void pin_point_toggle(int p_point_index);
- void pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath());
+ void pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath(), int p_insert_at = -1);
bool is_point_pinned(int p_point_index) const;
void _pin_point_deferred(int p_point_index, bool pin, const NodePath p_spatial_attachment_path);
@@ -193,7 +198,7 @@ private:
void _update_cache_pin_points_datas();
void _pin_point_on_physics_server(int p_point_index, bool pin);
- void _add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path);
+ void _add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path, int p_insert_at = -1);
void _reset_points_offsets();
void _remove_pinned_point(int p_point_index);
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index 50218a6d86..42460eec4c 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -132,7 +132,7 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
// Properly setup UVs for impostor textures (AtlasTexture).
Ref<AtlasTexture> atlas_tex = p_texture;
- if (atlas_tex != nullptr) {
+ if (atlas_tex.is_valid()) {
src_tsize[0] = atlas_tex->get_atlas()->get_width();
src_tsize[1] = atlas_tex->get_atlas()->get_height();
}
@@ -1324,7 +1324,7 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool
name = animation;
}
- ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name));
+ ERR_FAIL_COND_MSG(frames.is_null(), vformat("There is no animation with name '%s'.", name));
ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name));
if (frames->get_frame_count(name) == 0) {
@@ -1402,7 +1402,7 @@ void AnimatedSprite3D::set_animation(const StringName &p_name) {
emit_signal(SceneStringName(animation_changed));
- if (frames == nullptr) {
+ if (frames.is_null()) {
animation = StringName();
stop();
ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name));
diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp
index f14ae3a285..a59754c8cc 100644
--- a/scene/3d/visual_instance_3d.cpp
+++ b/scene/3d/visual_instance_3d.cpp
@@ -30,6 +30,8 @@
#include "visual_instance_3d.h"
+#include "core/config/project_settings.h"
+
AABB VisualInstance3D::get_aabb() const {
AABB ret;
GDVIRTUAL_CALL(_get_aabb, ret);
@@ -41,7 +43,38 @@ void VisualInstance3D::_update_visibility() {
return;
}
- RS::get_singleton()->instance_set_visible(get_instance(), is_visible_in_tree());
+ bool already_visible = _is_vi_visible();
+ bool visible = is_visible_in_tree();
+ _set_vi_visible(visible);
+
+ // If making visible, make sure the rendering server is up to date with the transform.
+ if (visible && !already_visible) {
+ if (!_is_using_identity_transform()) {
+ Transform3D gt = get_global_transform();
+ RS::get_singleton()->instance_set_transform(instance, gt);
+ }
+ }
+
+ RS::get_singleton()->instance_set_visible(instance, visible);
+}
+
+void VisualInstance3D::_physics_interpolated_changed() {
+ RenderingServer::get_singleton()->instance_set_interpolated(instance, is_physics_interpolated());
+}
+
+void VisualInstance3D::set_instance_use_identity_transform(bool p_enable) {
+ // Prevent sending instance transforms when using global coordinates.
+ _set_use_identity_transform(p_enable);
+
+ if (is_inside_tree()) {
+ if (p_enable) {
+ // Want to make sure instance is using identity transform.
+ RS::get_singleton()->instance_set_transform(instance, Transform3D());
+ } else {
+ // Want to make sure instance is up to date.
+ RS::get_singleton()->instance_set_transform(instance, get_global_transform());
+ }
+ }
}
void VisualInstance3D::_notification(int p_what) {
@@ -53,13 +86,52 @@ void VisualInstance3D::_notification(int p_what) {
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
- Transform3D gt = get_global_transform();
- RenderingServer::get_singleton()->instance_set_transform(instance, gt);
+ if (_is_vi_visible() || is_physics_interpolated_and_enabled()) {
+ if (!_is_using_identity_transform()) {
+ RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform());
+
+ // For instance when first adding to the tree, when the previous transform is
+ // unset, to prevent streaking from the origin.
+ if (_is_physics_interpolation_reset_requested() && is_physics_interpolated_and_enabled() && is_inside_tree()) {
+ if (_is_vi_visible()) {
+ _notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+ }
+ _set_physics_interpolation_reset_requested(false);
+ }
+ }
+ }
+ } break;
+
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (_is_vi_visible() && is_physics_interpolated() && is_inside_tree()) {
+ // We must ensure the RenderingServer transform is up to date before resetting.
+ // This is because NOTIFICATION_TRANSFORM_CHANGED is deferred,
+ // and cannot be relied to be called in order before NOTIFICATION_RESET_PHYSICS_INTERPOLATION.
+ if (!_is_using_identity_transform()) {
+ RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform());
+ }
+
+ RenderingServer::get_singleton()->instance_reset_physics_interpolation(instance);
+ }
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+ else if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) {
+
+ String node_name = is_inside_tree() ? String(get_path()) : String(get_name());
+ if (!_is_vi_visible()) {
+ WARN_PRINT("[Physics interpolation] NOTIFICATION_RESET_PHYSICS_INTERPOLATION only works with unhidden nodes: \"" + node_name + "\".");
+ }
+ if (!is_physics_interpolated()) {
+ WARN_PRINT("[Physics interpolation] NOTIFICATION_RESET_PHYSICS_INTERPOLATION only works with interpolated nodes: \"" + node_name + "\".");
+ }
+ }
+#endif
+
} break;
case NOTIFICATION_EXIT_WORLD: {
RenderingServer::get_singleton()->instance_set_scenario(instance, RID());
RenderingServer::get_singleton()->instance_attach_skeleton(instance, RID());
+ _set_vi_visible(false);
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
@@ -425,7 +497,7 @@ bool GeometryInstance3D::is_ignoring_occlusion_culling() {
}
PackedStringArray GeometryInstance3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (!Math::is_zero_approx(visibility_range_end) && visibility_range_end <= visibility_range_begin) {
warnings.push_back(RTR("The GeometryInstance3D visibility range's End distance is set to a non-zero value, but is lower than the Begin distance.\nThis means the GeometryInstance3D will never be visible.\nTo resolve this, set the End distance to 0 or to a value greater than the Begin distance."));
diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h
index 59ede26ac1..9b02c928b7 100644
--- a/scene/3d/visual_instance_3d.h
+++ b/scene/3d/visual_instance_3d.h
@@ -45,6 +45,9 @@ class VisualInstance3D : public Node3D {
protected:
void _update_visibility();
+ virtual void _physics_interpolated_changed() override;
+ void set_instance_use_identity_transform(bool p_enable);
+
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp
index ffca856fba..80ff176a98 100644
--- a/scene/3d/voxel_gi.cpp
+++ b/scene/3d/voxel_gi.cpp
@@ -518,7 +518,7 @@ AABB VoxelGI::get_aabb() const {
}
PackedStringArray VoxelGI::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("VoxelGI nodes are not supported when using the GL Compatibility backend yet. Support will be added in a future release."));
diff --git a/scene/3d/voxelizer.h b/scene/3d/voxelizer.h
index 6ea1cfdbb0..08d018eee9 100644
--- a/scene/3d/voxelizer.h
+++ b/scene/3d/voxelizer.h
@@ -35,9 +35,8 @@
class Voxelizer {
private:
- enum {
+ enum : uint32_t {
CHILD_EMPTY = 0xFFFFFFFF
-
};
struct Cell {
diff --git a/scene/3d/xr_hand_modifier_3d.cpp b/scene/3d/xr_hand_modifier_3d.cpp
index baaa9eee48..3b533da701 100644
--- a/scene/3d/xr_hand_modifier_3d.cpp
+++ b/scene/3d/xr_hand_modifier_3d.cpp
@@ -30,6 +30,7 @@
#include "xr_hand_modifier_3d.h"
+#include "core/config/project_settings.h"
#include "servers/xr/xr_pose.h"
#include "servers/xr_server.h"
@@ -207,6 +208,11 @@ void XRHandModifier3D::_process_modification() {
// Apply previous relative transforms if they are stored.
for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
+ const int bone = joints[joint].bone;
+ if (bone == -1) {
+ continue;
+ }
+
if (bone_update == BONE_UPDATE_FULL) {
skeleton->set_bone_pose_position(joints[joint].bone, previous_relative_transforms[joint].origin);
}
@@ -278,6 +284,17 @@ void XRHandModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) {
_get_joint_data();
}
+PackedStringArray XRHandModifier3D::get_configuration_warnings() const {
+ PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings();
+
+ // Detect OpenXR without the Hand Tracking extension.
+ if (GLOBAL_GET("xr/openxr/enabled") && !GLOBAL_GET("xr/openxr/extensions/hand_tracking")) {
+ warnings.push_back("XRHandModifier3D requires the OpenXR Hand Tracking extension to be enabled.");
+ }
+
+ return warnings;
+}
+
void XRHandModifier3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
diff --git a/scene/3d/xr_hand_modifier_3d.h b/scene/3d/xr_hand_modifier_3d.h
index 3d78f32b64..d58ccd0adf 100644
--- a/scene/3d/xr_hand_modifier_3d.h
+++ b/scene/3d/xr_hand_modifier_3d.h
@@ -55,6 +55,8 @@ public:
void set_bone_update(BoneUpdate p_bone_update);
BoneUpdate get_bone_update() const;
+ PackedStringArray get_configuration_warnings() const override;
+
void _notification(int p_what);
protected:
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index 3f4b962641..214c1f77ca 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -77,7 +77,7 @@ void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) {
}
PackedStringArray XRCamera3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Camera3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
// Warn if the node has a parent which isn't an XROrigin3D!
@@ -303,6 +303,8 @@ StringName XRNode3D::get_pose_name() const {
void XRNode3D::set_show_when_tracked(bool p_show) {
show_when_tracked = p_show;
+
+ _update_visibility();
}
bool XRNode3D::get_show_when_tracked() const {
@@ -361,6 +363,9 @@ void XRNode3D::_bind_tracker() {
if (pose.is_valid()) {
set_transform(pose->get_adjusted_transform());
_set_has_tracking_data(pose->get_has_tracking_data());
+ } else {
+ // Pose has been invalidated or was never set.
+ _set_has_tracking_data(false);
}
}
}
@@ -407,6 +412,10 @@ void XRNode3D::_pose_lost_tracking(const Ref<XRPose> &p_pose) {
}
void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) {
+ // Always update our visibility, we may have set our tracking data
+ // when conditions weren't right.
+ _update_visibility();
+
// Ignore if the has_tracking_data state isn't changing.
if (p_has_tracking_data == has_tracking_data) {
return;
@@ -415,10 +424,19 @@ void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) {
// Handle change of has_tracking_data.
has_tracking_data = p_has_tracking_data;
emit_signal(SNAME("tracking_changed"), has_tracking_data);
+}
+void XRNode3D::_update_visibility() {
// If configured, show or hide the node based on tracking data.
if (show_when_tracked) {
- set_visible(has_tracking_data);
+ // Only react to this if we have a primary interface.
+ XRServer *xr_server = XRServer::get_singleton();
+ if (xr_server != nullptr) {
+ Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
+ if (xr_interface.is_valid()) {
+ set_visible(has_tracking_data);
+ }
+ }
}
}
@@ -443,7 +461,7 @@ XRNode3D::~XRNode3D() {
}
PackedStringArray XRNode3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
// Warn if the node has a parent which isn't an XROrigin3D!
@@ -626,7 +644,7 @@ Plane XRAnchor3D::get_plane() const {
Vector<XROrigin3D *> XROrigin3D::origin_nodes;
PackedStringArray XROrigin3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
bool has_camera = false;
diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h
index a42f6d470f..94c3923433 100644
--- a/scene/3d/xr_nodes.h
+++ b/scene/3d/xr_nodes.h
@@ -95,6 +95,8 @@ protected:
void _pose_lost_tracking(const Ref<XRPose> &p_pose);
void _set_has_tracking_data(bool p_has_tracking_data);
+ void _update_visibility();
+
public:
void _validate_property(PropertyInfo &p_property) const;
void set_tracker(const StringName &p_tracker_name);