summaryrefslogtreecommitdiffstats
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/audio_stream_player_2d.cpp4
-rw-r--r--scene/2d/gpu_particles_2d.cpp23
-rw-r--r--scene/2d/navigation_link_2d.cpp133
-rw-r--r--scene/2d/navigation_link_2d.h13
-rw-r--r--scene/2d/physics/shape_cast_2d.cpp6
-rw-r--r--scene/2d/physics/shape_cast_2d.h2
-rw-r--r--scene/2d/polygon_2d.cpp2
-rw-r--r--scene/2d/tile_map.cpp16
-rw-r--r--scene/2d/tile_map.h4
-rw-r--r--scene/2d/tile_map_layer.cpp16
-rw-r--r--scene/2d/tile_map_layer.h4
-rw-r--r--scene/3d/audio_stream_player_3d.cpp4
-rw-r--r--scene/3d/camera_3d.cpp138
-rw-r--r--scene/3d/camera_3d.h32
-rw-r--r--scene/3d/gpu_particles_3d.cpp22
-rw-r--r--scene/3d/lightmap_gi.cpp3
-rw-r--r--scene/3d/navigation_link_3d.cpp106
-rw-r--r--scene/3d/navigation_link_3d.h9
-rw-r--r--scene/3d/node_3d.cpp147
-rw-r--r--scene/3d/node_3d.h45
-rw-r--r--scene/3d/occluder_instance_3d.cpp3
-rw-r--r--scene/3d/occluder_instance_3d.h1
-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/skeleton_ik_3d.cpp6
-rw-r--r--scene/3d/skeleton_modifier_3d.cpp2
-rw-r--r--scene/3d/soft_body_3d.cpp8
-rw-r--r--scene/3d/visual_instance_3d.cpp78
-rw-r--r--scene/3d/visual_instance_3d.h3
-rw-r--r--scene/animation/animation_blend_tree.cpp21
-rw-r--r--scene/animation/animation_blend_tree.h21
-rw-r--r--scene/audio/audio_stream_player.cpp4
-rw-r--r--scene/audio/audio_stream_player_internal.cpp13
-rw-r--r--scene/audio/audio_stream_player_internal.h5
-rw-r--r--scene/gui/rich_text_label.cpp10
-rw-r--r--scene/main/node.cpp35
-rw-r--r--scene/main/node.h25
-rw-r--r--scene/main/scene_tree.cpp65
-rw-r--r--scene/main/scene_tree.h16
-rw-r--r--scene/main/viewport.cpp8
-rw-r--r--scene/resources/2d/tile_set.cpp17
-rw-r--r--scene/resources/2d/tile_set.h1
-rw-r--r--scene/resources/3d/importer_mesh.cpp31
-rw-r--r--scene/resources/3d/importer_mesh.h4
-rw-r--r--scene/resources/3d/primitive_meshes.cpp42
-rw-r--r--scene/resources/3d/primitive_meshes.h5
-rw-r--r--scene/resources/font.cpp9
-rw-r--r--scene/resources/visual_shader.cpp19
48 files changed, 964 insertions, 226 deletions
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index 8e91dce425..89a0479de3 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -242,7 +242,7 @@ void AudioStreamPlayer2D::seek(float p_seconds) {
void AudioStreamPlayer2D::stop() {
setplay.set(-1);
- internal->stop();
+ internal->stop_basic();
}
bool AudioStreamPlayer2D::is_playing() const {
@@ -430,7 +430,7 @@ void AudioStreamPlayer2D::_bind_methods() {
}
AudioStreamPlayer2D::AudioStreamPlayer2D() {
- internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer2D::play), true));
+ internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer2D::play), callable_mp(this, &AudioStreamPlayer2D::stop), true));
cached_global_panning_strength = GLOBAL_GET("audio/general/2d_panning_strength");
set_hide_clip_children(true);
}
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index 1d3f1ceada..bfbdb49f22 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -688,6 +688,7 @@ void GPUParticles2D::_notification(int p_what) {
RS::get_singleton()->particles_set_speed_scale(particles, 0);
}
set_process_internal(true);
+ set_physics_process_internal(true);
previous_position = get_global_position();
} break;
@@ -711,15 +712,6 @@ void GPUParticles2D::_notification(int p_what) {
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
- const Vector3 velocity = Vector3((get_global_position() - previous_position).x, (get_global_position() - previous_position).y, 0.0) /
- 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) {
@@ -739,6 +731,19 @@ void GPUParticles2D::_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 = Vector3((get_global_position() - previous_position).x, (get_global_position() - previous_position).y, 0.0) /
+ 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;
}
}
diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp
index 04ba550888..111f5a7b78 100644
--- a/scene/2d/navigation_link_2d.cpp
+++ b/scene/2d/navigation_link_2d.cpp
@@ -41,6 +41,9 @@ void NavigationLink2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationLink2D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationLink2D::is_enabled);
+ ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationLink2D::set_navigation_map);
+ ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationLink2D::get_navigation_map);
+
ClassDB::bind_method(D_METHOD("set_bidirectional", "bidirectional"), &NavigationLink2D::set_bidirectional);
ClassDB::bind_method(D_METHOD("is_bidirectional"), &NavigationLink2D::is_bidirectional);
@@ -106,12 +109,7 @@ bool NavigationLink2D::_get(const StringName &p_name, Variant &r_ret) const {
void NavigationLink2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- if (enabled) {
- NavigationServer2D::get_singleton()->link_set_map(link, get_world_2d()->get_navigation_map());
- }
- current_global_transform = get_global_transform();
- NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
- NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
+ _link_enter_navigation_map();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
@@ -120,36 +118,15 @@ void NavigationLink2D::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
set_physics_process_internal(false);
- if (is_inside_tree()) {
- Transform2D new_global_transform = get_global_transform();
- if (current_global_transform != new_global_transform) {
- current_global_transform = new_global_transform;
- NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
- NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
- queue_redraw();
- }
- }
+ _link_update_transform();
} break;
case NOTIFICATION_EXIT_TREE: {
- NavigationServer2D::get_singleton()->link_set_map(link, RID());
+ _link_exit_navigation_map();
} break;
case NOTIFICATION_DRAW: {
#ifdef DEBUG_ENABLED
- if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled())) {
- Color color;
- if (enabled) {
- color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_color();
- } else {
- color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_disabled_color();
- }
-
- real_t radius = NavigationServer2D::get_singleton()->map_get_link_connection_radius(get_world_2d()->get_navigation_map());
-
- draw_line(get_start_position(), get_end_position(), color);
- draw_arc(get_start_position(), radius, 0, Math_TAU, 10, color);
- draw_arc(get_end_position(), radius, 0, Math_TAU, 10, color);
- }
+ _update_debug_mesh();
#endif // DEBUG_ENABLED
} break;
}
@@ -188,15 +165,32 @@ void NavigationLink2D::set_enabled(bool p_enabled) {
enabled = p_enabled;
- NavigationServer3D::get_singleton()->link_set_enabled(link, enabled);
+ NavigationServer2D::get_singleton()->link_set_enabled(link, enabled);
#ifdef DEBUG_ENABLED
- if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) {
- queue_redraw();
- }
+ queue_redraw();
#endif // DEBUG_ENABLED
}
+void NavigationLink2D::set_navigation_map(RID p_navigation_map) {
+ if (map_override == p_navigation_map) {
+ return;
+ }
+
+ map_override = p_navigation_map;
+
+ NavigationServer2D::get_singleton()->link_set_map(link, map_override);
+}
+
+RID NavigationLink2D::get_navigation_map() const {
+ if (map_override.is_valid()) {
+ return map_override;
+ } else if (is_inside_tree()) {
+ return get_world_2d()->get_navigation_map();
+ }
+ return RID();
+}
+
void NavigationLink2D::set_bidirectional(bool p_bidirectional) {
if (bidirectional == p_bidirectional) {
return;
@@ -255,9 +249,7 @@ void NavigationLink2D::set_start_position(Vector2 p_position) {
update_configuration_warnings();
#ifdef DEBUG_ENABLED
- if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) {
- queue_redraw();
- }
+ queue_redraw();
#endif // DEBUG_ENABLED
}
@@ -277,9 +269,7 @@ void NavigationLink2D::set_end_position(Vector2 p_position) {
update_configuration_warnings();
#ifdef DEBUG_ENABLED
- if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) {
- queue_redraw();
- }
+ queue_redraw();
#endif // DEBUG_ENABLED
}
@@ -347,6 +337,69 @@ PackedStringArray NavigationLink2D::get_configuration_warnings() const {
return warnings;
}
+void NavigationLink2D::_link_enter_navigation_map() {
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ if (map_override.is_valid()) {
+ NavigationServer2D::get_singleton()->link_set_map(link, map_override);
+ } else {
+ NavigationServer2D::get_singleton()->link_set_map(link, get_world_2d()->get_navigation_map());
+ }
+
+ current_global_transform = get_global_transform();
+
+ NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
+ NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
+ NavigationServer2D::get_singleton()->link_set_enabled(link, enabled);
+
+ queue_redraw();
+}
+
+void NavigationLink2D::_link_exit_navigation_map() {
+ NavigationServer2D::get_singleton()->link_set_map(link, RID());
+}
+
+void NavigationLink2D::_link_update_transform() {
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ Transform2D new_global_transform = get_global_transform();
+ if (current_global_transform != new_global_transform) {
+ current_global_transform = new_global_transform;
+ NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
+ NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
+ queue_redraw();
+ }
+}
+
+#ifdef DEBUG_ENABLED
+void NavigationLink2D::_update_debug_mesh() {
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ if (!Engine::get_singleton()->is_editor_hint() && !NavigationServer2D::get_singleton()->get_debug_enabled()) {
+ return;
+ }
+
+ Color color;
+ if (enabled) {
+ color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_color();
+ } else {
+ color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_disabled_color();
+ }
+
+ real_t radius = NavigationServer2D::get_singleton()->map_get_link_connection_radius(get_world_2d()->get_navigation_map());
+
+ draw_line(get_start_position(), get_end_position(), color);
+ draw_arc(get_start_position(), radius, 0, Math_TAU, 10, color);
+ draw_arc(get_end_position(), radius, 0, Math_TAU, 10, color);
+}
+#endif // DEBUG_ENABLED
+
NavigationLink2D::NavigationLink2D() {
link = NavigationServer2D::get_singleton()->link_create();
diff --git a/scene/2d/navigation_link_2d.h b/scene/2d/navigation_link_2d.h
index 2929691c04..c724096607 100644
--- a/scene/2d/navigation_link_2d.h
+++ b/scene/2d/navigation_link_2d.h
@@ -38,6 +38,7 @@ class NavigationLink2D : public Node2D {
bool enabled = true;
RID link;
+ RID map_override;
bool bidirectional = true;
uint32_t navigation_layers = 1;
Vector2 end_position;
@@ -47,6 +48,10 @@ class NavigationLink2D : public Node2D {
Transform2D current_global_transform;
+#ifdef DEBUG_ENABLED
+ void _update_debug_mesh();
+#endif // DEBUG_ENABLED
+
protected:
static void _bind_methods();
void _notification(int p_what);
@@ -66,6 +71,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; }
@@ -97,6 +105,11 @@ public:
NavigationLink2D();
~NavigationLink2D();
+
+private:
+ void _link_enter_navigation_map();
+ void _link_exit_navigation_map();
+ void _link_update_transform();
};
#endif // NAVIGATION_LINK_2D_H
diff --git a/scene/2d/physics/shape_cast_2d.cpp b/scene/2d/physics/shape_cast_2d.cpp
index 00be84b622..b92978bcad 100644
--- a/scene/2d/physics/shape_cast_2d.cpp
+++ b/scene/2d/physics/shape_cast_2d.cpp
@@ -382,7 +382,7 @@ bool ShapeCast2D::is_collide_with_bodies_enabled() const {
return collide_with_bodies;
}
-Array ShapeCast2D::_get_collision_result() const {
+Array ShapeCast2D::get_collision_result() const {
Array ret;
for (int i = 0; i < result.size(); ++i) {
@@ -464,7 +464,7 @@ void ShapeCast2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &ShapeCast2D::set_collide_with_bodies);
ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &ShapeCast2D::is_collide_with_bodies_enabled);
- ClassDB::bind_method(D_METHOD("_get_collision_result"), &ShapeCast2D::_get_collision_result);
+ ClassDB::bind_method(D_METHOD("get_collision_result"), &ShapeCast2D::get_collision_result);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D"), "set_shape", "get_shape");
@@ -473,7 +473,7 @@ void ShapeCast2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01,suffix:px"), "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_2D_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");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled");
diff --git a/scene/2d/physics/shape_cast_2d.h b/scene/2d/physics/shape_cast_2d.h
index 6b8fd5b798..d866dd4edb 100644
--- a/scene/2d/physics/shape_cast_2d.h
+++ b/scene/2d/physics/shape_cast_2d.h
@@ -60,7 +60,6 @@ class ShapeCast2D : public Node2D {
real_t collision_safe_fraction = 1.0;
real_t collision_unsafe_fraction = 1.0;
- Array _get_collision_result() const;
void _shape_changed();
protected:
@@ -102,6 +101,7 @@ public:
void force_shapecast_update();
bool is_colliding() const;
+ 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/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp
index 4266060466..42f7a75c0a 100644
--- a/scene/2d/polygon_2d.cpp
+++ b/scene/2d/polygon_2d.cpp
@@ -682,7 +682,7 @@ void Polygon2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "uv"), "set_uv", "get_uv");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "vertex_colors"), "set_vertex_colors", "get_vertex_colors");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons"), "set_polygons", "get_polygons");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_bones", "_get_bones");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_bones", "_get_bones");
ADD_PROPERTY(PropertyInfo(Variant::INT, "internal_vertex_count", PROPERTY_HINT_RANGE, "0,1000"), "set_internal_vertex_count", "get_internal_vertex_count");
}
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index c12b95314e..b10f2097da 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -532,6 +532,18 @@ TileData *TileMap::get_cell_tile_data(int p_layer, const Vector2i &p_coords, boo
}
}
+bool TileMap::is_cell_flipped_h(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const {
+ return get_cell_alternative_tile(p_layer, p_coords, p_use_proxies) & TileSetAtlasSource::TRANSFORM_FLIP_H;
+}
+
+bool TileMap::is_cell_flipped_v(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const {
+ return get_cell_alternative_tile(p_layer, p_coords, p_use_proxies) & TileSetAtlasSource::TRANSFORM_FLIP_V;
+}
+
+bool TileMap::is_cell_transposed(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const {
+ return get_cell_alternative_tile(p_layer, p_coords, p_use_proxies) & TileSetAtlasSource::TRANSFORM_TRANSPOSE;
+}
+
Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) {
TILEMAP_CALL_FOR_LAYER_V(p_layer, Ref<TileMapPattern>(), get_pattern, p_coords_array);
}
@@ -926,6 +938,10 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "layer", "coords", "use_proxies"), &TileMap::get_cell_alternative_tile, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_cell_tile_data", "layer", "coords", "use_proxies"), &TileMap::get_cell_tile_data, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("is_cell_flipped_h", "layer", "coords", "use_proxies"), &TileMap::is_cell_flipped_h, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("is_cell_flipped_v", "layer", "coords", "use_proxies"), &TileMap::is_cell_flipped_v, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("is_cell_transposed", "layer", "coords", "use_proxies"), &TileMap::is_cell_transposed, DEFVAL(false));
+
ClassDB::bind_method(D_METHOD("get_coords_for_body_rid", "body"), &TileMap::get_coords_for_body_rid);
ClassDB::bind_method(D_METHOD("get_layer_for_body_rid", "body"), &TileMap::get_layer_for_body_rid);
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index 690102f730..142dc1193f 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -167,6 +167,10 @@ public:
// Helper method to make accessing the data easier.
TileData *get_cell_tile_data(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
+ bool is_cell_flipped_h(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
+ bool is_cell_flipped_v(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
+ bool is_cell_transposed(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
+
// Patterns.
Ref<TileMapPattern> get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array);
Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern);
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index 437790bb99..7b125a6895 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -1773,6 +1773,10 @@ void TileMapLayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapLayer::get_cell_alternative_tile);
ClassDB::bind_method(D_METHOD("get_cell_tile_data", "coords"), &TileMapLayer::get_cell_tile_data);
+ ClassDB::bind_method(D_METHOD("is_cell_flipped_h", "coords"), &TileMapLayer::is_cell_flipped_h);
+ ClassDB::bind_method(D_METHOD("is_cell_flipped_v", "coords"), &TileMapLayer::is_cell_flipped_v);
+ ClassDB::bind_method(D_METHOD("is_cell_transposed", "coords"), &TileMapLayer::is_cell_transposed);
+
ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapLayer::get_used_cells);
ClassDB::bind_method(D_METHOD("get_used_cells_by_id", "source_id", "atlas_coords", "alternative_tile"), &TileMapLayer::get_used_cells_by_id, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE));
ClassDB::bind_method(D_METHOD("get_used_rect"), &TileMapLayer::get_used_rect);
@@ -2490,6 +2494,18 @@ Rect2i TileMapLayer::get_used_rect() const {
return used_rect_cache;
}
+bool TileMapLayer::is_cell_flipped_h(const Vector2i &p_coords) const {
+ return get_cell_alternative_tile(p_coords) & TileSetAtlasSource::TRANSFORM_FLIP_H;
+}
+
+bool TileMapLayer::is_cell_flipped_v(const Vector2i &p_coords) const {
+ return get_cell_alternative_tile(p_coords) & TileSetAtlasSource::TRANSFORM_FLIP_V;
+}
+
+bool TileMapLayer::is_cell_transposed(const Vector2i &p_coords) const {
+ return get_cell_alternative_tile(p_coords) & TileSetAtlasSource::TRANSFORM_TRANSPOSE;
+}
+
Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_array) {
ERR_FAIL_COND_V(tile_set.is_null(), nullptr);
diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h
index c71f13d7be..1a6d182094 100644
--- a/scene/2d/tile_map_layer.h
+++ b/scene/2d/tile_map_layer.h
@@ -438,6 +438,10 @@ public:
TypedArray<Vector2i> get_used_cells_by_id(int p_source_id = TileSet::INVALID_SOURCE, const Vector2i &p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) const;
Rect2i get_used_rect() const;
+ bool is_cell_flipped_h(const Vector2i &p_coords) const;
+ bool is_cell_flipped_v(const Vector2i &p_coords) const;
+ bool is_cell_transposed(const Vector2i &p_coords) const;
+
// Patterns.
Ref<TileMapPattern> get_pattern(TypedArray<Vector2i> p_coords_array);
void set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern);
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 6888462876..4d3f494ccf 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 {
@@ -862,7 +862,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/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/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/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index 038a78609f..3f8b0dfb8e 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -709,7 +709,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 +1072,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;
diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp
index dc776ebea2..bebba9a6c0 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;
@@ -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/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..6982df12f6 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();
}
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/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/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.cpp b/scene/3d/soft_body_3d.cpp
index f02cd9b700..4fe5dd2385 100644
--- a/scene/3d/soft_body_3d.cpp
+++ b/scene/3d/soft_body_3d.cpp
@@ -218,7 +218,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;
diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp
index f14ae3a285..79a01450dd 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: {
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/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index e4baae1afb..cdc85d2b2d 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -754,9 +754,6 @@ AnimationNode::NodeTimeInfo AnimationNodeAdd2::_process(const AnimationMixer::Pl
return nti;
}
-void AnimationNodeAdd2::_bind_methods() {
-}
-
AnimationNodeAdd2::AnimationNodeAdd2() {
add_input("in");
add_input("add");
@@ -800,9 +797,6 @@ AnimationNode::NodeTimeInfo AnimationNodeAdd3::_process(const AnimationMixer::Pl
return nti;
}
-void AnimationNodeAdd3::_bind_methods() {
-}
-
AnimationNodeAdd3::AnimationNodeAdd3() {
add_input("-add");
add_input("in");
@@ -845,9 +839,6 @@ bool AnimationNodeBlend2::has_filter() const {
return true;
}
-void AnimationNodeBlend2::_bind_methods() {
-}
-
AnimationNodeBlend2::AnimationNodeBlend2() {
add_input("in");
add_input("blend");
@@ -887,9 +878,6 @@ AnimationNode::NodeTimeInfo AnimationNodeBlend3::_process(const AnimationMixer::
return amount > 0.5 ? nti2 : (amount < -0.5 ? nti0 : nti1); // Hacky but good enough.
}
-void AnimationNodeBlend3::_bind_methods() {
-}
-
AnimationNodeBlend3::AnimationNodeBlend3() {
add_input("-blend");
add_input("in");
@@ -932,9 +920,6 @@ AnimationNode::NodeTimeInfo AnimationNodeSub2::_process(const AnimationMixer::Pl
return blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
}
-void AnimationNodeSub2::_bind_methods() {
-}
-
AnimationNodeSub2::AnimationNodeSub2() {
add_input("in");
add_input("sub");
@@ -972,9 +957,6 @@ AnimationNode::NodeTimeInfo AnimationNodeTimeScale::_process(const AnimationMixe
return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
}
-void AnimationNodeTimeScale::_bind_methods() {
-}
-
AnimationNodeTimeScale::AnimationNodeTimeScale() {
add_input("in");
}
@@ -1014,9 +996,6 @@ AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(const AnimationMixer
return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
}
-void AnimationNodeTimeSeek::_bind_methods() {
-}
-
AnimationNodeTimeSeek::AnimationNodeTimeSeek() {
add_input("in");
}
diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h
index c7ef7ed624..2add35d009 100644
--- a/scene/animation/animation_blend_tree.h
+++ b/scene/animation/animation_blend_tree.h
@@ -200,9 +200,6 @@ class AnimationNodeAdd2 : public AnimationNodeSync {
StringName add_amount = PNAME("add_amount");
-protected:
- static void _bind_methods();
-
public:
void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@@ -220,9 +217,6 @@ class AnimationNodeAdd3 : public AnimationNodeSync {
StringName add_amount = PNAME("add_amount");
-protected:
- static void _bind_methods();
-
public:
void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@@ -240,9 +234,6 @@ class AnimationNodeBlend2 : public AnimationNodeSync {
StringName blend_amount = PNAME("blend_amount");
-protected:
- static void _bind_methods();
-
public:
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@@ -259,9 +250,6 @@ class AnimationNodeBlend3 : public AnimationNodeSync {
StringName blend_amount = PNAME("blend_amount");
-protected:
- static void _bind_methods();
-
public:
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@@ -277,9 +265,6 @@ class AnimationNodeSub2 : public AnimationNodeSync {
StringName sub_amount = PNAME("sub_amount");
-protected:
- static void _bind_methods();
-
public:
void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@@ -297,9 +282,6 @@ class AnimationNodeTimeScale : public AnimationNode {
StringName scale = PNAME("scale");
-protected:
- static void _bind_methods();
-
public:
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@@ -316,9 +298,6 @@ class AnimationNodeTimeSeek : public AnimationNode {
StringName seek_pos_request = PNAME("seek_request");
-protected:
- static void _bind_methods();
-
public:
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index e90c1aa245..183c4af950 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -112,7 +112,7 @@ void AudioStreamPlayer::seek(float p_seconds) {
}
void AudioStreamPlayer::stop() {
- internal->stop();
+ internal->stop_basic();
}
bool AudioStreamPlayer::is_playing() const {
@@ -283,7 +283,7 @@ void AudioStreamPlayer::_bind_methods() {
}
AudioStreamPlayer::AudioStreamPlayer() {
- internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer::play), false));
+ internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer::play), callable_mp(this, &AudioStreamPlayer::stop), false));
}
AudioStreamPlayer::~AudioStreamPlayer() {
diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp
index 36c14e03d5..206408e3a7 100644
--- a/scene/audio/audio_stream_player_internal.cpp
+++ b/scene/audio/audio_stream_player_internal.cpp
@@ -132,7 +132,7 @@ Ref<AudioStreamPlayback> AudioStreamPlayerInternal::play_basic() {
}
ERR_FAIL_COND_V_MSG(!node->is_inside_tree(), stream_playback, "Playback can only happen when a node is inside the scene tree");
if (stream->is_monophonic() && is_playing()) {
- stop();
+ stop_callable.call();
}
stream_playback = stream->instantiate_playback();
ERR_FAIL_COND_V_MSG(stream_playback.is_null(), stream_playback, "Failed to instantiate playback.");
@@ -242,7 +242,7 @@ void AudioStreamPlayerInternal::set_stream(Ref<AudioStream> p_stream) {
if (stream.is_valid()) {
stream->disconnect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayerInternal::_update_stream_parameters));
}
- stop();
+ stop_callable.call();
stream = p_stream;
_update_stream_parameters();
if (stream.is_valid()) {
@@ -253,12 +253,12 @@ void AudioStreamPlayerInternal::set_stream(Ref<AudioStream> p_stream) {
void AudioStreamPlayerInternal::seek(float p_seconds) {
if (is_playing()) {
- stop();
+ stop_callable.call();
play_callable.call(p_seconds);
}
}
-void AudioStreamPlayerInternal::stop() {
+void AudioStreamPlayerInternal::stop_basic() {
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
AudioServer::get_singleton()->stop_playback_stream(playback);
}
@@ -289,7 +289,7 @@ void AudioStreamPlayerInternal::set_playing(bool p_enable) {
if (p_enable) {
play_callable.call(0.0);
} else {
- stop();
+ stop_callable.call();
}
}
@@ -339,9 +339,10 @@ StringName AudioStreamPlayerInternal::get_bus() const {
return SceneStringName(Master);
}
-AudioStreamPlayerInternal::AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, bool p_physical) {
+AudioStreamPlayerInternal::AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, const Callable &p_stop_callable, bool p_physical) {
node = p_node;
play_callable = p_play_callable;
+ stop_callable = p_stop_callable;
physical = p_physical;
bus = SceneStringName(Master);
diff --git a/scene/audio/audio_stream_player_internal.h b/scene/audio/audio_stream_player_internal.h
index ec4489067e..7d8faeba06 100644
--- a/scene/audio/audio_stream_player_internal.h
+++ b/scene/audio/audio_stream_player_internal.h
@@ -53,6 +53,7 @@ private:
Node *node = nullptr;
Callable play_callable;
+ Callable stop_callable;
bool physical = false;
AudioServer::PlaybackType playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT;
@@ -94,7 +95,7 @@ public:
Ref<AudioStreamPlayback> play_basic();
void seek(float p_seconds);
- void stop();
+ void stop_basic();
bool is_playing() const;
float get_playback_position();
@@ -110,7 +111,7 @@ public:
void set_playback_type(AudioServer::PlaybackType p_playback_type);
AudioServer::PlaybackType get_playback_type() const;
- AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, bool p_physical);
+ AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, const Callable &p_stop_callable, bool p_physical);
};
#endif // AUDIO_STREAM_PLAYER_INTERNAL_H
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 1da3668ebe..e9fe78e162 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -1448,6 +1448,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
bool line_clicked = false;
float text_rect_begin = 0.0;
int char_pos = -1;
+ bool char_clicked = false;
Line &l = p_frame->lines[p_line];
MutexLock lock(l.text_buf->get_mutex());
@@ -1578,6 +1579,9 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
}
if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) {
+ if (!p_meta) {
+ char_pos = rtl ? TS->shaped_text_get_range(rid).y : TS->shaped_text_get_range(rid).x;
+ }
if ((!rtl && p_click.x >= rect.position.x) || (rtl && p_click.x <= rect.position.x + rect.size.x)) {
if (p_meta) {
int64_t glyph_idx = TS->shaped_text_hit_test_grapheme(rid, p_click.x - rect.position.x);
@@ -1592,6 +1596,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
obj_rect.position.y += baseline_y;
if (p_click.y >= obj_rect.position.y && p_click.y <= obj_rect.position.y + obj_rect.size.y) {
char_pos = glyphs[glyph_idx].start;
+ char_clicked = true;
}
break;
}
@@ -1602,18 +1607,21 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
float fd = TS->font_get_descent(glyphs[glyph_idx].font_rid, glyphs[glyph_idx].font_size);
if (p_click.y >= baseline_y - fa && p_click.y <= baseline_y + fd) {
char_pos = glyphs[glyph_idx].start;
+ char_clicked = true;
}
} else if (!(glyphs[glyph_idx].flags & TextServer::GRAPHEME_IS_VIRTUAL)) {
// Hex code box.
Vector2 gl_size = TS->get_hex_code_box_size(glyphs[glyph_idx].font_size, glyphs[glyph_idx].index);
if (p_click.y >= baseline_y - gl_size.y * 0.9 && p_click.y <= baseline_y + gl_size.y * 0.2) {
char_pos = glyphs[glyph_idx].start;
+ char_clicked = true;
}
}
}
} else {
char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x);
char_pos = TS->shaped_text_closest_character_pos(rid, char_pos);
+ char_clicked = true;
}
}
line_clicked = true;
@@ -1621,7 +1629,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
}
// If table hit was detected, and line hit is in the table bounds use table hit.
- if (table_hit && (((char_pos + p_frame->lines[p_line].char_offset) >= table_range.x && (char_pos + p_frame->lines[p_line].char_offset) <= table_range.y) || char_pos == -1)) {
+ if (table_hit && (((char_pos + p_frame->lines[p_line].char_offset) >= table_range.x && (char_pos + p_frame->lines[p_line].char_offset) <= table_range.y) || !char_clicked)) {
if (r_click_frame != nullptr) {
*r_click_frame = table_click_frame;
}
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 0396f3ab4a..5c46abc732 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -138,6 +138,12 @@ void Node::_notification(int p_notification) {
get_tree()->nodes_in_tree_count++;
orphan_node_count--;
+
+ // Allow physics interpolated nodes to automatically reset when added to the tree
+ // (this is to save the user from doing this manually each time).
+ if (get_tree()->is_physics_interpolation_enabled()) {
+ _set_physics_interpolation_reset_requested(true);
+ }
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -437,6 +443,18 @@ void Node::_propagate_physics_interpolated(bool p_interpolated) {
data.blocked--;
}
+void Node::_propagate_physics_interpolation_reset_requested(bool p_requested) {
+ if (is_physics_interpolated()) {
+ data.physics_interpolation_reset_requested = p_requested;
+ }
+
+ data.blocked++;
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->_propagate_physics_interpolation_reset_requested(p_requested);
+ }
+ data.blocked--;
+}
+
void Node::move_child(Node *p_child, int p_index) {
ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Moving child node positions inside the SceneTree is only allowed from the main thread. Use call_deferred(\"move_child\",child,index).");
ERR_FAIL_NULL(p_child);
@@ -890,15 +908,23 @@ void Node::set_physics_interpolation_mode(PhysicsInterpolationMode p_mode) {
}
// If swapping from interpolated to non-interpolated, use this as an extra means to cause a reset.
- if (is_physics_interpolated() && !interpolate) {
- reset_physics_interpolation();
+ if (is_physics_interpolated() && !interpolate && is_inside_tree()) {
+ propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
}
_propagate_physics_interpolated(interpolate);
}
void Node::reset_physics_interpolation() {
- propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+ if (is_inside_tree()) {
+ propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+
+ // If `reset_physics_interpolation()` is called explicitly by the user
+ // (e.g. from scripts) then we prevent deferred auto-resets taking place.
+ // The user is trusted to call reset in the right order, and auto-reset
+ // will interfere with their control of prev / curr, so should be turned off.
+ _propagate_physics_interpolation_reset_requested(false);
+ }
}
bool Node::_is_enabled() const {
@@ -3825,6 +3851,9 @@ Node::Node() {
data.unhandled_key_input = false;
data.physics_interpolated = true;
+ data.physics_interpolation_reset_requested = false;
+ data.physics_interpolated_client_side = false;
+ data.use_identity_transform = false;
data.parent_owned = false;
data.in_constructor = true;
diff --git a/scene/main/node.h b/scene/main/node.h
index ee195ddef9..2f6372dad5 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -225,6 +225,21 @@ private:
// is switched on.
bool physics_interpolated : 1;
+ // We can auto-reset physics interpolation when e.g. adding a node for the first time.
+ bool physics_interpolation_reset_requested : 1;
+
+ // Most nodes need not be interpolated in the scene tree, physics interpolation
+ // is normally only needed in the RenderingServer. However if we need to read the
+ // interpolated transform of a node in the SceneTree, it is necessary to duplicate
+ // the interpolation logic client side, in order to prevent stalling the RenderingServer
+ // by reading back.
+ bool physics_interpolated_client_side : 1;
+
+ // For certain nodes (e.g. CPU particles in global mode)
+ // it can be useful to not send the instance transform to the
+ // RenderingServer, and specify the mesh in world space.
+ bool use_identity_transform : 1;
+
bool parent_owned : 1;
bool in_constructor : 1;
bool use_placeholder : 1;
@@ -263,6 +278,7 @@ private:
void _propagate_exit_tree();
void _propagate_after_exit_tree();
void _propagate_physics_interpolated(bool p_interpolated);
+ void _propagate_physics_interpolation_reset_requested(bool p_requested);
void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification);
void _propagate_groups_dirty();
Array _get_node_and_resource(const NodePath &p_path);
@@ -334,6 +350,15 @@ protected:
void _set_owner_nocheck(Node *p_owner);
void _set_name_nocheck(const StringName &p_name);
+ void _set_physics_interpolated_client_side(bool p_enable) { data.physics_interpolated_client_side = p_enable; }
+ bool _is_physics_interpolated_client_side() const { return data.physics_interpolated_client_side; }
+
+ void _set_physics_interpolation_reset_requested(bool p_enable) { data.physics_interpolation_reset_requested = p_enable; }
+ bool _is_physics_interpolation_reset_requested() const { return data.physics_interpolation_reset_requested; }
+
+ void _set_use_identity_transform(bool p_enable) { data.use_identity_transform = p_enable; }
+ bool _is_using_identity_transform() const { return data.use_identity_transform; }
+
//call from SceneTree
void _call_input(const Ref<InputEvent> &p_event);
void _call_shortcut_input(const Ref<InputEvent> &p_event);
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index ced6d9aaa6..f0c9e8a866 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -59,6 +59,7 @@
#include "servers/navigation_server_3d.h"
#include "servers/physics_server_2d.h"
#ifndef _3D_DISABLED
+#include "scene/3d/node_3d.h"
#include "scene/resources/3d/world_3d.h"
#include "servers/physics_server_3d.h"
#endif // _3D_DISABLED
@@ -118,6 +119,29 @@ void SceneTreeTimer::release_connections() {
SceneTreeTimer::SceneTreeTimer() {}
+#ifndef _3D_DISABLED
+// This should be called once per physics tick, to make sure the transform previous and current
+// is kept up to date on the few Node3Ds that are using client side physics interpolation.
+void SceneTree::ClientPhysicsInterpolation::physics_process() {
+ for (SelfList<Node3D> *E = _node_3d_list.first(); E;) {
+ Node3D *node_3d = E->self();
+
+ SelfList<Node3D> *current = E;
+
+ // Get the next element here BEFORE we potentially delete one.
+ E = E->next();
+
+ // This will return false if the Node3D has timed out ..
+ // i.e. if get_global_transform_interpolated() has not been called
+ // for a few seconds, we can delete from the list to keep processing
+ // to a minimum.
+ if (!node_3d->update_client_physics_interpolation_data()) {
+ _node_3d_list.remove(current);
+ }
+ }
+}
+#endif
+
void SceneTree::tree_changed() {
emit_signal(tree_changed_name);
}
@@ -466,9 +490,31 @@ bool SceneTree::is_physics_interpolation_enabled() const {
return _physics_interpolation_enabled;
}
+#ifndef _3D_DISABLED
+void SceneTree::client_physics_interpolation_add_node_3d(SelfList<Node3D> *p_elem) {
+ // This ensures that _update_physics_interpolation_data() will be called at least once every
+ // physics tick, to ensure the previous and current transforms are kept up to date.
+ _client_physics_interpolation._node_3d_list.add(p_elem);
+}
+
+void SceneTree::client_physics_interpolation_remove_node_3d(SelfList<Node3D> *p_elem) {
+ _client_physics_interpolation._node_3d_list.remove(p_elem);
+}
+#endif
+
void SceneTree::iteration_prepare() {
if (_physics_interpolation_enabled) {
+ // Make sure any pending transforms from the last tick / frame
+ // are flushed before pumping the interpolation prev and currents.
+ flush_transform_notifications();
RenderingServer::get_singleton()->tick();
+
+#ifndef _3D_DISABLED
+ // Any objects performing client physics interpolation
+ // should be given an opportunity to keep their previous transforms
+ // up to date before each new physics tick.
+ _client_physics_interpolation.physics_process();
+#endif
}
}
@@ -503,6 +549,14 @@ bool SceneTree::physics_process(double p_time) {
return _quit;
}
+void SceneTree::iteration_end() {
+ // When physics interpolation is active, we want all pending transforms
+ // to be flushed to the RenderingServer before finishing a physics tick.
+ if (_physics_interpolation_enabled) {
+ flush_transform_notifications();
+ }
+}
+
bool SceneTree::process(double p_time) {
if (MainLoop::process(p_time)) {
_quit = true;
@@ -570,6 +624,10 @@ bool SceneTree::process(double p_time) {
#endif // _3D_DISABLED
#endif // TOOLS_ENABLED
+ if (_physics_interpolation_enabled) {
+ RenderingServer::get_singleton()->pre_draw(true);
+ }
+
return _quit;
}
@@ -1761,6 +1819,13 @@ SceneTree::SceneTree() {
set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false));
+ // Always disable jitter fix if physics interpolation is enabled -
+ // Jitter fix will interfere with interpolation, and is not necessary
+ // when interpolation is active.
+ if (is_physics_interpolation_enabled()) {
+ Engine::get_singleton()->set_physics_jitter_fix(0);
+ }
+
// Initialize network state.
set_multiplayer(MultiplayerAPI::create_default_interface());
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index 6f0a61ec51..7e44541105 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -41,6 +41,9 @@
class PackedScene;
class Node;
+#ifndef _3D_DISABLED
+class Node3D;
+#endif
class Window;
class Material;
class Mesh;
@@ -120,6 +123,13 @@ private:
bool changed = false;
};
+#ifndef _3D_DISABLED
+ struct ClientPhysicsInterpolation {
+ SelfList<Node3D>::List _node_3d_list;
+ void physics_process();
+ } _client_physics_interpolation;
+#endif
+
Window *root = nullptr;
double physics_process_time = 0.0;
@@ -315,6 +325,7 @@ public:
virtual void iteration_prepare() override;
virtual bool physics_process(double p_time) override;
+ virtual void iteration_end() override;
virtual bool process(double p_time) override;
virtual void finalize() override;
@@ -423,6 +434,11 @@ public:
void set_physics_interpolation_enabled(bool p_enabled);
bool is_physics_interpolation_enabled() const;
+#ifndef _3D_DISABLED
+ void client_physics_interpolation_add_node_3d(SelfList<Node3D> *p_elem);
+ void client_physics_interpolation_remove_node_3d(SelfList<Node3D> *p_elem);
+#endif
+
SceneTree();
~SceneTree();
};
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 1302e3c53e..c85fda2371 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1511,6 +1511,7 @@ void Viewport::_gui_show_tooltip() {
r.size *= win_scale;
vr = window->get_usable_parent_rect();
}
+ r.size = r.size.ceil();
r.size = r.size.min(panel->get_max_size());
if (r.size.x + r.position.x > vr.size.x + vr.position.x) {
@@ -5024,6 +5025,13 @@ Viewport::Viewport() {
#endif // _3D_DISABLED
set_sdf_oversize(sdf_oversize); // Set to server.
+
+ // Physics interpolation mode for viewports is a special case.
+ // Typically viewports will be housed within Controls,
+ // and Controls default to PHYSICS_INTERPOLATION_MODE_OFF.
+ // Viewports can thus inherit physics interpolation OFF, which is unexpected.
+ // Setting to ON allows each viewport to have a fresh interpolation state.
+ set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_ON);
}
Viewport::~Viewport() {
diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp
index d124577d25..6a799e90ce 100644
--- a/scene/resources/2d/tile_set.cpp
+++ b/scene/resources/2d/tile_set.cpp
@@ -5275,11 +5275,26 @@ Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords, int
bool TileSetAtlasSource::is_position_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Vector2 p_position) const {
Size2 size = get_tile_texture_region(p_atlas_coords).size;
- Rect2 rect = Rect2(-size / 2 - get_tile_data(p_atlas_coords, p_alternative_tile)->get_texture_origin(), size);
+ TileData *tile_data = get_tile_data(p_atlas_coords, p_alternative_tile);
+ if (tile_data->get_transpose()) {
+ size = Size2(size.y, size.x);
+ }
+ Rect2 rect = Rect2(-size / 2 - tile_data->get_texture_origin(), size);
return rect.has_point(p_position);
}
+bool TileSetAtlasSource::is_rect_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Rect2 p_rect) const {
+ Size2 size = get_tile_texture_region(p_atlas_coords).size;
+ TileData *tile_data = get_tile_data(p_atlas_coords, p_alternative_tile);
+ if (tile_data->get_transpose()) {
+ size = Size2(size.y, size.x);
+ }
+ Rect2 rect = Rect2(-size / 2 - tile_data->get_texture_origin(), size);
+
+ return p_rect.intersection(rect) == p_rect;
+}
+
int TileSetAtlasSource::alternative_no_transform(int p_alternative_id) {
return p_alternative_id & ~(TRANSFORM_FLIP_H | TRANSFORM_FLIP_V | TRANSFORM_TRANSPOSE);
}
diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h
index e083fa45b9..51df972c8d 100644
--- a/scene/resources/2d/tile_set.h
+++ b/scene/resources/2d/tile_set.h
@@ -763,6 +763,7 @@ public:
Vector2i get_atlas_grid_size() const;
Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const;
bool is_position_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Vector2 p_position) const;
+ bool is_rect_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Rect2 p_rect) const;
static int alternative_no_transform(int p_alternative_id);
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index f912d2650d..91531699b4 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -269,7 +269,7 @@ void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_ma
} \
write_array[vert_idx] = transformed_vert;
-void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array) {
+void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array, bool p_raycast_normals) {
if (!SurfaceTool::simplify_scale_func) {
return;
}
@@ -432,6 +432,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
unsigned int index_target = 12; // Start with the smallest target, 4 triangles
unsigned int last_index_count = 0;
+ // Only used for normal raycasting
int split_vertex_count = vertex_count;
LocalVector<Vector3> split_vertex_normals;
LocalVector<int> split_vertex_indices;
@@ -441,7 +442,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
RandomPCG pcg;
pcg.seed(123456789); // Keep seed constant across imports
- Ref<StaticRaycaster> raycaster = StaticRaycaster::create();
+ Ref<StaticRaycaster> raycaster = p_raycast_normals ? StaticRaycaster::create() : Ref<StaticRaycaster>();
if (raycaster.is_valid()) {
raycaster->add_mesh(vertices, indices, 0);
raycaster->commit();
@@ -488,19 +489,22 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
}
new_indices.resize(new_index_count);
-
- LocalVector<LocalVector<int>> vertex_corners;
- vertex_corners.resize(vertex_count);
{
int *ptrw = new_indices.ptrw();
for (unsigned int j = 0; j < new_index_count; j++) {
- const int &remapped = vertex_inverse_remap[ptrw[j]];
- vertex_corners[remapped].push_back(j);
- ptrw[j] = remapped;
+ ptrw[j] = vertex_inverse_remap[ptrw[j]];
}
}
if (raycaster.is_valid()) {
+ LocalVector<LocalVector<int>> vertex_corners;
+ vertex_corners.resize(vertex_count);
+
+ int *ptrw = new_indices.ptrw();
+ for (unsigned int j = 0; j < new_index_count; j++) {
+ vertex_corners[ptrw[j]].push_back(j);
+ }
+
float error_factor = 1.0f / (scale * MAX(mesh_error, 0.15));
const float ray_bias = 0.05;
float ray_length = ray_bias + mesh_error * scale * 3.0f;
@@ -671,7 +675,10 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
}
}
- surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals);
+ if (raycaster.is_valid()) {
+ surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals);
+ }
+
surfaces.write[i].lods.sort_custom<Surface::LODComparator>();
for (int j = 0; j < surfaces.write[i].lods.size(); j++) {
@@ -682,6 +689,10 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
}
}
+void ImporterMesh::_generate_lods_bind(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array) {
+ generate_lods(p_normal_merge_angle, p_normal_split_angle, p_skin_pose_transform_array);
+}
+
bool ImporterMesh::has_mesh() const {
return mesh.is_valid();
}
@@ -1367,7 +1378,7 @@ void ImporterMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &ImporterMesh::set_surface_name);
ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &ImporterMesh::set_surface_material);
- ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle", "bone_transform_array"), &ImporterMesh::generate_lods);
+ ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle", "bone_transform_array"), &ImporterMesh::_generate_lods_bind);
ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &ImporterMesh::get_mesh, DEFVAL(Ref<ArrayMesh>()));
ClassDB::bind_method(D_METHOD("clear"), &ImporterMesh::clear);
diff --git a/scene/resources/3d/importer_mesh.h b/scene/resources/3d/importer_mesh.h
index ff8683449b..777f936030 100644
--- a/scene/resources/3d/importer_mesh.h
+++ b/scene/resources/3d/importer_mesh.h
@@ -86,6 +86,8 @@ protected:
void _set_data(const Dictionary &p_data);
Dictionary _get_data() const;
+ void _generate_lods_bind(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array);
+
static void _bind_methods();
public:
@@ -112,7 +114,7 @@ public:
void set_surface_material(int p_surface, const Ref<Material> &p_material);
- void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array);
+ void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array, bool p_raycast_normals = false);
void create_shadow_mesh();
Ref<ImporterMesh> get_shadow_mesh() const;
diff --git a/scene/resources/3d/primitive_meshes.cpp b/scene/resources/3d/primitive_meshes.cpp
index ee772f960a..128822d2cb 100644
--- a/scene/resources/3d/primitive_meshes.cpp
+++ b/scene/resources/3d/primitive_meshes.cpp
@@ -324,22 +324,43 @@ Vector2 PrimitiveMesh::get_uv2_scale(Vector2 p_margin_scale) const {
}
float PrimitiveMesh::get_lightmap_texel_size() const {
- float texel_size = GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size");
+ return texel_size;
+}
- if (texel_size <= 0.0) {
- texel_size = 0.2;
+void PrimitiveMesh::_on_settings_changed() {
+ float new_texel_size = float(GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size"));
+ if (new_texel_size <= 0.0) {
+ new_texel_size = 0.2;
+ }
+ if (texel_size == new_texel_size) {
+ return;
}
- return texel_size;
+ texel_size = new_texel_size;
+ _update_lightmap_size();
+ request_update();
}
PrimitiveMesh::PrimitiveMesh() {
+ ERR_FAIL_NULL(RenderingServer::get_singleton());
mesh = RenderingServer::get_singleton()->mesh_create();
+
+ ERR_FAIL_NULL(ProjectSettings::get_singleton());
+ texel_size = float(GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size"));
+ if (texel_size <= 0.0) {
+ texel_size = 0.2;
+ }
+ ProjectSettings *project_settings = ProjectSettings::get_singleton();
+ project_settings->connect("settings_changed", callable_mp(this, &PrimitiveMesh::_on_settings_changed));
}
PrimitiveMesh::~PrimitiveMesh() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RenderingServer::get_singleton()->free(mesh);
+
+ ERR_FAIL_NULL(ProjectSettings::get_singleton());
+ ProjectSettings *project_settings = ProjectSettings::get_singleton();
+ project_settings->disconnect("settings_changed", callable_mp(this, &PrimitiveMesh::_on_settings_changed));
}
/**
@@ -350,7 +371,6 @@ void CapsuleMesh::_update_lightmap_size() {
if (get_add_uv2()) {
// size must have changed, update lightmap size hint
Size2i _lightmap_size_hint;
- float texel_size = get_lightmap_texel_size();
float padding = get_uv2_padding();
float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend
@@ -365,7 +385,6 @@ void CapsuleMesh::_update_lightmap_size() {
void CapsuleMesh::_create_mesh_array(Array &p_arr) const {
bool _add_uv2 = get_add_uv2();
- float texel_size = get_lightmap_texel_size();
float _uv2_padding = get_uv2_padding() * texel_size;
create_mesh_array(p_arr, radius, height, radial_segments, rings, _add_uv2, _uv2_padding);
@@ -613,7 +632,6 @@ void BoxMesh::_update_lightmap_size() {
if (get_add_uv2()) {
// size must have changed, update lightmap size hint
Size2i _lightmap_size_hint;
- float texel_size = get_lightmap_texel_size();
float padding = get_uv2_padding();
float width = (size.x + size.z) / texel_size;
@@ -632,7 +650,6 @@ void BoxMesh::_create_mesh_array(Array &p_arr) const {
// With 3 faces along the width and 2 along the height of the texture we need to adjust our scale
// accordingly.
bool _add_uv2 = get_add_uv2();
- float texel_size = get_lightmap_texel_size();
float _uv2_padding = get_uv2_padding() * texel_size;
BoxMesh::create_mesh_array(p_arr, size, subdivide_w, subdivide_h, subdivide_d, _add_uv2, _uv2_padding);
@@ -937,7 +954,6 @@ void CylinderMesh::_update_lightmap_size() {
if (get_add_uv2()) {
// size must have changed, update lightmap size hint
Size2i _lightmap_size_hint;
- float texel_size = get_lightmap_texel_size();
float padding = get_uv2_padding();
float top_circumference = top_radius * Math_PI * 2.0;
@@ -957,7 +973,6 @@ void CylinderMesh::_update_lightmap_size() {
void CylinderMesh::_create_mesh_array(Array &p_arr) const {
bool _add_uv2 = get_add_uv2();
- float texel_size = get_lightmap_texel_size();
float _uv2_padding = get_uv2_padding() * texel_size;
create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings, cap_top, cap_bottom, _add_uv2, _uv2_padding);
@@ -1244,7 +1259,6 @@ void PlaneMesh::_update_lightmap_size() {
if (get_add_uv2()) {
// size must have changed, update lightmap size hint
Size2i _lightmap_size_hint;
- float texel_size = get_lightmap_texel_size();
float padding = get_uv2_padding();
_lightmap_size_hint.x = MAX(1.0, (size.x / texel_size) + padding);
@@ -1416,7 +1430,6 @@ void PrismMesh::_update_lightmap_size() {
if (get_add_uv2()) {
// size must have changed, update lightmap size hint
Size2i _lightmap_size_hint;
- float texel_size = get_lightmap_texel_size();
float padding = get_uv2_padding();
// left_to_right does not effect the surface area of the prism so we ignore that.
@@ -1440,7 +1453,6 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const {
// Only used if we calculate UV2
bool _add_uv2 = get_add_uv2();
- float texel_size = get_lightmap_texel_size();
float _uv2_padding = get_uv2_padding() * texel_size;
float horizontal_total = size.x + size.z + 2.0 * _uv2_padding;
@@ -1762,7 +1774,6 @@ void SphereMesh::_update_lightmap_size() {
if (get_add_uv2()) {
// size must have changed, update lightmap size hint
Size2i _lightmap_size_hint;
- float texel_size = get_lightmap_texel_size();
float padding = get_uv2_padding();
float _width = radius * Math_TAU;
@@ -1776,7 +1787,6 @@ void SphereMesh::_update_lightmap_size() {
void SphereMesh::_create_mesh_array(Array &p_arr) const {
bool _add_uv2 = get_add_uv2();
- float texel_size = get_lightmap_texel_size();
float _uv2_padding = get_uv2_padding() * texel_size;
create_mesh_array(p_arr, radius, height, radial_segments, rings, is_hemisphere, _add_uv2, _uv2_padding);
@@ -1950,7 +1960,6 @@ void TorusMesh::_update_lightmap_size() {
if (get_add_uv2()) {
// size must have changed, update lightmap size hint
Size2i _lightmap_size_hint;
- float texel_size = get_lightmap_texel_size();
float padding = get_uv2_padding();
float min_radius = inner_radius;
@@ -2000,7 +2009,6 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const {
// Only used if we calculate UV2
bool _add_uv2 = get_add_uv2();
- float texel_size = get_lightmap_texel_size();
float _uv2_padding = get_uv2_padding() * texel_size;
float horizontal_total = max_radius * Math_TAU + _uv2_padding;
diff --git a/scene/resources/3d/primitive_meshes.h b/scene/resources/3d/primitive_meshes.h
index 4d2d0760b3..fc2489923a 100644
--- a/scene/resources/3d/primitive_meshes.h
+++ b/scene/resources/3d/primitive_meshes.h
@@ -67,6 +67,9 @@ protected:
// assume primitive triangles as the type, correct for all but one and it will change this :)
Mesh::PrimitiveType primitive_type = Mesh::PRIMITIVE_TRIANGLES;
+ // Copy of our texel_size project setting.
+ float texel_size = 0.2;
+
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const {}
@@ -76,6 +79,8 @@ protected:
float get_lightmap_texel_size() const;
virtual void _update_lightmap_size(){};
+ void _on_settings_changed();
+
public:
virtual int get_surface_count() const override;
virtual int surface_get_array_len(int p_idx) const override;
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 104187775d..6b65ea4cfb 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -1482,8 +1482,8 @@ Error FontFile::_load_bitmap_font(const String &p_path, List<String> *r_image_fi
switch (block_type) {
case 1: /* info */ {
ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, "Invalid BMFont info block size.");
- base_size = f->get_16();
- if (base_size <= 0) {
+ base_size = ABS(static_cast<int16_t>(f->get_16()));
+ if (base_size == 0) {
base_size = 16;
}
uint8_t flags = f->get_8();
@@ -1776,7 +1776,10 @@ Error FontFile::_load_bitmap_font(const String &p_path, List<String> *r_image_fi
if (type == "info") {
if (keys.has("size")) {
- base_size = keys["size"].to_int();
+ base_size = ABS(keys["size"].to_int());
+ if (base_size == 0) {
+ base_size = 16;
+ }
}
if (keys.has("outline")) {
outline = keys["outline"].to_int();
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 4bedcb1820..1fa52b9c73 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -412,7 +412,6 @@ String VisualShaderNode::get_warning(Shader::Mode p_mode, VisualShader::Type p_t
}
VisualShaderNode::Category VisualShaderNode::get_category() const {
- WARN_PRINT(get_caption() + " is missing a category.");
return CATEGORY_NONE;
}
@@ -1825,7 +1824,7 @@ void VisualShader::reset_state() {
void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const {
//mode
- p_list->push_back(PropertyInfo(Variant::INT, PNAME("mode"), PROPERTY_HINT_ENUM, "Node3D,CanvasItem,Particles,Sky,Fog"));
+ p_list->push_back(PropertyInfo(Variant::INT, PNAME("mode"), PROPERTY_HINT_ENUM, "Spatial,CanvasItem,Particles,Sky,Fog"));
//render modes
HashMap<String, String> blend_mode_enums;
@@ -3004,9 +3003,9 @@ VisualShader::VisualShader() {
///////////////////////////////////////////////////////////
const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = {
- // Node3D
+ // Spatial
- // Node3D, Vertex
+ // Spatial, Vertex
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "vertex_id", "VERTEX_ID" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" },
@@ -3043,7 +3042,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = {
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom2", "CUSTOM2" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom3", "CUSTOM3" },
- // Node3D, Fragment
+ // Spatial, Fragment
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" },
@@ -3075,7 +3074,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = {
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_view", "NODE_POSITION_VIEW" },
- // Node3D, Light
+ // Spatial, Light
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" },
@@ -3883,9 +3882,9 @@ VisualShaderNodeParameterRef::VisualShaderNodeParameterRef() {
const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = {
////////////////////////////////////////////////////////////////////////
- // Node3D.
+ // Spatial.
////////////////////////////////////////////////////////////////////////
- // Node3D, Vertex.
+ // Spatial, Vertex.
////////////////////////////////////////////////////////////////////////
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Vertex", "VERTEX" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Normal", "NORMAL" },
@@ -3900,7 +3899,7 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = {
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "Model View Matrix", "MODELVIEW_MATRIX" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "Projection Matrix", "PROJECTION_MATRIX" },
////////////////////////////////////////////////////////////////////////
- // Node3D, Fragment.
+ // Spatial, Fragment.
////////////////////////////////////////////////////////////////////////
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Albedo", "ALBEDO" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha", "ALPHA" },
@@ -3932,7 +3931,7 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = {
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Depth", "DEPTH" },
////////////////////////////////////////////////////////////////////////
- // Node3D, Light.
+ // Spatial, Light.
////////////////////////////////////////////////////////////////////////
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Diffuse", "DIFFUSE_LIGHT" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Specular", "SPECULAR_LIGHT" },