feat: updated engine version to 4.4-rc1
This commit is contained in:
parent
ee00efde1f
commit
21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
|
|
|
|||
|
|
@ -401,10 +401,19 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() {
|
|||
if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) {
|
||||
total_max = MAX(total_max, listener_area_pos.length());
|
||||
}
|
||||
if (total_max > max_distance) {
|
||||
if (dist > total_max || total_max > max_distance) {
|
||||
if (!was_further_than_max_distance_last_frame) {
|
||||
HashMap<StringName, Vector<AudioFrame>> bus_volumes;
|
||||
for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
|
||||
// So the player gets muted and mostly stops mixing when out of range.
|
||||
AudioServer::get_singleton()->set_playback_bus_volumes_linear(playback, bus_volumes);
|
||||
}
|
||||
was_further_than_max_distance_last_frame = true; // Cache so we don't set the volume over and over.
|
||||
}
|
||||
continue; //can't hear this sound in this listener
|
||||
}
|
||||
}
|
||||
was_further_than_max_distance_last_frame = false;
|
||||
|
||||
float multiplier = Math::db_to_linear(_get_attenuation_db(dist));
|
||||
if (max_distance > 0) {
|
||||
|
|
@ -510,6 +519,14 @@ float AudioStreamPlayer3D::get_volume_db() const {
|
|||
return internal->volume_db;
|
||||
}
|
||||
|
||||
void AudioStreamPlayer3D::set_volume_linear(float p_volume) {
|
||||
set_volume_db(Math::linear_to_db(p_volume));
|
||||
}
|
||||
|
||||
float AudioStreamPlayer3D::get_volume_linear() const {
|
||||
return Math::db_to_linear(get_volume_db());
|
||||
}
|
||||
|
||||
void AudioStreamPlayer3D::set_unit_size(float p_volume) {
|
||||
unit_size = p_volume;
|
||||
update_gizmos();
|
||||
|
|
@ -544,7 +561,7 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
|
|||
setplay.set(p_from_pos);
|
||||
|
||||
// Sample handling.
|
||||
if (stream_playback->get_is_sample()) {
|
||||
if (stream_playback->get_is_sample() && stream_playback->get_sample_playback().is_valid()) {
|
||||
Ref<AudioSamplePlayback> sample_playback = stream_playback->get_sample_playback();
|
||||
sample_playback->offset = p_from_pos;
|
||||
sample_playback->bus = _get_actual_bus();
|
||||
|
|
@ -554,15 +571,12 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
|
|||
}
|
||||
|
||||
void AudioStreamPlayer3D::seek(float p_seconds) {
|
||||
if (is_playing()) {
|
||||
stop();
|
||||
play(p_seconds);
|
||||
}
|
||||
internal->seek(p_seconds);
|
||||
}
|
||||
|
||||
void AudioStreamPlayer3D::stop() {
|
||||
setplay.set(-1);
|
||||
internal->stop();
|
||||
internal->stop_basic();
|
||||
}
|
||||
|
||||
bool AudioStreamPlayer3D::is_playing() const {
|
||||
|
|
@ -573,6 +587,9 @@ bool AudioStreamPlayer3D::is_playing() const {
|
|||
}
|
||||
|
||||
float AudioStreamPlayer3D::get_playback_position() {
|
||||
if (setplay.get() >= 0) {
|
||||
return setplay.get(); // play() has been called this frame, but no playback exists just yet.
|
||||
}
|
||||
return internal->get_playback_position();
|
||||
}
|
||||
|
||||
|
|
@ -596,10 +613,6 @@ void AudioStreamPlayer3D::_set_playing(bool p_enable) {
|
|||
internal->set_playing(p_enable);
|
||||
}
|
||||
|
||||
bool AudioStreamPlayer3D::_is_active() const {
|
||||
return internal->is_active();
|
||||
}
|
||||
|
||||
void AudioStreamPlayer3D::_validate_property(PropertyInfo &p_property) const {
|
||||
internal->validate_property(p_property);
|
||||
}
|
||||
|
|
@ -757,6 +770,9 @@ void AudioStreamPlayer3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_volume_db", "volume_db"), &AudioStreamPlayer3D::set_volume_db);
|
||||
ClassDB::bind_method(D_METHOD("get_volume_db"), &AudioStreamPlayer3D::get_volume_db);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_volume_linear", "volume_linear"), &AudioStreamPlayer3D::set_volume_linear);
|
||||
ClassDB::bind_method(D_METHOD("get_volume_linear"), &AudioStreamPlayer3D::get_volume_linear);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_unit_size", "unit_size"), &AudioStreamPlayer3D::set_unit_size);
|
||||
ClassDB::bind_method(D_METHOD("get_unit_size"), &AudioStreamPlayer3D::get_unit_size);
|
||||
|
||||
|
|
@ -779,8 +795,7 @@ void AudioStreamPlayer3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer3D::set_autoplay);
|
||||
ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer3D::is_autoplay_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer3D::_set_playing);
|
||||
ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer3D::_is_active);
|
||||
ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer3D::_set_playing);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_max_distance", "meters"), &AudioStreamPlayer3D::set_max_distance);
|
||||
ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer3D::get_max_distance);
|
||||
|
|
@ -827,10 +842,11 @@ void AudioStreamPlayer3D::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "attenuation_model", PROPERTY_HINT_ENUM, "Inverse,Inverse Square,Logarithmic,Disabled"), "set_attenuation_model", "get_attenuation_model");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,80,suffix:dB"), "set_volume_db", "get_volume_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_linear", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_volume_linear", "get_volume_linear");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater"), "set_unit_size", "get_unit_size");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6,suffix:dB"), "set_max_db", "get_max_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_ONESHOT, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,or_greater,suffix:m"), "set_max_distance", "get_max_distance");
|
||||
|
|
@ -862,7 +878,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");
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ struct AudioFrame;
|
|||
class AudioStream;
|
||||
class AudioStreamPlayback;
|
||||
class AudioStreamPlayerInternal;
|
||||
class Camera3D;
|
||||
class VelocityTracker3D;
|
||||
|
||||
class AudioStreamPlayer3D : public Node3D {
|
||||
|
|
@ -105,6 +104,7 @@ private:
|
|||
float linear_attenuation = 0;
|
||||
|
||||
float max_distance = 0.0;
|
||||
bool was_further_than_max_distance_last_frame = false;
|
||||
|
||||
Ref<VelocityTracker3D> velocity_tracker;
|
||||
|
||||
|
|
@ -136,6 +136,9 @@ public:
|
|||
void set_volume_db(float p_volume);
|
||||
float get_volume_db() const;
|
||||
|
||||
void set_volume_linear(float p_volume);
|
||||
float get_volume_linear() const;
|
||||
|
||||
void set_unit_size(float p_volume);
|
||||
float get_unit_size() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "bone_name") {
|
||||
// Because it is a constant function, we cannot use the _get_skeleton_3d function.
|
||||
// Because it is a constant function, we cannot use the get_skeleton function.
|
||||
const Skeleton3D *parent = nullptr;
|
||||
if (use_external_skeleton) {
|
||||
if (external_skeleton_node_cache.is_valid()) {
|
||||
|
|
@ -134,7 +134,7 @@ void BoneAttachment3D::_update_external_skeleton_cache() {
|
|||
}
|
||||
|
||||
void BoneAttachment3D::_check_bind() {
|
||||
Skeleton3D *sk = _get_skeleton3d();
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
|
||||
if (sk && !bound) {
|
||||
if (bone_idx <= -1) {
|
||||
|
|
@ -148,7 +148,7 @@ void BoneAttachment3D::_check_bind() {
|
|||
}
|
||||
}
|
||||
|
||||
Skeleton3D *BoneAttachment3D::_get_skeleton3d() {
|
||||
Skeleton3D *BoneAttachment3D::get_skeleton() {
|
||||
if (use_external_skeleton) {
|
||||
if (external_skeleton_node_cache.is_valid()) {
|
||||
return Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache));
|
||||
|
|
@ -166,7 +166,7 @@ Skeleton3D *BoneAttachment3D::_get_skeleton3d() {
|
|||
|
||||
void BoneAttachment3D::_check_unbind() {
|
||||
if (bound) {
|
||||
Skeleton3D *sk = _get_skeleton3d();
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
|
||||
if (sk) {
|
||||
sk->disconnect(SceneStringName(skeleton_updated), callable_mp(this, &BoneAttachment3D::on_skeleton_update));
|
||||
|
|
@ -181,7 +181,7 @@ void BoneAttachment3D::_transform_changed() {
|
|||
}
|
||||
|
||||
if (override_pose && !overriding) {
|
||||
Skeleton3D *sk = _get_skeleton3d();
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
|
||||
ERR_FAIL_NULL_MSG(sk, "Cannot override pose: Skeleton not found!");
|
||||
ERR_FAIL_INDEX_MSG(bone_idx, sk->get_bone_count(), "Cannot override pose: Bone index is out of range!");
|
||||
|
|
@ -200,7 +200,7 @@ void BoneAttachment3D::_transform_changed() {
|
|||
|
||||
void BoneAttachment3D::set_bone_name(const String &p_name) {
|
||||
bone_name = p_name;
|
||||
Skeleton3D *sk = _get_skeleton3d();
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
set_bone_idx(sk->find_bone(bone_name));
|
||||
}
|
||||
|
|
@ -217,7 +217,7 @@ void BoneAttachment3D::set_bone_idx(const int &p_idx) {
|
|||
|
||||
bone_idx = p_idx;
|
||||
|
||||
Skeleton3D *sk = _get_skeleton3d();
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
if (bone_idx <= -1 || bone_idx >= sk->get_bone_count()) {
|
||||
WARN_PRINT("Bone index out of range! Cannot connect BoneAttachment to node!");
|
||||
|
|
@ -247,7 +247,7 @@ void BoneAttachment3D::set_override_pose(bool p_override) {
|
|||
set_notify_transform(override_pose);
|
||||
set_process_internal(override_pose);
|
||||
if (!override_pose && bone_idx >= 0) {
|
||||
Skeleton3D *sk = _get_skeleton3d();
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
sk->reset_bone_pose(bone_idx);
|
||||
}
|
||||
|
|
@ -318,7 +318,7 @@ void BoneAttachment3D::on_skeleton_update() {
|
|||
}
|
||||
updating = true;
|
||||
if (bone_idx >= 0) {
|
||||
Skeleton3D *sk = _get_skeleton3d();
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
if (!override_pose) {
|
||||
if (use_external_skeleton) {
|
||||
|
|
@ -369,6 +369,8 @@ BoneAttachment3D::BoneAttachment3D() {
|
|||
}
|
||||
|
||||
void BoneAttachment3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_skeleton"), &BoneAttachment3D::get_skeleton);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &BoneAttachment3D::set_bone_name);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_name"), &BoneAttachment3D::get_bone_name);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,9 +32,6 @@
|
|||
#define BONE_ATTACHMENT_3D_H
|
||||
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "scene/resources/bone_map.h"
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
class BoneAttachment3D : public Node3D {
|
||||
GDCLASS(BoneAttachment3D, Node3D);
|
||||
|
|
@ -57,7 +54,6 @@ class BoneAttachment3D : public Node3D {
|
|||
bool updating = false;
|
||||
void _transform_changed();
|
||||
void _update_external_skeleton_cache();
|
||||
Skeleton3D *_get_skeleton3d();
|
||||
|
||||
protected:
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
|
@ -79,6 +75,8 @@ public:
|
|||
|
||||
virtual PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
Skeleton3D *get_skeleton();
|
||||
|
||||
void set_bone_name(const String &p_name);
|
||||
String get_bone_name() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include "camera_3d.h"
|
||||
|
||||
#include "core/math/projection.h"
|
||||
#include "core/math/transform_interpolator.h"
|
||||
#include "scene/main/viewport.h"
|
||||
|
||||
void Camera3D::_update_audio_listener_state() {
|
||||
|
|
@ -88,7 +89,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 +107,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 +186,60 @@ 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;
|
||||
_update_process_mode();
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_SUSPENDED:
|
||||
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 +268,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;
|
||||
|
|
@ -250,7 +378,7 @@ void Camera3D::set_projection(ProjectionType p_mode) {
|
|||
|
||||
RID Camera3D::get_camera() const {
|
||||
return camera;
|
||||
};
|
||||
}
|
||||
|
||||
void Camera3D::make_current() {
|
||||
current = true;
|
||||
|
|
@ -296,7 +424,7 @@ bool Camera3D::is_current() const {
|
|||
Vector3 Camera3D::project_ray_normal(const Point2 &p_pos) const {
|
||||
Vector3 ray = project_local_ray_normal(p_pos);
|
||||
return get_camera_transform().basis.xform(ray).normalized();
|
||||
};
|
||||
}
|
||||
|
||||
Vector3 Camera3D::project_local_ray_normal(const Point2 &p_pos) const {
|
||||
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene.");
|
||||
|
|
@ -314,7 +442,7 @@ Vector3 Camera3D::project_local_ray_normal(const Point2 &p_pos) const {
|
|||
}
|
||||
|
||||
return ray;
|
||||
};
|
||||
}
|
||||
|
||||
Vector3 Camera3D::project_ray_origin(const Point2 &p_pos) const {
|
||||
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene.");
|
||||
|
|
@ -343,7 +471,7 @@ Vector3 Camera3D::project_ray_origin(const Point2 &p_pos) const {
|
|||
} else {
|
||||
return get_camera_transform().origin;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
bool Camera3D::is_position_behind(const Vector3 &p_pos) const {
|
||||
Transform3D t = get_global_transform();
|
||||
|
|
@ -379,6 +507,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;
|
||||
|
|
@ -396,9 +529,12 @@ Vector3 Camera3D::project_position(const Point2 &p_point, real_t p_z_depth) cons
|
|||
}
|
||||
Size2 viewport_size = get_viewport()->get_visible_rect().size;
|
||||
|
||||
Projection cm = _get_camera_projection(p_z_depth);
|
||||
Projection cm = _get_camera_projection(_near);
|
||||
|
||||
Vector2 vp_he = cm.get_viewport_half_extents();
|
||||
Plane z_slice(Vector3(0, 0, 1), -p_z_depth);
|
||||
Vector3 res;
|
||||
z_slice.intersect_3(cm.get_projection_plane(Projection::Planes::PLANE_RIGHT), cm.get_projection_plane(Projection::Planes::PLANE_TOP), &res);
|
||||
Vector2 vp_he(res.x, res.y);
|
||||
|
||||
Vector2 point;
|
||||
point.x = (p_point.x / viewport_size.x) * 2.0 - 1.0;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
41
engine/scene/3d/cpu_particles_3d.compat.inc
Normal file
41
engine/scene/3d/cpu_particles_3d.compat.inc
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**************************************************************************/
|
||||
/* cpu_particles_3d.compat.inc */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
void CPUParticles3D::_restart_bind_compat_92089() {
|
||||
restart(false);
|
||||
}
|
||||
|
||||
void CPUParticles3D::_bind_compatibility_methods() {
|
||||
ClassDB::bind_compatibility_method(D_METHOD("restart"), &CPUParticles3D::_restart_bind_compat_92089);
|
||||
}
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
|
@ -29,13 +29,14 @@
|
|||
/**************************************************************************/
|
||||
|
||||
#include "cpu_particles_3d.h"
|
||||
#include "cpu_particles_3d.compat.inc"
|
||||
|
||||
#include "core/math/random_number_generator.h"
|
||||
#include "scene/3d/camera_3d.h"
|
||||
#include "scene/3d/gpu_particles_3d.h"
|
||||
#include "scene/main/viewport.h"
|
||||
#include "scene/resources/curve_texture.h"
|
||||
#include "scene/resources/gradient_texture.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
#include "scene/resources/particle_process_material.h"
|
||||
|
||||
AABB CPUParticles3D::get_aabb() const {
|
||||
|
|
@ -47,15 +48,22 @@ void CPUParticles3D::set_emitting(bool p_emitting) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (p_emitting && !use_fixed_seed) {
|
||||
set_seed(Math::rand());
|
||||
}
|
||||
|
||||
emitting = p_emitting;
|
||||
if (emitting) {
|
||||
active = true;
|
||||
set_process_internal(true);
|
||||
_set_emitting();
|
||||
}
|
||||
}
|
||||
|
||||
// first update before rendering to avoid one frame delay after emitting starts
|
||||
if (time == 0) {
|
||||
_update_internal();
|
||||
}
|
||||
void CPUParticles3D::_set_emitting() {
|
||||
active = true;
|
||||
set_process_internal(true);
|
||||
// first update before rendering to avoid one frame delay after emitting starts
|
||||
if (time == 0) {
|
||||
_update_internal();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +240,7 @@ PackedStringArray CPUParticles3D::get_configuration_warnings() const {
|
|||
return warnings;
|
||||
}
|
||||
|
||||
void CPUParticles3D::restart() {
|
||||
void CPUParticles3D::restart(bool p_keep_seed) {
|
||||
time = 0;
|
||||
frame_remainder = 0;
|
||||
cycle = 0;
|
||||
|
|
@ -246,8 +254,12 @@ void CPUParticles3D::restart() {
|
|||
w[i].active = false;
|
||||
}
|
||||
}
|
||||
if (!p_keep_seed && !use_fixed_seed) {
|
||||
seed = Math::rand();
|
||||
}
|
||||
|
||||
set_emitting(true);
|
||||
emitting = true;
|
||||
_set_emitting();
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_direction(Vector3 p_direction) {
|
||||
|
|
@ -310,7 +322,7 @@ real_t CPUParticles3D::get_param_max(Parameter p_param) const {
|
|||
|
||||
static void _adjust_curve_range(const Ref<Curve> &p_curve, real_t p_min, real_t p_max) {
|
||||
Ref<Curve> curve = p_curve;
|
||||
if (!curve.is_valid()) {
|
||||
if (curve.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -410,14 +422,17 @@ bool CPUParticles3D::get_particle_flag(ParticleFlags p_particle_flag) const {
|
|||
void CPUParticles3D::set_emission_shape(EmissionShape p_shape) {
|
||||
ERR_FAIL_INDEX(p_shape, EMISSION_SHAPE_MAX);
|
||||
emission_shape = p_shape;
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_emission_sphere_radius(real_t p_radius) {
|
||||
emission_sphere_radius = p_radius;
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_emission_box_extents(Vector3 p_extents) {
|
||||
emission_box_extents = p_extents;
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_emission_points(const Vector<Vector3> &p_points) {
|
||||
|
|
@ -434,18 +449,27 @@ void CPUParticles3D::set_emission_colors(const Vector<Color> &p_colors) {
|
|||
|
||||
void CPUParticles3D::set_emission_ring_axis(Vector3 p_axis) {
|
||||
emission_ring_axis = p_axis;
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_emission_ring_height(real_t p_height) {
|
||||
emission_ring_height = p_height;
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_emission_ring_radius(real_t p_radius) {
|
||||
emission_ring_radius = p_radius;
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) {
|
||||
emission_ring_inner_radius = p_radius;
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_emission_ring_cone_angle(real_t p_angle) {
|
||||
emission_ring_cone_angle = p_angle;
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) {
|
||||
|
|
@ -501,6 +525,10 @@ real_t CPUParticles3D::get_emission_ring_inner_radius() const {
|
|||
return emission_ring_inner_radius;
|
||||
}
|
||||
|
||||
real_t CPUParticles3D::get_emission_ring_cone_angle() const {
|
||||
return emission_ring_cone_angle;
|
||||
}
|
||||
|
||||
CPUParticles3D::EmissionShape CPUParticles3D::get_emission_shape() const {
|
||||
return emission_shape;
|
||||
}
|
||||
|
|
@ -534,7 +562,35 @@ AABB CPUParticles3D::capture_aabb() const {
|
|||
return RS::get_singleton()->multimesh_get_aabb(multimesh);
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_use_fixed_seed(bool p_use_fixed_seed) {
|
||||
if (p_use_fixed_seed == use_fixed_seed) {
|
||||
return;
|
||||
}
|
||||
use_fixed_seed = p_use_fixed_seed;
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
bool CPUParticles3D::get_use_fixed_seed() const {
|
||||
return use_fixed_seed;
|
||||
}
|
||||
|
||||
void CPUParticles3D::set_seed(uint32_t p_seed) {
|
||||
seed = p_seed;
|
||||
}
|
||||
|
||||
uint32_t CPUParticles3D::get_seed() const {
|
||||
return seed;
|
||||
}
|
||||
|
||||
void CPUParticles3D::request_particles_process(real_t p_requested_process_time) {
|
||||
_requested_process_time = p_requested_process_time;
|
||||
}
|
||||
|
||||
void CPUParticles3D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "emitting") {
|
||||
p_property.hint = one_shot ? PROPERTY_HINT_ONESHOT : PROPERTY_HINT_NONE;
|
||||
}
|
||||
|
||||
if (p_property.name == "emission_sphere_radius" && (emission_shape != EMISSION_SHAPE_SPHERE && emission_shape != EMISSION_SHAPE_SPHERE_SURFACE)) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
|
|
@ -562,6 +618,10 @@ void CPUParticles3D::_validate_property(PropertyInfo &p_property) const {
|
|||
if (p_property.name.begins_with("scale_curve_") && !split_scale) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
|
||||
if (p_property.name == "seed" && !use_fixed_seed) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t idhash(uint32_t x) {
|
||||
|
|
@ -607,25 +667,27 @@ void CPUParticles3D::_update_internal() {
|
|||
|
||||
bool processed = false;
|
||||
|
||||
if (time == 0 && pre_process_time > 0.0) {
|
||||
double frame_time;
|
||||
if (fixed_fps > 0) {
|
||||
frame_time = 1.0 / fixed_fps;
|
||||
} else {
|
||||
frame_time = 1.0 / 30.0;
|
||||
}
|
||||
|
||||
double todo = pre_process_time;
|
||||
|
||||
while (todo >= 0) {
|
||||
_particles_process(frame_time);
|
||||
processed = true;
|
||||
todo -= frame_time;
|
||||
}
|
||||
double frame_time;
|
||||
if (fixed_fps > 0) {
|
||||
frame_time = 1.0 / fixed_fps;
|
||||
} else {
|
||||
frame_time = 1.0 / 30.0;
|
||||
}
|
||||
double todo = _requested_process_time;
|
||||
_requested_process_time = 0.;
|
||||
if (time == 0 && pre_process_time > 0.0) {
|
||||
todo += pre_process_time;
|
||||
}
|
||||
real_t tmp_speed = speed_scale;
|
||||
speed_scale = 1.0;
|
||||
while (todo > 0) {
|
||||
_particles_process(frame_time);
|
||||
todo -= frame_time;
|
||||
}
|
||||
speed_scale = tmp_speed;
|
||||
todo = 0.0;
|
||||
|
||||
if (fixed_fps > 0) {
|
||||
double frame_time = 1.0 / fixed_fps;
|
||||
double decr = frame_time;
|
||||
|
||||
double ldelta = delta;
|
||||
|
|
@ -634,7 +696,7 @@ void CPUParticles3D::_update_internal() {
|
|||
} else if (ldelta <= 0.0) { //unlikely but..
|
||||
ldelta = 0.001;
|
||||
}
|
||||
double todo = frame_remainder + ldelta;
|
||||
todo = frame_remainder + ldelta;
|
||||
|
||||
while (todo >= frame_time) {
|
||||
_particles_process(frame_time);
|
||||
|
|
@ -698,13 +760,13 @@ void CPUParticles3D::_particles_process(double p_delta) {
|
|||
double restart_phase = double(i) / double(pcount);
|
||||
|
||||
if (randomness_ratio > 0.0) {
|
||||
uint32_t seed = cycle;
|
||||
uint32_t _seed = cycle;
|
||||
if (restart_phase >= system_phase) {
|
||||
seed -= uint32_t(1);
|
||||
_seed -= uint32_t(1);
|
||||
}
|
||||
seed *= uint32_t(pcount);
|
||||
seed += uint32_t(i);
|
||||
double random = double(idhash(seed) % uint32_t(65536)) / 65536.0;
|
||||
_seed *= uint32_t(pcount);
|
||||
_seed += uint32_t(i);
|
||||
double random = double(idhash(_seed) % uint32_t(65536)) / 65536.0;
|
||||
restart_phase += randomness_ratio * random * 1.0 / double(pcount);
|
||||
}
|
||||
|
||||
|
|
@ -765,27 +827,27 @@ void CPUParticles3D::_particles_process(double p_delta) {
|
|||
tex_anim_offset = curve_parameters[PARAM_ANGLE]->sample(tv);
|
||||
}
|
||||
|
||||
p.seed = Math::rand();
|
||||
|
||||
p.angle_rand = Math::randf();
|
||||
p.scale_rand = Math::randf();
|
||||
p.hue_rot_rand = Math::randf();
|
||||
p.anim_offset_rand = Math::randf();
|
||||
p.seed = seed + uint32_t(1) + i + cycle;
|
||||
rng->set_seed(p.seed);
|
||||
p.angle_rand = rng->randf();
|
||||
p.scale_rand = rng->randf();
|
||||
p.hue_rot_rand = rng->randf();
|
||||
p.anim_offset_rand = rng->randf();
|
||||
|
||||
if (color_initial_ramp.is_valid()) {
|
||||
p.start_color_rand = color_initial_ramp->get_color_at_offset(Math::randf());
|
||||
p.start_color_rand = color_initial_ramp->get_color_at_offset(rng->randf());
|
||||
} else {
|
||||
p.start_color_rand = Color(1, 1, 1, 1);
|
||||
}
|
||||
|
||||
if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
|
||||
real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg_to_rad((Math::randf() * 2.0 - 1.0) * spread);
|
||||
real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg_to_rad((rng->randf() * 2.0 - 1.0) * spread);
|
||||
Vector3 rot = Vector3(Math::cos(angle1_rad), Math::sin(angle1_rad), 0.0);
|
||||
p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], (real_t)Math::randf());
|
||||
p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], rng->randf());
|
||||
} else {
|
||||
//initiate velocity spread in 3D
|
||||
real_t angle1_rad = Math::deg_to_rad((Math::randf() * (real_t)2.0 - (real_t)1.0) * spread);
|
||||
real_t angle2_rad = Math::deg_to_rad((Math::randf() * (real_t)2.0 - (real_t)1.0) * ((real_t)1.0 - flatness) * spread);
|
||||
real_t angle1_rad = Math::deg_to_rad((rng->randf() * (real_t)2.0 - (real_t)1.0) * spread);
|
||||
real_t angle2_rad = Math::deg_to_rad((rng->randf() * (real_t)2.0 - (real_t)1.0) * ((real_t)1.0 - flatness) * spread);
|
||||
|
||||
Vector3 direction_xz = Vector3(Math::sin(angle1_rad), 0, Math::cos(angle1_rad));
|
||||
Vector3 direction_yz = Vector3(0, Math::sin(angle2_rad), Math::cos(angle2_rad));
|
||||
|
|
@ -805,14 +867,14 @@ void CPUParticles3D::_particles_process(double p_delta) {
|
|||
binormal.normalize();
|
||||
Vector3 normal = binormal.cross(direction_nrm);
|
||||
spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;
|
||||
p.velocity = spread_direction * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], (real_t)Math::randf());
|
||||
p.velocity = spread_direction * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], rng->randf());
|
||||
}
|
||||
|
||||
real_t base_angle = tex_angle * Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand);
|
||||
p.custom[0] = Math::deg_to_rad(base_angle); //angle
|
||||
p.custom[1] = 0.0; //phase
|
||||
p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); //animation offset (0-1)
|
||||
p.custom[3] = (1.0 - Math::randf() * lifetime_randomness);
|
||||
p.custom[3] = (1.0 - rng->randf() * lifetime_randomness);
|
||||
p.transform = Transform3D();
|
||||
p.time = 0;
|
||||
p.lifetime = lifetime * p.custom[3];
|
||||
|
|
@ -823,20 +885,20 @@ void CPUParticles3D::_particles_process(double p_delta) {
|
|||
//do none
|
||||
} break;
|
||||
case EMISSION_SHAPE_SPHERE: {
|
||||
real_t s = 2.0 * Math::randf() - 1.0;
|
||||
real_t t = Math_TAU * Math::randf();
|
||||
real_t x = Math::randf();
|
||||
real_t s = 2.0 * rng->randf() - 1.0;
|
||||
real_t t = Math_TAU * rng->randf();
|
||||
real_t x = rng->randf();
|
||||
real_t radius = emission_sphere_radius * Math::sqrt(1.0 - s * s);
|
||||
p.transform.origin = Vector3(0, 0, 0).lerp(Vector3(radius * Math::cos(t), radius * Math::sin(t), emission_sphere_radius * s), x);
|
||||
} break;
|
||||
case EMISSION_SHAPE_SPHERE_SURFACE: {
|
||||
real_t s = 2.0 * Math::randf() - 1.0;
|
||||
real_t t = Math_TAU * Math::randf();
|
||||
real_t s = 2.0 * rng->randf() - 1.0;
|
||||
real_t t = Math_TAU * rng->randf();
|
||||
real_t radius = emission_sphere_radius * Math::sqrt(1.0 - s * s);
|
||||
p.transform.origin = Vector3(radius * Math::cos(t), radius * Math::sin(t), emission_sphere_radius * s);
|
||||
} break;
|
||||
case EMISSION_SHAPE_BOX: {
|
||||
p.transform.origin = Vector3(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0) * emission_box_extents;
|
||||
p.transform.origin = Vector3(rng->randf() * 2.0 - 1.0, rng->randf() * 2.0 - 1.0, rng->randf() * 2.0 - 1.0) * emission_box_extents;
|
||||
} break;
|
||||
case EMISSION_SHAPE_POINTS:
|
||||
case EMISSION_SHAPE_DIRECTED_POINTS: {
|
||||
|
|
@ -878,8 +940,14 @@ void CPUParticles3D::_particles_process(double p_delta) {
|
|||
}
|
||||
} break;
|
||||
case EMISSION_SHAPE_RING: {
|
||||
real_t ring_random_angle = Math::randf() * Math_TAU;
|
||||
real_t ring_random_radius = Math::sqrt(Math::randf() * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
|
||||
real_t radius_clamped = MAX(0.001, emission_ring_radius);
|
||||
real_t top_radius = MAX(radius_clamped - Math::tan(Math::deg_to_rad(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0);
|
||||
real_t y_pos = rng->randf();
|
||||
real_t skew = MAX(MIN(radius_clamped, top_radius) / MAX(radius_clamped, top_radius), 0.5);
|
||||
y_pos = radius_clamped < top_radius ? Math::pow(y_pos, skew) : 1.0 - Math::pow(y_pos, skew);
|
||||
real_t ring_random_angle = rng->randf() * Math_TAU;
|
||||
real_t ring_random_radius = Math::sqrt(rng->randf() * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
|
||||
ring_random_radius = Math::lerp(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos);
|
||||
Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized();
|
||||
Vector3 ortho_axis;
|
||||
if (axis.abs() == Vector3(1.0, 0.0, 0.0)) {
|
||||
|
|
@ -890,7 +958,7 @@ void CPUParticles3D::_particles_process(double p_delta) {
|
|||
ortho_axis = ortho_axis.normalized();
|
||||
ortho_axis.rotate(axis, ring_random_angle);
|
||||
ortho_axis = ortho_axis.normalized();
|
||||
p.transform.origin = ortho_axis * ring_random_radius + (Math::randf() * emission_ring_height - emission_ring_height / 2.0) * axis;
|
||||
p.transform.origin = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis;
|
||||
} break;
|
||||
case EMISSION_SHAPE_MAX: { // Max value for validity check.
|
||||
break;
|
||||
|
|
@ -1392,6 +1460,11 @@ void CPUParticles3D::convert_from_particles(Node *p_particles) {
|
|||
set_emission_shape(EmissionShape(material->get_emission_shape()));
|
||||
set_emission_sphere_radius(material->get_emission_sphere_radius());
|
||||
set_emission_box_extents(material->get_emission_box_extents());
|
||||
set_emission_ring_height(material->get_emission_ring_height());
|
||||
set_emission_ring_radius(material->get_emission_ring_radius());
|
||||
set_emission_ring_inner_radius(material->get_emission_ring_inner_radius());
|
||||
set_emission_ring_cone_angle(material->get_emission_ring_cone_angle());
|
||||
|
||||
Ref<CurveXYZTexture> scale3D = material->get_param_texture(ParticleProcessMaterial::PARAM_SCALE);
|
||||
if (scale3D.is_valid()) {
|
||||
split_scale = true;
|
||||
|
|
@ -1464,17 +1537,28 @@ void CPUParticles3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &CPUParticles3D::set_mesh);
|
||||
ClassDB::bind_method(D_METHOD("get_mesh"), &CPUParticles3D::get_mesh);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("restart"), &CPUParticles3D::restart);
|
||||
ClassDB::bind_method(D_METHOD("set_use_fixed_seed", "use_fixed_seed"), &CPUParticles3D::set_use_fixed_seed);
|
||||
ClassDB::bind_method(D_METHOD("get_use_fixed_seed"), &CPUParticles3D::get_use_fixed_seed);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
|
||||
ClassDB::bind_method(D_METHOD("set_seed", "seed"), &CPUParticles3D::set_seed);
|
||||
ClassDB::bind_method(D_METHOD("get_seed"), &CPUParticles3D::get_seed);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("restart", "keep_seed"), &CPUParticles3D::restart, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("request_particles_process", "process_time"), &CPUParticles3D::request_particles_process);
|
||||
ClassDB::bind_method(D_METHOD("capture_aabb"), &CPUParticles3D::capture_aabb);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting", PROPERTY_HINT_ONESHOT), "set_emitting", "is_emitting");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); // FIXME: Evaluate support for `exp` in integer properties, or remove this.
|
||||
ADD_GROUP("Time", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,exp,suffix:s"), "set_pre_process_time", "get_pre_process_time");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,10.0,0.01,or_greater,exp,suffix:s"), "set_pre_process_time", "get_pre_process_time");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_fixed_seed"), "set_use_fixed_seed", "get_use_fixed_seed");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "seed", PROPERTY_HINT_RANGE, "0," + itos(UINT32_MAX) + ",1"), "set_seed", "get_seed");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta");
|
||||
|
|
@ -1488,6 +1572,8 @@ void CPUParticles3D::_bind_methods() {
|
|||
BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME);
|
||||
BIND_ENUM_CONSTANT(DRAW_ORDER_VIEW_DEPTH);
|
||||
|
||||
ADD_PROPERTY_DEFAULT("seed", 0);
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_direction", "direction"), &CPUParticles3D::set_direction);
|
||||
|
|
@ -1550,6 +1636,9 @@ void CPUParticles3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &CPUParticles3D::set_emission_ring_inner_radius);
|
||||
ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &CPUParticles3D::get_emission_ring_inner_radius);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &CPUParticles3D::set_emission_ring_cone_angle);
|
||||
ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &CPUParticles3D::get_emission_ring_cone_angle);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity);
|
||||
ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity);
|
||||
|
||||
|
|
@ -1577,9 +1666,10 @@ void CPUParticles3D::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle");
|
||||
ADD_GROUP("Particle Flags", "particle_flag_");
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y);
|
||||
|
|
@ -1684,6 +1774,9 @@ CPUParticles3D::CPUParticles3D() {
|
|||
|
||||
set_emitting(true);
|
||||
set_amount(8);
|
||||
set_seed(Math::rand());
|
||||
|
||||
rng.instantiate();
|
||||
|
||||
set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0);
|
||||
set_param_min(PARAM_ANGULAR_VELOCITY, 0);
|
||||
|
|
@ -1716,6 +1809,7 @@ CPUParticles3D::CPUParticles3D() {
|
|||
set_emission_ring_height(1);
|
||||
set_emission_ring_radius(1);
|
||||
set_emission_ring_inner_radius(0);
|
||||
set_emission_ring_cone_angle(90);
|
||||
|
||||
set_gravity(Vector3(0, -9.8, 0));
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@
|
|||
|
||||
#include "scene/3d/visual_instance_3d.h"
|
||||
|
||||
class RandomNumberGenerator;
|
||||
|
||||
class CPUParticles3D : public GeometryInstance3D {
|
||||
private:
|
||||
GDCLASS(CPUParticles3D, GeometryInstance3D);
|
||||
|
|
@ -134,6 +136,7 @@ private:
|
|||
|
||||
double lifetime = 1.0;
|
||||
double pre_process_time = 0.0;
|
||||
double _requested_process_time = 0.0;
|
||||
real_t explosiveness_ratio = 0.0;
|
||||
real_t randomness_ratio = 0.0;
|
||||
double lifetime_randomness = 0.0;
|
||||
|
|
@ -142,6 +145,8 @@ private:
|
|||
bool local_coords = false;
|
||||
int fixed_fps = 0;
|
||||
bool fractional_delta = true;
|
||||
uint32_t seed = 0;
|
||||
bool use_fixed_seed = false;
|
||||
|
||||
Transform3D inv_emission_transform;
|
||||
|
||||
|
|
@ -178,6 +183,7 @@ private:
|
|||
real_t emission_ring_height = 0.0;
|
||||
real_t emission_ring_radius = 0.0;
|
||||
real_t emission_ring_inner_radius = 0.0;
|
||||
real_t emission_ring_cone_angle = 0.0;
|
||||
|
||||
Ref<Curve> scale_curve_x;
|
||||
Ref<Curve> scale_curve_y;
|
||||
|
|
@ -186,9 +192,12 @@ private:
|
|||
|
||||
Vector3 gravity = Vector3(0, -9.8, 0);
|
||||
|
||||
Ref<RandomNumberGenerator> rng;
|
||||
|
||||
void _update_internal();
|
||||
void _particles_process(double p_delta);
|
||||
void _update_particle_data_buffer();
|
||||
void _set_emitting();
|
||||
|
||||
Mutex update_mutex;
|
||||
|
||||
|
|
@ -201,6 +210,11 @@ protected:
|
|||
void _notification(int p_what);
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void _restart_bind_compat_92089();
|
||||
static void _bind_compatibility_methods();
|
||||
#endif
|
||||
|
||||
public:
|
||||
AABB get_aabb() const override;
|
||||
|
||||
|
|
@ -240,6 +254,14 @@ public:
|
|||
void set_mesh(const Ref<Mesh> &p_mesh);
|
||||
Ref<Mesh> get_mesh() const;
|
||||
|
||||
void set_use_fixed_seed(bool p_use_fixed_seed = false);
|
||||
bool get_use_fixed_seed() const;
|
||||
|
||||
void set_seed(uint32_t p_seed);
|
||||
uint32_t get_seed() const;
|
||||
|
||||
void request_particles_process(real_t p_requested_process_time);
|
||||
|
||||
///////////////////
|
||||
|
||||
void set_direction(Vector3 p_direction);
|
||||
|
|
@ -282,6 +304,7 @@ public:
|
|||
void set_emission_ring_height(real_t p_height);
|
||||
void set_emission_ring_radius(real_t p_radius);
|
||||
void set_emission_ring_inner_radius(real_t p_radius);
|
||||
void set_emission_ring_cone_angle(real_t p_angle);
|
||||
void set_scale_curve_x(Ref<Curve> p_scale_curve);
|
||||
void set_scale_curve_y(Ref<Curve> p_scale_curve);
|
||||
void set_scale_curve_z(Ref<Curve> p_scale_curve);
|
||||
|
|
@ -297,6 +320,7 @@ public:
|
|||
real_t get_emission_ring_height() const;
|
||||
real_t get_emission_ring_radius() const;
|
||||
real_t get_emission_ring_inner_radius() const;
|
||||
real_t get_emission_ring_cone_angle() const;
|
||||
Ref<Curve> get_scale_curve_x() const;
|
||||
Ref<Curve> get_scale_curve_y() const;
|
||||
Ref<Curve> get_scale_curve_z() const;
|
||||
|
|
@ -307,7 +331,7 @@ public:
|
|||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
void restart();
|
||||
void restart(bool p_keep_seed = false);
|
||||
|
||||
void convert_from_particles(Node *p_particles);
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,20 @@ void Decal::set_texture(DecalTexture p_type, const Ref<Texture2D> &p_texture) {
|
|||
ERR_FAIL_INDEX(p_type, TEXTURE_MAX);
|
||||
textures[p_type] = p_texture;
|
||||
RID texture_rid = p_texture.is_valid() ? p_texture->get_rid() : RID();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (p_texture.is_valid() &&
|
||||
(p_texture->is_class("AnimatedTexture") ||
|
||||
p_texture->is_class("AtlasTexture") ||
|
||||
p_texture->is_class("CameraTexture") ||
|
||||
p_texture->is_class("CanvasTexture") ||
|
||||
p_texture->is_class("MeshTexture") ||
|
||||
p_texture->is_class("Texture2DRD") ||
|
||||
p_texture->is_class("ViewportTexture"))) {
|
||||
WARN_PRINT(vformat("%s cannot be used as a Decal texture (%s). As a workaround, assign the value returned by %s's `get_image()` instead.", p_texture->get_class(), get_path(), p_texture->get_class()));
|
||||
}
|
||||
#endif
|
||||
|
||||
RS::get_singleton()->decal_set_texture(decal, RS::DecalTexture(p_type), texture_rid);
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
|
@ -163,10 +177,10 @@ void Decal::_validate_property(PropertyInfo &p_property) const {
|
|||
}
|
||||
|
||||
PackedStringArray Decal::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
|
||||
|
||||
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
|
||||
warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile rendering backends."));
|
||||
warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile renderers."));
|
||||
return warnings;
|
||||
}
|
||||
|
||||
|
|
@ -225,10 +239,12 @@ void Decal::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,suffix:m"), "set_size", "get_size");
|
||||
|
||||
ADD_GROUP("Textures", "texture_");
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_albedo", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_ALBEDO);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_NORMAL);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_orm", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_ORM);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_emission", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_EMISSION);
|
||||
// Only allow texture types that display correctly.
|
||||
const String texture_hint = "Texture2D,-AnimatedTexture,-AtlasTexture,-CameraTexture,-CanvasTexture,-MeshTexture,-Texture2DRD,-ViewportTexture";
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_albedo", PROPERTY_HINT_RESOURCE_TYPE, texture_hint), "set_texture", "get_texture", TEXTURE_ALBEDO);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_normal", PROPERTY_HINT_RESOURCE_TYPE, texture_hint), "set_texture", "get_texture", TEXTURE_NORMAL);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_orm", PROPERTY_HINT_RESOURCE_TYPE, texture_hint), "set_texture", "get_texture", TEXTURE_ORM);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_emission", PROPERTY_HINT_RESOURCE_TYPE, texture_hint), "set_texture", "get_texture", TEXTURE_EMISSION);
|
||||
|
||||
ADD_GROUP("Parameters", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_emission_energy", "get_emission_energy");
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@
|
|||
/**************************************************************************/
|
||||
|
||||
#include "fog_volume.h"
|
||||
|
||||
#include "scene/main/viewport.h"
|
||||
#include "scene/resources/environment.h"
|
||||
|
||||
///////////////////////////
|
||||
|
|
@ -116,12 +118,12 @@ AABB FogVolume::get_aabb() const {
|
|||
}
|
||||
|
||||
PackedStringArray FogVolume::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
|
||||
|
||||
Ref<Environment> environment = get_viewport()->find_world_3d()->get_environment();
|
||||
|
||||
if (OS::get_singleton()->get_current_rendering_method() != "forward_plus") {
|
||||
warnings.push_back(RTR("Fog Volumes are only visible when using the Forward+ backend."));
|
||||
warnings.push_back(RTR("Fog Volumes are only visible when using the Forward+ renderer."));
|
||||
return warnings;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@
|
|||
|
||||
#include "core/templates/rid.h"
|
||||
#include "scene/3d/visual_instance_3d.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "scene/main/viewport.h"
|
||||
#include "scene/resources/material.h"
|
||||
|
||||
class FogVolume : public VisualInstance3D {
|
||||
|
|
|
|||
41
engine/scene/3d/gpu_particles_3d.compat.inc
Normal file
41
engine/scene/3d/gpu_particles_3d.compat.inc
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**************************************************************************/
|
||||
/* gpu_particles_3d.compat.inc */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
void GPUParticles3D::_restart_bind_compat_92089() {
|
||||
restart(false);
|
||||
}
|
||||
|
||||
void GPUParticles3D::_bind_compatibility_methods() {
|
||||
ClassDB::bind_compatibility_method(D_METHOD("restart"), &GPUParticles3D::_restart_bind_compat_92089);
|
||||
}
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
/**************************************************************************/
|
||||
|
||||
#include "gpu_particles_3d.h"
|
||||
#include "gpu_particles_3d.compat.inc"
|
||||
|
||||
#include "scene/3d/cpu_particles_3d.h"
|
||||
#include "scene/resources/curve_texture.h"
|
||||
|
|
@ -41,7 +42,9 @@ AABB GPUParticles3D::get_aabb() const {
|
|||
|
||||
void GPUParticles3D::set_emitting(bool p_emitting) {
|
||||
// Do not return even if `p_emitting == emitting` because `emitting` is just an approximation.
|
||||
|
||||
if (p_emitting && p_emitting != emitting && !use_fixed_seed) {
|
||||
set_seed(Math::rand());
|
||||
}
|
||||
if (p_emitting && one_shot) {
|
||||
if (!active && !emitting) {
|
||||
// Last cycle ended.
|
||||
|
|
@ -96,6 +99,27 @@ void GPUParticles3D::set_one_shot(bool p_one_shot) {
|
|||
}
|
||||
}
|
||||
|
||||
void GPUParticles3D::set_use_fixed_seed(bool p_use_fixed_seed) {
|
||||
if (p_use_fixed_seed == use_fixed_seed) {
|
||||
return;
|
||||
}
|
||||
use_fixed_seed = p_use_fixed_seed;
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
bool GPUParticles3D::get_use_fixed_seed() const {
|
||||
return use_fixed_seed;
|
||||
}
|
||||
|
||||
void GPUParticles3D::set_seed(uint32_t p_seed) {
|
||||
seed = p_seed;
|
||||
RS::get_singleton()->particles_set_seed(particles, p_seed);
|
||||
}
|
||||
|
||||
uint32_t GPUParticles3D::get_seed() const {
|
||||
return seed;
|
||||
}
|
||||
|
||||
void GPUParticles3D::set_pre_process_time(double p_time) {
|
||||
pre_process_time = p_time;
|
||||
RS::get_singleton()->particles_set_pre_process_time(particles, pre_process_time);
|
||||
|
|
@ -123,10 +147,23 @@ void GPUParticles3D::set_use_local_coordinates(bool p_enable) {
|
|||
}
|
||||
|
||||
void GPUParticles3D::set_process_material(const Ref<Material> &p_material) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (process_material.is_valid()) {
|
||||
if (Ref<ParticleProcessMaterial>(process_material).is_valid()) {
|
||||
process_material->disconnect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
process_material = p_material;
|
||||
RID material_rid;
|
||||
if (process_material.is_valid()) {
|
||||
material_rid = process_material->get_rid();
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (Ref<ParticleProcessMaterial>(process_material).is_valid()) {
|
||||
process_material->connect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
RS::get_singleton()->particles_set_process_material(particles, material_rid);
|
||||
|
||||
|
|
@ -381,22 +418,25 @@ PackedStringArray GPUParticles3D::get_configuration_warnings() const {
|
|||
warnings.push_back(RTR("Only one Trail mesh is supported. If you want to use more than a single mesh, a Skin is needed (see documentation)."));
|
||||
}
|
||||
|
||||
if ((dp_count || !skin.is_null()) && (missing_trails || no_materials)) {
|
||||
if ((dp_count || skin.is_valid()) && (missing_trails || no_materials)) {
|
||||
warnings.push_back(RTR("Trails enabled, but one or more mesh materials are either missing or not set for trails rendering."));
|
||||
}
|
||||
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
|
||||
warnings.push_back(RTR("Particle trails are only available when using the Forward+ or Mobile rendering backends."));
|
||||
warnings.push_back(RTR("Particle trails are only available when using the Forward+ or Mobile renderers."));
|
||||
}
|
||||
}
|
||||
|
||||
if (sub_emitter != NodePath() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
|
||||
warnings.push_back(RTR("Particle sub-emitters are only available when using the Forward+ or Mobile rendering backends."));
|
||||
warnings.push_back(RTR("Particle sub-emitters are only available when using the Forward+ or Mobile renderers."));
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void GPUParticles3D::restart() {
|
||||
void GPUParticles3D::restart(bool p_keep_seed) {
|
||||
if (!p_keep_seed && !use_fixed_seed) {
|
||||
set_seed(Math::rand());
|
||||
}
|
||||
RenderingServer::get_singleton()->particles_restart(particles);
|
||||
RenderingServer::get_singleton()->particles_set_emitting(particles, true);
|
||||
|
||||
|
|
@ -414,6 +454,10 @@ AABB GPUParticles3D::capture_aabb() const {
|
|||
}
|
||||
|
||||
void GPUParticles3D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "emitting") {
|
||||
p_property.hint = one_shot ? PROPERTY_HINT_ONESHOT : PROPERTY_HINT_NONE;
|
||||
}
|
||||
|
||||
if (p_property.name.begins_with("draw_pass_")) {
|
||||
int index = p_property.name.get_slicec('_', 2).to_int() - 1;
|
||||
if (index >= draw_passes.size()) {
|
||||
|
|
@ -421,6 +465,13 @@ void GPUParticles3D::_validate_property(PropertyInfo &p_property) const {
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (p_property.name == "seed" && !use_fixed_seed) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void GPUParticles3D::request_particles_process(real_t p_requested_process_time) {
|
||||
RS::get_singleton()->particles_request_process_time(particles, p_requested_process_time);
|
||||
}
|
||||
|
||||
void GPUParticles3D::emit_particle(const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) {
|
||||
|
|
@ -487,8 +538,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,12 +563,15 @@ 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: {
|
||||
RS::get_singleton()->particles_set_subemitter(particles, RID());
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_SUSPENDED:
|
||||
case NOTIFICATION_UNSUSPENDED:
|
||||
case NOTIFICATION_PAUSED:
|
||||
case NOTIFICATION_UNPAUSED: {
|
||||
if (is_inside_tree()) {
|
||||
|
|
@ -615,6 +682,10 @@ void GPUParticles3D::convert_from_particles(Node *p_particles) {
|
|||
proc_mat->set_emission_shape(ParticleProcessMaterial::EmissionShape(cpu_particles->get_emission_shape()));
|
||||
proc_mat->set_emission_sphere_radius(cpu_particles->get_emission_sphere_radius());
|
||||
proc_mat->set_emission_box_extents(cpu_particles->get_emission_box_extents());
|
||||
proc_mat->set_emission_ring_height(cpu_particles->get_emission_ring_height());
|
||||
proc_mat->set_emission_ring_radius(cpu_particles->get_emission_ring_radius());
|
||||
proc_mat->set_emission_ring_inner_radius(cpu_particles->get_emission_ring_inner_radius());
|
||||
proc_mat->set_emission_ring_cone_angle(cpu_particles->get_emission_ring_cone_angle());
|
||||
|
||||
if (cpu_particles->get_split_scale()) {
|
||||
Ref<CurveXYZTexture> scale3D = memnew(CurveXYZTexture);
|
||||
|
|
@ -699,6 +770,12 @@ void GPUParticles3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("get_collision_base_size"), &GPUParticles3D::get_collision_base_size);
|
||||
ClassDB::bind_method(D_METHOD("get_interp_to_end"), &GPUParticles3D::get_interp_to_end);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_fixed_seed", "use_fixed_seed"), &GPUParticles3D::set_use_fixed_seed);
|
||||
ClassDB::bind_method(D_METHOD("get_use_fixed_seed"), &GPUParticles3D::get_use_fixed_seed);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_seed", "seed"), &GPUParticles3D::set_seed);
|
||||
ClassDB::bind_method(D_METHOD("get_seed"), &GPUParticles3D::get_seed);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &GPUParticles3D::set_draw_order);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_draw_order"), &GPUParticles3D::get_draw_order);
|
||||
|
|
@ -712,7 +789,7 @@ void GPUParticles3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_skin", "skin"), &GPUParticles3D::set_skin);
|
||||
ClassDB::bind_method(D_METHOD("get_skin"), &GPUParticles3D::get_skin);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("restart"), &GPUParticles3D::restart);
|
||||
ClassDB::bind_method(D_METHOD("restart", "keep_seed"), &GPUParticles3D::restart, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("capture_aabb"), &GPUParticles3D::capture_aabb);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_sub_emitter", "path"), &GPUParticles3D::set_sub_emitter);
|
||||
|
|
@ -734,24 +811,29 @@ void GPUParticles3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_amount_ratio", "ratio"), &GPUParticles3D::set_amount_ratio);
|
||||
ClassDB::bind_method(D_METHOD("get_amount_ratio"), &GPUParticles3D::get_amount_ratio);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("request_particles_process", "process_time"), &GPUParticles3D::request_particles_process);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("finished"));
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting", PROPERTY_HINT_ONESHOT), "set_emitting", "is_emitting");
|
||||
ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false.
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); // FIXME: Evaluate support for `exp` in integer properties, or remove this.
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amount_ratio", PROPERTY_HINT_RANGE, "0,1,0.0001"), "set_amount_ratio", "get_amount_ratio");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles3D"), "set_sub_emitter", "get_sub_emitter");
|
||||
ADD_GROUP("Time", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interp_to_end", PROPERTY_HINT_RANGE, "0.00,1.0,0.01"), "set_interp_to_end", "get_interp_to_end");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,exp,suffix:s"), "set_pre_process_time", "get_pre_process_time");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,10.0,0.01,or_greater,exp,suffix:s"), "set_pre_process_time", "get_pre_process_time");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_fixed_seed"), "set_use_fixed_seed", "get_use_fixed_seed");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "seed", PROPERTY_HINT_RANGE, "0," + itos(UINT32_MAX) + ",1"), "set_seed", "get_seed");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta");
|
||||
|
||||
ADD_GROUP("Collision", "collision_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,suffix:m"), "set_collision_base_size", "get_collision_base_size");
|
||||
ADD_GROUP("Drawing", "");
|
||||
|
|
@ -788,6 +870,8 @@ void GPUParticles3D::_bind_methods() {
|
|||
BIND_ENUM_CONSTANT(TRANSFORM_ALIGN_Z_BILLBOARD);
|
||||
BIND_ENUM_CONSTANT(TRANSFORM_ALIGN_Y_TO_VELOCITY);
|
||||
BIND_ENUM_CONSTANT(TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY);
|
||||
|
||||
ADD_PROPERTY_DEFAULT("seed", 0);
|
||||
}
|
||||
|
||||
GPUParticles3D::GPUParticles3D() {
|
||||
|
|
@ -797,6 +881,7 @@ GPUParticles3D::GPUParticles3D() {
|
|||
one_shot = false; // Needed so that set_emitting doesn't access uninitialized values
|
||||
set_emitting(true);
|
||||
set_one_shot(false);
|
||||
set_seed(Math::rand());
|
||||
set_amount_ratio(1.0);
|
||||
set_amount(8);
|
||||
set_lifetime(1);
|
||||
|
|
@ -814,6 +899,7 @@ GPUParticles3D::GPUParticles3D() {
|
|||
set_speed_scale(1);
|
||||
set_collision_base_size(collision_base_size);
|
||||
set_transform_align(TRANSFORM_ALIGN_DISABLED);
|
||||
set_use_fixed_seed(false);
|
||||
}
|
||||
|
||||
GPUParticles3D::~GPUParticles3D() {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ private:
|
|||
bool interpolate = true;
|
||||
NodePath sub_emitter;
|
||||
real_t collision_base_size = 0.01;
|
||||
uint32_t seed = 0;
|
||||
bool use_fixed_seed = false;
|
||||
|
||||
bool trail_enabled = false;
|
||||
double trail_lifetime = 0.3;
|
||||
|
|
@ -107,6 +109,11 @@ protected:
|
|||
void _notification(int p_what);
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void _restart_bind_compat_92089();
|
||||
static void _bind_compatibility_methods();
|
||||
#endif
|
||||
|
||||
public:
|
||||
AABB get_aabb() const override;
|
||||
|
||||
|
|
@ -175,7 +182,14 @@ public:
|
|||
void set_transform_align(TransformAlign p_align);
|
||||
TransformAlign get_transform_align() const;
|
||||
|
||||
void restart();
|
||||
void restart(bool p_keep_seed = false);
|
||||
|
||||
void set_use_fixed_seed(bool p_use_fixed_seed);
|
||||
bool get_use_fixed_seed() const;
|
||||
|
||||
void set_seed(uint32_t p_seed);
|
||||
uint32_t get_seed() const;
|
||||
void request_particles_process(real_t p_requested_process_time);
|
||||
|
||||
enum EmitFlags {
|
||||
EMIT_FLAG_POSITION = RS::PARTICLES_EMIT_FLAG_POSITION,
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ void GPUParticlesCollisionSDF3D::_find_meshes(const AABB &p_aabb, Node *p_at_nod
|
|||
for (int i = 0; i < meshes.size(); i += 2) {
|
||||
Transform3D mxf = meshes[i];
|
||||
Ref<Mesh> mesh = meshes[i + 1];
|
||||
if (!mesh.is_valid()) {
|
||||
if (mesh.is_null()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -524,7 +524,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() {
|
|||
}
|
||||
|
||||
PackedStringArray GPUParticlesCollisionSDF3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = GPUParticlesCollision3D::get_configuration_warnings();
|
||||
|
||||
if (bake_mask == 0) {
|
||||
warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any collision for this GPUParticlesCollisionSDF3D.\nTo resolve this, enable at least one bit in the Bake Mask property."));
|
||||
|
|
@ -721,6 +721,12 @@ void GPUParticlesCollisionHeightField3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_update_mode", "update_mode"), &GPUParticlesCollisionHeightField3D::set_update_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_update_mode"), &GPUParticlesCollisionHeightField3D::get_update_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_heightfield_mask", "heightfield_mask"), &GPUParticlesCollisionHeightField3D::set_heightfield_mask);
|
||||
ClassDB::bind_method(D_METHOD("get_heightfield_mask"), &GPUParticlesCollisionHeightField3D::get_heightfield_mask);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_heightfield_mask_value", "layer_number", "value"), &GPUParticlesCollisionHeightField3D::set_heightfield_mask_value);
|
||||
ClassDB::bind_method(D_METHOD("get_heightfield_mask_value", "layer_number"), &GPUParticlesCollisionHeightField3D::get_heightfield_mask_value);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_follow_camera_enabled", "enabled"), &GPUParticlesCollisionHeightField3D::set_follow_camera_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_follow_camera_enabled"), &GPUParticlesCollisionHeightField3D::is_follow_camera_enabled);
|
||||
|
||||
|
|
@ -728,6 +734,7 @@ void GPUParticlesCollisionHeightField3D::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "256 (Fastest),512 (Fast),1024 (Average),2048 (Slow),4096 (Slower),8192 (Slowest)"), "set_resolution", "get_resolution");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "When Moved (Fast),Always (Slow)"), "set_update_mode", "get_update_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_camera_enabled"), "set_follow_camera_enabled", "is_follow_camera_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "heightfield_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_heightfield_mask", "get_heightfield_mask");
|
||||
|
||||
BIND_ENUM_CONSTANT(RESOLUTION_256);
|
||||
BIND_ENUM_CONSTANT(RESOLUTION_512);
|
||||
|
|
@ -790,6 +797,33 @@ GPUParticlesCollisionHeightField3D::UpdateMode GPUParticlesCollisionHeightField3
|
|||
return update_mode;
|
||||
}
|
||||
|
||||
void GPUParticlesCollisionHeightField3D::set_heightfield_mask(uint32_t p_heightfield_mask) {
|
||||
heightfield_mask = p_heightfield_mask;
|
||||
RS::get_singleton()->particles_collision_set_height_field_mask(_get_collision(), p_heightfield_mask);
|
||||
}
|
||||
|
||||
uint32_t GPUParticlesCollisionHeightField3D::get_heightfield_mask() const {
|
||||
return heightfield_mask;
|
||||
}
|
||||
|
||||
void GPUParticlesCollisionHeightField3D::set_heightfield_mask_value(int p_layer_number, bool p_value) {
|
||||
ERR_FAIL_COND_MSG(p_layer_number < 1, "Render layer number must be between 1 and 20 inclusive.");
|
||||
ERR_FAIL_COND_MSG(p_layer_number > 20, "Render layer number must be between 1 and 20 inclusive.");
|
||||
uint32_t mask = get_heightfield_mask();
|
||||
if (p_value) {
|
||||
mask |= 1 << (p_layer_number - 1);
|
||||
} else {
|
||||
mask &= ~(1 << (p_layer_number - 1));
|
||||
}
|
||||
set_heightfield_mask(mask);
|
||||
}
|
||||
|
||||
bool GPUParticlesCollisionHeightField3D::get_heightfield_mask_value(int p_layer_number) const {
|
||||
ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Render layer number must be between 1 and 20 inclusive.");
|
||||
ERR_FAIL_COND_V_MSG(p_layer_number > 20, false, "Render layer number must be between 1 and 20 inclusive.");
|
||||
return heightfield_mask & (1 << (p_layer_number - 1));
|
||||
}
|
||||
|
||||
void GPUParticlesCollisionHeightField3D::set_follow_camera_enabled(bool p_enabled) {
|
||||
follow_camera_mode = p_enabled;
|
||||
set_process_internal(follow_camera_mode || update_mode == UPDATE_MODE_ALWAYS);
|
||||
|
|
|
|||
|
|
@ -225,6 +225,7 @@ public:
|
|||
};
|
||||
|
||||
private:
|
||||
uint32_t heightfield_mask = (1 << 20) - 1; // Only the first 20 bits are set by default to ignore editor layers.
|
||||
Vector3 size = Vector3(2, 2, 2);
|
||||
Resolution resolution = RESOLUTION_1024;
|
||||
bool follow_camera_mode = false;
|
||||
|
|
@ -249,6 +250,12 @@ public:
|
|||
void set_update_mode(UpdateMode p_update_mode);
|
||||
UpdateMode get_update_mode() const;
|
||||
|
||||
void set_heightfield_mask(uint32_t p_heightfield_mask);
|
||||
uint32_t get_heightfield_mask() const;
|
||||
|
||||
void set_heightfield_mask_value(int p_layer_number, bool p_value);
|
||||
bool get_heightfield_mask_value(int p_layer_number) const;
|
||||
|
||||
void set_follow_camera_enabled(bool p_enabled);
|
||||
bool is_follow_camera_enabled() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@
|
|||
#include "scene/3d/node_3d.h"
|
||||
#include "scene/3d/visual_instance_3d.h"
|
||||
#include "scene/resources/3d/skin.h"
|
||||
#include "scene/resources/immediate_mesh.h"
|
||||
|
||||
class ImporterMesh;
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
#include "label_3d.h"
|
||||
|
||||
#include "scene/main/viewport.h"
|
||||
#include "scene/main/window.h"
|
||||
#include "scene/resources/theme.h"
|
||||
#include "scene/theme/theme_db.h"
|
||||
|
||||
|
|
@ -197,14 +197,14 @@ void Label3D::_notification(int p_what) {
|
|||
if (!pending_update) {
|
||||
_im_update();
|
||||
}
|
||||
Viewport *viewport = get_viewport();
|
||||
ERR_FAIL_NULL(viewport);
|
||||
viewport->connect("size_changed", callable_mp(this, &Label3D::_font_changed));
|
||||
Window *window = get_window();
|
||||
ERR_FAIL_NULL(window);
|
||||
window->connect("size_changed", callable_mp(this, &Label3D::_font_changed));
|
||||
} break;
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
Viewport *viewport = get_viewport();
|
||||
ERR_FAIL_NULL(viewport);
|
||||
viewport->disconnect("size_changed", callable_mp(this, &Label3D::_font_changed));
|
||||
Window *window = get_window();
|
||||
ERR_FAIL_NULL(window);
|
||||
window->disconnect("size_changed", callable_mp(this, &Label3D::_font_changed));
|
||||
} break;
|
||||
case NOTIFICATION_TRANSLATION_CHANGED: {
|
||||
String new_text = atr(text);
|
||||
|
|
@ -318,7 +318,7 @@ Ref<TriangleMesh> Label3D::generate_triangle_mesh() const {
|
|||
facesw[j] = vtx;
|
||||
}
|
||||
|
||||
triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh));
|
||||
triangle_mesh.instantiate();
|
||||
triangle_mesh->create(faces);
|
||||
|
||||
return triangle_mesh;
|
||||
|
|
@ -344,7 +344,7 @@ void Label3D::_generate_glyph_surfaces(const Glyph &p_glyph, Vector2 &r_offset,
|
|||
gl_uv = TS->font_get_glyph_uv_rect(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index);
|
||||
texs = TS->font_get_glyph_texture_size(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index);
|
||||
}
|
||||
} else {
|
||||
} else if (((p_glyph.flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((p_glyph.flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
|
||||
gl_sz = TS->get_hex_code_box_size(p_glyph.font_size, p_glyph.index) * pixel_size;
|
||||
gl_of = Vector2(0, -gl_sz.y);
|
||||
}
|
||||
|
|
@ -392,6 +392,7 @@ void Label3D::_generate_glyph_surfaces(const Glyph &p_glyph, Vector2 &r_offset,
|
|||
|
||||
RS::get_singleton()->material_set_shader(surf.material, shader_rid);
|
||||
RS::get_singleton()->material_set_param(surf.material, "texture_albedo", tex);
|
||||
RS::get_singleton()->material_set_param(surf.material, "albedo_texture_size", texs);
|
||||
if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) {
|
||||
RS::get_singleton()->material_set_render_priority(surf.material, p_priority);
|
||||
} else {
|
||||
|
|
@ -797,13 +798,13 @@ Ref<Font> Label3D::_get_font_or_default() const {
|
|||
}
|
||||
|
||||
const StringName theme_name = SceneStringName(font);
|
||||
List<StringName> theme_types;
|
||||
ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types);
|
||||
Vector<StringName> theme_types;
|
||||
ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), theme_types);
|
||||
|
||||
ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context();
|
||||
List<Ref<Theme>> themes = global_context->get_themes();
|
||||
Vector<Ref<Theme>> themes = global_context->get_themes();
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
themes.push_front(ThemeDB::get_singleton()->get_project_theme());
|
||||
themes.insert(0, ThemeDB::get_singleton()->get_project_theme());
|
||||
}
|
||||
|
||||
for (const Ref<Theme> &theme : themes) {
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ public:
|
|||
StandardMaterial3D::TextureFilter get_texture_filter() const;
|
||||
|
||||
virtual AABB get_aabb() const override;
|
||||
Ref<TriangleMesh> generate_triangle_mesh() const;
|
||||
virtual Ref<TriangleMesh> generate_triangle_mesh() const override;
|
||||
|
||||
Label3D();
|
||||
~Label3D();
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
|
||||
#include "light_3d.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
|
||||
void Light3D::set_param(Param p_param, real_t p_value) {
|
||||
ERR_FAIL_INDEX(p_param, PARAM_MAX);
|
||||
param[p_param] = p_value;
|
||||
|
|
@ -146,6 +146,15 @@ bool Light3D::get_shadow_reverse_cull_face() const {
|
|||
return reverse_cull;
|
||||
}
|
||||
|
||||
void Light3D::set_shadow_caster_mask(uint32_t p_caster_mask) {
|
||||
shadow_caster_mask = p_caster_mask;
|
||||
RS::get_singleton()->light_set_shadow_caster_mask(light, shadow_caster_mask);
|
||||
}
|
||||
|
||||
uint32_t Light3D::get_shadow_caster_mask() const {
|
||||
return shadow_caster_mask;
|
||||
}
|
||||
|
||||
AABB Light3D::get_aabb() const {
|
||||
if (type == RenderingServer::LIGHT_DIRECTIONAL) {
|
||||
return AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2));
|
||||
|
|
@ -191,6 +200,20 @@ Light3D::BakeMode Light3D::get_bake_mode() const {
|
|||
void Light3D::set_projector(const Ref<Texture2D> &p_texture) {
|
||||
projector = p_texture;
|
||||
RID tex_id = projector.is_valid() ? projector->get_rid() : RID();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (p_texture.is_valid() &&
|
||||
(p_texture->is_class("AnimatedTexture") ||
|
||||
p_texture->is_class("AtlasTexture") ||
|
||||
p_texture->is_class("CameraTexture") ||
|
||||
p_texture->is_class("CanvasTexture") ||
|
||||
p_texture->is_class("MeshTexture") ||
|
||||
p_texture->is_class("Texture2DRD") ||
|
||||
p_texture->is_class("ViewportTexture"))) {
|
||||
WARN_PRINT(vformat("%s cannot be used as a Light3D projector texture (%s). As a workaround, assign the value returned by %s's `get_image()` instead.", p_texture->get_class(), get_path(), p_texture->get_class()));
|
||||
}
|
||||
#endif
|
||||
|
||||
RS::get_singleton()->light_set_projector(light, tex_id);
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
|
@ -300,7 +323,7 @@ bool Light3D::is_editor_only() const {
|
|||
}
|
||||
|
||||
void Light3D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (!shadow && (p_property.name == "shadow_bias" || p_property.name == "shadow_normal_bias" || p_property.name == "shadow_reverse_cull_face" || p_property.name == "shadow_transmittance_bias" || p_property.name == "shadow_opacity" || p_property.name == "shadow_blur" || p_property.name == "distance_fade_shadow")) {
|
||||
if (!shadow && (p_property.name == "shadow_bias" || p_property.name == "shadow_normal_bias" || p_property.name == "shadow_reverse_cull_face" || p_property.name == "shadow_transmittance_bias" || p_property.name == "shadow_opacity" || p_property.name == "shadow_blur" || p_property.name == "distance_fade_shadow" || p_property.name == "shadow_caster_mask")) {
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
|
||||
|
|
@ -354,6 +377,9 @@ void Light3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_shadow_reverse_cull_face", "enable"), &Light3D::set_shadow_reverse_cull_face);
|
||||
ClassDB::bind_method(D_METHOD("get_shadow_reverse_cull_face"), &Light3D::get_shadow_reverse_cull_face);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_shadow_caster_mask", "caster_mask"), &Light3D::set_shadow_caster_mask);
|
||||
ClassDB::bind_method(D_METHOD("get_shadow_caster_mask"), &Light3D::get_shadow_caster_mask);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_bake_mode", "bake_mode"), &Light3D::set_bake_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_bake_mode"), &Light3D::get_bake_mode);
|
||||
|
||||
|
|
@ -372,7 +398,8 @@ void Light3D::_bind_methods() {
|
|||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_energy", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_param", "get_param", PARAM_ENERGY);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_indirect_energy", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_param", "get_param", PARAM_INDIRECT_ENERGY);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_volumetric_fog_energy", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_param", "get_param", PARAM_VOLUMETRIC_FOG_ENERGY);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_projector", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_projector", "get_projector");
|
||||
// Only allow texture types that display correctly.
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_projector", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D,-AnimatedTexture,-AtlasTexture,-CameraTexture,-CanvasTexture,-MeshTexture,-Texture2DRD,-ViewportTexture"), "set_projector", "get_projector");
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_size", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_param", "get_param", PARAM_SIZE);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_angular_distance", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_param", "get_param", PARAM_SIZE);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "light_negative"), "set_negative", "is_negative");
|
||||
|
|
@ -388,6 +415,7 @@ void Light3D::_bind_methods() {
|
|||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_transmittance_bias", PROPERTY_HINT_RANGE, "-16,16,0.001"), "set_param", "get_param", PARAM_TRANSMITTANCE_BIAS);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_opacity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_OPACITY);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_blur", PROPERTY_HINT_RANGE, "0,10,0.001"), "set_param", "get_param", PARAM_SHADOW_BLUR);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_caster_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_shadow_caster_mask", "get_shadow_caster_mask");
|
||||
|
||||
ADD_GROUP("Distance Fade", "distance_fade_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distance_fade_enabled"), "set_enable_distance_fade", "is_distance_fade_enabled");
|
||||
|
|
@ -532,7 +560,7 @@ void DirectionalLight3D::_validate_property(PropertyInfo &p_property) const {
|
|||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
|
||||
if (p_property.name == "light_size" || p_property.name == "light_projector" || p_property.name == "light_specular") {
|
||||
if (p_property.name == "light_size" || p_property.name == "light_projector") {
|
||||
// Not implemented in DirectionalLight3D (`light_size` is replaced by `light_angular_distance`).
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
|
|
@ -582,6 +610,7 @@ DirectionalLight3D::DirectionalLight3D() :
|
|||
// Increase the default shadow normal bias to better suit most scenes.
|
||||
set_param(PARAM_SHADOW_NORMAL_BIAS, 2.0);
|
||||
set_param(PARAM_INTENSITY, 100000.0); // Specified in Lux, approximate mid-day sun.
|
||||
set_param(PARAM_SPECULAR, 1.0);
|
||||
set_shadow_mode(SHADOW_PARALLEL_4_SPLITS);
|
||||
blend_splits = false;
|
||||
set_sky_mode(SKY_MODE_LIGHT_AND_SKY);
|
||||
|
|
@ -604,7 +633,7 @@ PackedStringArray OmniLight3D::get_configuration_warnings() const {
|
|||
}
|
||||
|
||||
if (get_projector().is_valid() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
|
||||
warnings.push_back(RTR("Projector textures are not supported when using the GL Compatibility backend yet. Support will be added in a future release."));
|
||||
warnings.push_back(RTR("Projector textures are not supported when using the Compatibility renderer yet. Support will be added in a future release."));
|
||||
}
|
||||
|
||||
return warnings;
|
||||
|
|
@ -640,7 +669,7 @@ PackedStringArray SpotLight3D::get_configuration_warnings() const {
|
|||
}
|
||||
|
||||
if (get_projector().is_valid() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
|
||||
warnings.push_back(RTR("Projector textures are not supported when using the GL Compatibility backend yet. Support will be added in a future release."));
|
||||
warnings.push_back(RTR("Projector textures are not supported when using the Compatibility renderer yet. Support will be added in a future release."));
|
||||
}
|
||||
|
||||
return warnings;
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ private:
|
|||
bool negative = false;
|
||||
bool reverse_cull = false;
|
||||
uint32_t cull_mask = 0;
|
||||
uint32_t shadow_caster_mask = 0xFFFFFFFF;
|
||||
bool distance_fade_enabled = false;
|
||||
real_t distance_fade_begin = 40.0;
|
||||
real_t distance_fade_shadow = 50.0;
|
||||
|
|
@ -136,6 +137,9 @@ public:
|
|||
void set_shadow_reverse_cull_face(bool p_enable);
|
||||
bool get_shadow_reverse_cull_face() const;
|
||||
|
||||
void set_shadow_caster_mask(uint32_t p_caster_mask);
|
||||
uint32_t get_shadow_caster_mask() const;
|
||||
|
||||
void set_bake_mode(BakeMode p_mode);
|
||||
BakeMode get_bake_mode() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,13 +33,16 @@
|
|||
#include "core/config/project_settings.h"
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/math/delaunay_3d.h"
|
||||
#include "lightmap_probe.h"
|
||||
#include "core/object/object.h"
|
||||
#include "scene/3d/lightmap_probe.h"
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/resources/camera_attributes.h"
|
||||
#include "scene/resources/environment.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
#include "scene/resources/sky.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h" // For lightmapper_rd.
|
||||
|
||||
void LightmapGIData::add_user(const NodePath &p_path, const Rect2 &p_uv_scale, int p_slice_index, int32_t p_sub_instance) {
|
||||
User user;
|
||||
user.path = p_path;
|
||||
|
|
@ -98,15 +101,15 @@ Array LightmapGIData::_get_user_data() const {
|
|||
}
|
||||
|
||||
void LightmapGIData::set_lightmap_textures(const TypedArray<TextureLayered> &p_data) {
|
||||
light_textures = p_data;
|
||||
storage_light_textures = p_data;
|
||||
if (p_data.is_empty()) {
|
||||
light_texture = Ref<TextureLayered>();
|
||||
combined_light_texture = Ref<TextureLayered>();
|
||||
_reset_lightmap_textures();
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_data.size() == 1) {
|
||||
light_texture = p_data[0];
|
||||
combined_light_texture = p_data[0];
|
||||
} else {
|
||||
Vector<Ref<Image>> images;
|
||||
for (int i = 0; i < p_data.size(); i++) {
|
||||
|
|
@ -121,13 +124,59 @@ void LightmapGIData::set_lightmap_textures(const TypedArray<TextureLayered> &p_d
|
|||
combined_texture.instantiate();
|
||||
|
||||
combined_texture->create_from_images(images);
|
||||
light_texture = combined_texture;
|
||||
combined_light_texture = combined_texture;
|
||||
}
|
||||
_reset_lightmap_textures();
|
||||
}
|
||||
|
||||
TypedArray<TextureLayered> LightmapGIData::get_lightmap_textures() const {
|
||||
return light_textures;
|
||||
return storage_light_textures;
|
||||
}
|
||||
|
||||
void LightmapGIData::set_shadowmask_textures(const TypedArray<TextureLayered> &p_data) {
|
||||
storage_shadowmask_textures = p_data;
|
||||
|
||||
if (p_data.is_empty()) {
|
||||
combined_shadowmask_texture = Ref<TextureLayered>();
|
||||
_reset_shadowmask_textures();
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_data.size() == 1) {
|
||||
combined_shadowmask_texture = p_data[0];
|
||||
|
||||
} else {
|
||||
Vector<Ref<Image>> images;
|
||||
for (int i = 0; i < p_data.size(); i++) {
|
||||
Ref<TextureLayered> texture = p_data[i];
|
||||
ERR_FAIL_COND_MSG(texture.is_null(), vformat("Invalid TextureLayered at index %d.", i));
|
||||
for (int j = 0; j < texture->get_layers(); j++) {
|
||||
images.push_back(texture->get_layer_data(j));
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Texture2DArray> combined_texture;
|
||||
combined_texture.instantiate();
|
||||
|
||||
combined_texture->create_from_images(images);
|
||||
combined_shadowmask_texture = combined_texture;
|
||||
}
|
||||
|
||||
_reset_shadowmask_textures();
|
||||
}
|
||||
|
||||
TypedArray<TextureLayered> LightmapGIData::get_shadowmask_textures() const {
|
||||
return storage_shadowmask_textures;
|
||||
}
|
||||
|
||||
void LightmapGIData::clear_shadowmask_textures() {
|
||||
RS::get_singleton()->lightmap_set_shadowmask_textures(lightmap, RID());
|
||||
storage_shadowmask_textures.clear();
|
||||
combined_shadowmask_texture.unref();
|
||||
}
|
||||
|
||||
bool LightmapGIData::has_shadowmask_textures() {
|
||||
return !storage_shadowmask_textures.is_empty() && combined_shadowmask_texture.is_valid();
|
||||
}
|
||||
|
||||
RID LightmapGIData::get_rid() const {
|
||||
|
|
@ -139,7 +188,11 @@ void LightmapGIData::clear() {
|
|||
}
|
||||
|
||||
void LightmapGIData::_reset_lightmap_textures() {
|
||||
RS::get_singleton()->lightmap_set_textures(lightmap, light_texture.is_valid() ? light_texture->get_rid() : RID(), uses_spherical_harmonics);
|
||||
RS::get_singleton()->lightmap_set_textures(lightmap, combined_light_texture.is_valid() ? combined_light_texture->get_rid() : RID(), uses_spherical_harmonics);
|
||||
}
|
||||
|
||||
void LightmapGIData::_reset_shadowmask_textures() {
|
||||
RS::get_singleton()->lightmap_set_shadowmask_textures(lightmap, combined_shadowmask_texture.is_valid() ? combined_shadowmask_texture->get_rid() : RID());
|
||||
}
|
||||
|
||||
void LightmapGIData::set_uses_spherical_harmonics(bool p_enable) {
|
||||
|
|
@ -151,6 +204,22 @@ bool LightmapGIData::is_using_spherical_harmonics() const {
|
|||
return uses_spherical_harmonics;
|
||||
}
|
||||
|
||||
void LightmapGIData::_set_uses_packed_directional(bool p_enable) {
|
||||
_uses_packed_directional = p_enable;
|
||||
}
|
||||
|
||||
bool LightmapGIData::_is_using_packed_directional() const {
|
||||
return _uses_packed_directional;
|
||||
}
|
||||
|
||||
void LightmapGIData::update_shadowmask_mode(ShadowmaskMode p_mode) {
|
||||
RS::get_singleton()->lightmap_set_shadowmask_mode(lightmap, (RS::ShadowmaskMode)p_mode);
|
||||
}
|
||||
|
||||
LightmapGIData::ShadowmaskMode LightmapGIData::get_shadowmask_mode() const {
|
||||
return (ShadowmaskMode)RS::get_singleton()->lightmap_get_shadowmask_mode(lightmap);
|
||||
}
|
||||
|
||||
void LightmapGIData::set_capture_data(const AABB &p_bounds, bool p_interior, const PackedVector3Array &p_points, const PackedColorArray &p_point_sh, const PackedInt32Array &p_tetrahedra, const PackedInt32Array &p_bsp_tree, float p_baked_exposure) {
|
||||
if (p_points.size()) {
|
||||
int pc = p_points.size();
|
||||
|
|
@ -230,10 +299,10 @@ void LightmapGIData::set_light_texture(const Ref<TextureLayered> &p_light_textur
|
|||
}
|
||||
|
||||
Ref<TextureLayered> LightmapGIData::get_light_texture() const {
|
||||
if (light_textures.is_empty()) {
|
||||
if (storage_light_textures.is_empty()) {
|
||||
return Ref<TextureLayered>();
|
||||
}
|
||||
return light_textures.get(0);
|
||||
return storage_light_textures.get(0);
|
||||
}
|
||||
|
||||
void LightmapGIData::_set_light_textures_data(const Array &p_data) {
|
||||
|
|
@ -241,7 +310,7 @@ void LightmapGIData::_set_light_textures_data(const Array &p_data) {
|
|||
}
|
||||
|
||||
Array LightmapGIData::_get_light_textures_data() const {
|
||||
return Array(light_textures);
|
||||
return Array(storage_light_textures);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -252,9 +321,15 @@ void LightmapGIData::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_lightmap_textures", "light_textures"), &LightmapGIData::set_lightmap_textures);
|
||||
ClassDB::bind_method(D_METHOD("get_lightmap_textures"), &LightmapGIData::get_lightmap_textures);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_shadowmask_textures", "shadowmask_textures"), &LightmapGIData::set_shadowmask_textures);
|
||||
ClassDB::bind_method(D_METHOD("get_shadowmask_textures"), &LightmapGIData::get_shadowmask_textures);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics);
|
||||
ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_set_uses_packed_directional", "_uses_packed_directional"), &LightmapGIData::_set_uses_packed_directional);
|
||||
ClassDB::bind_method(D_METHOD("_is_using_packed_directional"), &LightmapGIData::_is_using_packed_directional);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("add_user", "path", "uv_scale", "slice_index", "sub_instance"), &LightmapGIData::add_user);
|
||||
ClassDB::bind_method(D_METHOD("get_user_count"), &LightmapGIData::get_user_count);
|
||||
ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &LightmapGIData::get_user_path);
|
||||
|
|
@ -263,10 +338,12 @@ void LightmapGIData::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("_set_probe_data", "data"), &LightmapGIData::_set_probe_data);
|
||||
ClassDB::bind_method(D_METHOD("_get_probe_data"), &LightmapGIData::_get_probe_data);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lightmap_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_NO_EDITOR), "set_lightmap_textures", "get_lightmap_textures");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lightmap_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), "set_lightmap_textures", "get_lightmap_textures");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "shadowmask_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), "set_shadowmask_textures", "get_shadowmask_textures");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "_uses_packed_directional", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_uses_packed_directional", "_is_using_packed_directional");
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ClassDB::bind_method(D_METHOD("set_light_texture", "light_texture"), &LightmapGIData::set_light_texture);
|
||||
|
|
@ -275,9 +352,13 @@ void LightmapGIData::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("_set_light_textures_data", "data"), &LightmapGIData::_set_light_textures_data);
|
||||
ClassDB::bind_method(D_METHOD("_get_light_textures_data"), &LightmapGIData::_get_light_textures_data);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_EDITOR), "set_light_texture", "get_light_texture");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_NONE), "set_light_texture", "get_light_texture");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data");
|
||||
#endif
|
||||
|
||||
BIND_ENUM_CONSTANT(SHADOWMASK_MODE_NONE);
|
||||
BIND_ENUM_CONSTANT(SHADOWMASK_MODE_REPLACE);
|
||||
BIND_ENUM_CONSTANT(SHADOWMASK_MODE_OVERLAY);
|
||||
}
|
||||
|
||||
LightmapGIData::LightmapGIData() {
|
||||
|
|
@ -321,9 +402,7 @@ void LightmapGI::_find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &m
|
|||
mf.node_path = get_path_to(mi);
|
||||
mf.subindex = -1;
|
||||
mf.mesh = mesh;
|
||||
|
||||
static const int lightmap_scale[GeometryInstance3D::LIGHTMAP_SCALE_MAX] = { 1, 2, 4, 8 };
|
||||
mf.lightmap_scale = lightmap_scale[mi->get_lightmap_scale()];
|
||||
mf.lightmap_scale = mi->get_lightmap_texel_scale();
|
||||
|
||||
Ref<Material> all_override = mi->get_material_override();
|
||||
for (int i = 0; i < mesh->get_surface_count(); i++) {
|
||||
|
|
@ -347,7 +426,7 @@ void LightmapGI::_find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &m
|
|||
Transform3D xf = get_global_transform().affine_inverse() * s->get_global_transform();
|
||||
for (int i = 0; i < bmeshes.size(); i += 2) {
|
||||
Ref<Mesh> mesh = bmeshes[i];
|
||||
if (!mesh.is_valid()) {
|
||||
if (mesh.is_null()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -357,7 +436,7 @@ void LightmapGI::_find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &m
|
|||
mf.xform = xf * mesh_xf;
|
||||
mf.node_path = get_path_to(s);
|
||||
mf.subindex = i / 2;
|
||||
mf.lightmap_scale = 1;
|
||||
mf.lightmap_scale = 1.0;
|
||||
mf.mesh = mesh;
|
||||
|
||||
meshes.push_back(mf);
|
||||
|
|
@ -709,7 +788,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;
|
||||
}
|
||||
|
|
@ -728,6 +807,83 @@ void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, f
|
|||
}
|
||||
}
|
||||
|
||||
LightmapGI::BakeError LightmapGI::_save_and_reimport_atlas_textures(const Ref<Lightmapper> p_lightmapper, const String &p_base_name, TypedArray<TextureLayered> &r_textures, bool p_is_shadowmask) const {
|
||||
Vector<Ref<Image>> images;
|
||||
images.resize(p_is_shadowmask ? p_lightmapper->get_shadowmask_texture_count() : p_lightmapper->get_bake_texture_count());
|
||||
|
||||
for (int i = 0; i < images.size(); i++) {
|
||||
images.set(i, p_is_shadowmask ? p_lightmapper->get_shadowmask_texture(i) : p_lightmapper->get_bake_texture(i));
|
||||
}
|
||||
|
||||
const int slice_count = images.size();
|
||||
const int slice_width = images[0]->get_width();
|
||||
const int slice_height = images[0]->get_height();
|
||||
|
||||
const int slices_per_texture = Image::MAX_HEIGHT / slice_height;
|
||||
const int texture_count = Math::ceil(slice_count / (float)slices_per_texture);
|
||||
const int last_count = slice_count % slices_per_texture;
|
||||
|
||||
r_textures.resize(texture_count);
|
||||
|
||||
for (int i = 0; i < texture_count; i++) {
|
||||
const int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture;
|
||||
|
||||
Ref<Image> texture_image = Image::create_empty(slice_width, slice_height * texture_slice_count, false, images[0]->get_format());
|
||||
|
||||
for (int j = 0; j < texture_slice_count; j++) {
|
||||
texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j));
|
||||
}
|
||||
|
||||
const String atlas_path = (texture_count > 1 ? p_base_name + "_" + itos(i) : p_base_name) + (p_is_shadowmask ? ".png" : ".exr");
|
||||
const String config_path = atlas_path + ".import";
|
||||
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
|
||||
// Load an import configuration if present.
|
||||
if (FileAccess::exists(config_path)) {
|
||||
config->load(config_path);
|
||||
}
|
||||
|
||||
config->set_value("remap", "importer", "2d_array_texture");
|
||||
config->set_value("remap", "type", "CompressedTexture2DArray");
|
||||
if (!config->has_section_key("params", "compress/mode")) {
|
||||
// Do not override an existing compression mode.
|
||||
config->set_value("params", "compress/mode", 2);
|
||||
}
|
||||
config->set_value("params", "compress/channel_pack", 1);
|
||||
config->set_value("params", "mipmaps/generate", false);
|
||||
config->set_value("params", "slices/horizontal", 1);
|
||||
config->set_value("params", "slices/vertical", texture_slice_count);
|
||||
|
||||
config->save(config_path);
|
||||
|
||||
if (supersampling_enabled) {
|
||||
texture_image->resize(texture_image->get_width() / supersampling_factor, texture_image->get_height() / supersampling_factor, Image::INTERPOLATE_TRILINEAR);
|
||||
}
|
||||
|
||||
// Save the file.
|
||||
Error save_err;
|
||||
if (p_is_shadowmask) {
|
||||
save_err = texture_image->save_png(atlas_path);
|
||||
} else {
|
||||
save_err = texture_image->save_exr(atlas_path, false);
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V(save_err, LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
|
||||
|
||||
// Reimport the file.
|
||||
ResourceLoader::import(atlas_path);
|
||||
Ref<TextureLayered> t = ResourceLoader::load(atlas_path); // If already loaded, it will be updated on refocus?
|
||||
ERR_FAIL_COND_V(t.is_null(), LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
|
||||
|
||||
// Store the atlas in the array.
|
||||
r_textures[i] = t;
|
||||
}
|
||||
|
||||
return LightmapGI::BAKE_ERROR_OK;
|
||||
}
|
||||
|
||||
LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_path, Lightmapper::BakeStepFunc p_bake_step, void *p_bake_userdata) {
|
||||
if (p_image_data_path.is_empty()) {
|
||||
if (get_light_data().is_null()) {
|
||||
|
|
@ -781,7 +937,8 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
// For now set to basic size to avoid crash.
|
||||
mesh_lightmap_size = Size2i(64, 64);
|
||||
}
|
||||
Size2i lightmap_size = Size2i(Size2(mesh_lightmap_size) * mf.lightmap_scale * texel_scale);
|
||||
// Double lightmap texel density if downsampling is enabled, as the final texture size will be halved before saving lightmaps.
|
||||
Size2i lightmap_size = Size2i(Size2(mesh_lightmap_size) * mf.lightmap_scale * texel_scale) * (supersampling_enabled ? supersampling_factor : 1.0);
|
||||
ERR_FAIL_COND_V(lightmap_size.x == 0 || lightmap_size.y == 0, BAKE_ERROR_LIGHTMAP_TOO_SMALL);
|
||||
|
||||
TypedArray<RID> overrides;
|
||||
|
|
@ -832,7 +989,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
w_albedo[i + 0] = uint8_t(CLAMP(float(r_aa[i + 0]) * (1.0 - float(r_orm[i + 2] / 255.0)), 0, 255));
|
||||
w_albedo[i + 1] = uint8_t(CLAMP(float(r_aa[i + 1]) * (1.0 - float(r_orm[i + 2] / 255.0)), 0, 255));
|
||||
w_albedo[i + 2] = uint8_t(CLAMP(float(r_aa[i + 2]) * (1.0 - float(r_orm[i + 2] / 255.0)), 0, 255));
|
||||
w_albedo[i + 3] = 255;
|
||||
w_albedo[i + 3] = r_aa[i + 3];
|
||||
}
|
||||
|
||||
md.albedo_on_uv2.instantiate();
|
||||
|
|
@ -853,6 +1010,11 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
continue;
|
||||
}
|
||||
Array a = mf.mesh->surface_get_arrays(i);
|
||||
Ref<Material> mat = mf.mesh->surface_get_material(i);
|
||||
RID mat_rid;
|
||||
if (mat.is_valid()) {
|
||||
mat_rid = mat->get_rid();
|
||||
}
|
||||
|
||||
Vector<Vector3> vertices = a[Mesh::ARRAY_VERTEX];
|
||||
const Vector3 *vr = vertices.ptr();
|
||||
|
|
@ -902,6 +1064,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
|
||||
md.uv2.push_back(uvr[vidx[k]]);
|
||||
md.normal.push_back(normal_xform.xform(nr[vidx[k]]).normalized());
|
||||
md.material.push_back(mat_rid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1026,20 +1189,20 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
if (Object::cast_to<DirectionalLight3D>(light)) {
|
||||
DirectionalLight3D *l = Object::cast_to<DirectionalLight3D>(light);
|
||||
if (l->get_sky_mode() != DirectionalLight3D::SKY_MODE_SKY_ONLY) {
|
||||
lightmapper->add_directional_light(light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
lightmapper->add_directional_light(light->get_name(), light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
}
|
||||
} else if (Object::cast_to<OmniLight3D>(light)) {
|
||||
OmniLight3D *l = Object::cast_to<OmniLight3D>(light);
|
||||
if (use_physical_light_units) {
|
||||
energy *= (1.0 / (Math_PI * 4.0));
|
||||
}
|
||||
lightmapper->add_omni_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
lightmapper->add_omni_light(light->get_name(), light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
} else if (Object::cast_to<SpotLight3D>(light)) {
|
||||
SpotLight3D *l = Object::cast_to<SpotLight3D>(light);
|
||||
if (use_physical_light_units) {
|
||||
energy *= (1.0 / Math_PI);
|
||||
}
|
||||
lightmapper->add_spot_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
lightmapper->add_spot_light(light->get_name(), light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < probes_found.size(); i++) {
|
||||
|
|
@ -1072,6 +1235,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;
|
||||
|
|
@ -1102,7 +1266,9 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
}
|
||||
}
|
||||
|
||||
Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, denoiser_range, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization);
|
||||
Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, denoiser_range, bounces,
|
||||
bounce_indirect_energy, bias, max_texture_size, directional, shadowmask_mode != LightmapGIData::SHADOWMASK_MODE_NONE, use_texture_for_bounces,
|
||||
Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization, (supersampling_enabled ? supersampling_factor : 1));
|
||||
|
||||
if (bake_err == Lightmapper::BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE) {
|
||||
return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL;
|
||||
|
|
@ -1110,83 +1276,50 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
return BAKE_ERROR_MESHES_INVALID;
|
||||
} else if (bake_err == Lightmapper::BAKE_ERROR_ATLAS_TOO_SMALL) {
|
||||
return BAKE_ERROR_ATLAS_TOO_SMALL;
|
||||
} else if (bake_err == Lightmapper::BAKE_ERROR_USER_ABORTED) {
|
||||
return BAKE_ERROR_USER_ABORTED;
|
||||
}
|
||||
|
||||
// POSTBAKE: Save Textures.
|
||||
TypedArray<TextureLayered> lightmap_textures;
|
||||
TypedArray<TextureLayered> shadowmask_textures;
|
||||
|
||||
TypedArray<TextureLayered> textures;
|
||||
{
|
||||
Vector<Ref<Image>> images;
|
||||
images.resize(lightmapper->get_bake_texture_count());
|
||||
for (int i = 0; i < images.size(); i++) {
|
||||
images.set(i, lightmapper->get_bake_texture(i));
|
||||
}
|
||||
const String texture_filename = p_image_data_path.get_basename();
|
||||
const int shadowmask_texture_count = lightmapper->get_shadowmask_texture_count();
|
||||
const bool save_shadowmask = shadowmask_mode != LightmapGIData::SHADOWMASK_MODE_NONE && shadowmask_texture_count > 0;
|
||||
|
||||
int slice_count = images.size();
|
||||
int slice_width = images[0]->get_width();
|
||||
int slice_height = images[0]->get_height();
|
||||
// Save the lightmap atlases.
|
||||
BakeError save_err = _save_and_reimport_atlas_textures(lightmapper, texture_filename, lightmap_textures, false);
|
||||
ERR_FAIL_COND_V(save_err != BAKE_ERROR_OK, save_err);
|
||||
|
||||
int slices_per_texture = Image::MAX_HEIGHT / slice_height;
|
||||
int texture_count = Math::ceil(slice_count / (float)slices_per_texture);
|
||||
|
||||
textures.resize(texture_count);
|
||||
|
||||
String base_path = p_image_data_path.get_basename();
|
||||
|
||||
int last_count = slice_count % slices_per_texture;
|
||||
for (int i = 0; i < texture_count; i++) {
|
||||
int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture;
|
||||
|
||||
Ref<Image> texture_image = Image::create_empty(slice_width, slice_height * texture_slice_count, false, images[0]->get_format());
|
||||
|
||||
for (int j = 0; j < texture_slice_count; j++) {
|
||||
texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j));
|
||||
}
|
||||
|
||||
String texture_path = texture_count > 1 ? base_path + "_" + itos(i) + ".exr" : base_path + ".exr";
|
||||
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
|
||||
if (FileAccess::exists(texture_path + ".import")) {
|
||||
config->load(texture_path + ".import");
|
||||
}
|
||||
|
||||
config->set_value("remap", "importer", "2d_array_texture");
|
||||
config->set_value("remap", "type", "CompressedTexture2DArray");
|
||||
if (!config->has_section_key("params", "compress/mode")) {
|
||||
// User may want another compression, so leave it be, but default to VRAM uncompressed.
|
||||
config->set_value("params", "compress/mode", 3);
|
||||
}
|
||||
config->set_value("params", "compress/channel_pack", 1);
|
||||
config->set_value("params", "mipmaps/generate", false);
|
||||
config->set_value("params", "slices/horizontal", 1);
|
||||
config->set_value("params", "slices/vertical", texture_slice_count);
|
||||
|
||||
config->save(texture_path + ".import");
|
||||
|
||||
Error err = texture_image->save_exr(texture_path, false);
|
||||
ERR_FAIL_COND_V(err, BAKE_ERROR_CANT_CREATE_IMAGE);
|
||||
ResourceLoader::import(texture_path);
|
||||
Ref<TextureLayered> t = ResourceLoader::load(texture_path); // If already loaded, it will be updated on refocus?
|
||||
ERR_FAIL_COND_V(t.is_null(), BAKE_ERROR_CANT_CREATE_IMAGE);
|
||||
textures[i] = t;
|
||||
}
|
||||
if (save_shadowmask) {
|
||||
// Save the shadowmask atlases.
|
||||
save_err = _save_and_reimport_atlas_textures(lightmapper, texture_filename + "_shadow", shadowmask_textures, true);
|
||||
ERR_FAIL_COND_V(save_err != BAKE_ERROR_OK, save_err);
|
||||
}
|
||||
|
||||
/* POSTBAKE: Save Light Data */
|
||||
|
||||
// POSTBAKE: Save Light Data.
|
||||
Ref<LightmapGIData> gi_data;
|
||||
|
||||
if (get_light_data().is_valid()) {
|
||||
gi_data = get_light_data();
|
||||
set_light_data(Ref<LightmapGIData>()); //clear
|
||||
set_light_data(Ref<LightmapGIData>()); // Clear.
|
||||
gi_data->clear();
|
||||
|
||||
} else {
|
||||
gi_data.instantiate();
|
||||
}
|
||||
|
||||
gi_data->set_lightmap_textures(textures);
|
||||
gi_data->set_lightmap_textures(lightmap_textures);
|
||||
|
||||
if (save_shadowmask) {
|
||||
gi_data->set_shadowmask_textures(shadowmask_textures);
|
||||
} else {
|
||||
gi_data->clear_shadowmask_textures();
|
||||
}
|
||||
|
||||
gi_data->set_uses_spherical_harmonics(directional);
|
||||
gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically.
|
||||
|
||||
for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) {
|
||||
Dictionary d = lightmapper->get_bake_mesh_userdata(i);
|
||||
|
|
@ -1202,7 +1335,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
}
|
||||
|
||||
{
|
||||
// create tetrahedrons
|
||||
// Create tetrahedrons.
|
||||
Vector<Vector3> points;
|
||||
Vector<Color> sh;
|
||||
points.resize(lightmapper->get_bake_probe_count());
|
||||
|
|
@ -1216,11 +1349,11 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
}
|
||||
}
|
||||
|
||||
//Obtain solved simplices
|
||||
|
||||
// Obtain solved simplices.
|
||||
if (p_bake_step) {
|
||||
p_bake_step(0.8, RTR("Generating Probe Volumes"), p_bake_userdata, true);
|
||||
}
|
||||
|
||||
Vector<Delaunay3D::OutputSimplex> solved_simplices = Delaunay3D::tetrahedralize(points);
|
||||
|
||||
LocalVector<BSPSimplex> bsp_simplices;
|
||||
|
|
@ -1343,6 +1476,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
}
|
||||
|
||||
set_light_data(gi_data);
|
||||
update_configuration_warnings();
|
||||
|
||||
return BAKE_ERROR_OK;
|
||||
}
|
||||
|
|
@ -1351,11 +1485,23 @@ void LightmapGI::_notification(int p_what) {
|
|||
switch (p_what) {
|
||||
case NOTIFICATION_POST_ENTER_TREE: {
|
||||
if (light_data.is_valid()) {
|
||||
ERR_FAIL_COND_MSG(
|
||||
light_data->is_using_spherical_harmonics() && !light_data->_is_using_packed_directional(),
|
||||
vformat(
|
||||
"%s (%s): The directional lightmap textures are stored in a format that isn't supported anymore. Please bake lightmaps again to make lightmaps display from this node again.",
|
||||
get_light_data()->get_path(), get_name()));
|
||||
|
||||
if (last_owner && last_owner != get_owner()) {
|
||||
light_data->clear_users();
|
||||
}
|
||||
|
||||
_assign_lightmaps();
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
last_owner = get_owner();
|
||||
|
||||
if (light_data.is_valid()) {
|
||||
_clear_lightmaps();
|
||||
}
|
||||
|
|
@ -1364,7 +1510,7 @@ void LightmapGI::_notification(int p_what) {
|
|||
}
|
||||
|
||||
void LightmapGI::_assign_lightmaps() {
|
||||
ERR_FAIL_COND(!light_data.is_valid());
|
||||
ERR_FAIL_COND(light_data.is_null());
|
||||
|
||||
for (int i = 0; i < light_data->get_user_count(); i++) {
|
||||
Node *node = get_node(light_data->get_user_path(i));
|
||||
|
|
@ -1383,7 +1529,7 @@ void LightmapGI::_assign_lightmaps() {
|
|||
}
|
||||
|
||||
void LightmapGI::_clear_lightmaps() {
|
||||
ERR_FAIL_COND(!light_data.is_valid());
|
||||
ERR_FAIL_COND(light_data.is_null());
|
||||
for (int i = 0; i < light_data->get_user_count(); i++) {
|
||||
Node *node = get_node(light_data->get_user_path(i));
|
||||
int instance_idx = light_data->get_user_sub_instance(i);
|
||||
|
|
@ -1414,6 +1560,7 @@ void LightmapGI::set_light_data(const Ref<LightmapGIData> &p_data) {
|
|||
if (is_inside_tree()) {
|
||||
_assign_lightmaps();
|
||||
}
|
||||
light_data->update_shadowmask_mode(shadowmask_mode);
|
||||
}
|
||||
|
||||
update_gizmos();
|
||||
|
|
@ -1468,6 +1615,19 @@ bool LightmapGI::is_directional() const {
|
|||
return directional;
|
||||
}
|
||||
|
||||
void LightmapGI::set_shadowmask_mode(LightmapGIData::ShadowmaskMode p_mode) {
|
||||
shadowmask_mode = p_mode;
|
||||
if (light_data.is_valid()) {
|
||||
light_data->update_shadowmask_mode(p_mode);
|
||||
}
|
||||
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
||||
LightmapGIData::ShadowmaskMode LightmapGI::get_shadowmask_mode() const {
|
||||
return shadowmask_mode;
|
||||
}
|
||||
|
||||
void LightmapGI::set_use_texture_for_bounces(bool p_enable) {
|
||||
use_texture_for_bounces = p_enable;
|
||||
}
|
||||
|
|
@ -1563,6 +1723,26 @@ int LightmapGI::get_max_texture_size() const {
|
|||
return max_texture_size;
|
||||
}
|
||||
|
||||
void LightmapGI::set_supersampling_enabled(bool p_enable) {
|
||||
supersampling_enabled = p_enable;
|
||||
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
bool LightmapGI::is_supersampling_enabled() const {
|
||||
return supersampling_enabled;
|
||||
}
|
||||
|
||||
void LightmapGI::set_supersampling_factor(float p_factor) {
|
||||
ERR_FAIL_COND(p_factor < 1);
|
||||
|
||||
supersampling_factor = p_factor;
|
||||
}
|
||||
|
||||
float LightmapGI::get_supersampling_factor() const {
|
||||
return supersampling_factor;
|
||||
}
|
||||
|
||||
void LightmapGI::set_generate_probes(GenerateProbes p_generate_probes) {
|
||||
gen_probes = p_generate_probes;
|
||||
}
|
||||
|
|
@ -1580,31 +1760,45 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const {
|
|||
}
|
||||
|
||||
PackedStringArray LightmapGI::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
|
||||
|
||||
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
|
||||
warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail."));
|
||||
#ifdef MODULE_LIGHTMAPPER_RD_ENABLED
|
||||
if (!DisplayServer::get_singleton()->can_create_rendering_device()) {
|
||||
warnings.push_back(vformat(RTR("Lightmaps can only be baked from a GPU that supports the RenderingDevice backends.\nYour GPU (%s) does not support RenderingDevice, as it does not support Vulkan, Direct3D 12, or Metal.\nLightmap baking will not be available on this device, although rendering existing baked lightmaps will work."), RenderingServer::get_singleton()->get_video_adapter_name()));
|
||||
return warnings;
|
||||
}
|
||||
|
||||
if (shadowmask_mode != LightmapGIData::SHADOWMASK_MODE_NONE && light_data.is_valid() && !light_data->has_shadowmask_textures()) {
|
||||
warnings.push_back(RTR("The lightmap has no baked shadowmask textures. Please rebake with the Shadowmask Mode set to anything other than None."));
|
||||
}
|
||||
|
||||
#elif defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
|
||||
warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name()));
|
||||
#else
|
||||
warnings.push_back(RTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time. Rendering existing baked lightmaps will still work."));
|
||||
#endif
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void LightmapGI::_validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "supersampling_factor" && !supersampling_enabled) {
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
if (p_property.name == "environment_custom_sky" && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
if (p_property.name == "environment_custom_color" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
if (p_property.name == "environment_custom_energy" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
if (p_property.name == "denoiser_strength" && !use_denoiser) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
if (p_property.name == "denoiser_range" && !use_denoiser) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1645,6 +1839,12 @@ void LightmapGI::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_max_texture_size", "max_texture_size"), &LightmapGI::set_max_texture_size);
|
||||
ClassDB::bind_method(D_METHOD("get_max_texture_size"), &LightmapGI::get_max_texture_size);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_supersampling_enabled", "enable"), &LightmapGI::set_supersampling_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_supersampling_enabled"), &LightmapGI::is_supersampling_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_supersampling_factor", "factor"), &LightmapGI::set_supersampling_factor);
|
||||
ClassDB::bind_method(D_METHOD("get_supersampling_factor"), &LightmapGI::get_supersampling_factor);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &LightmapGI::set_use_denoiser);
|
||||
ClassDB::bind_method(D_METHOD("is_using_denoiser"), &LightmapGI::is_using_denoiser);
|
||||
|
||||
|
|
@ -1660,6 +1860,9 @@ void LightmapGI::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_directional", "directional"), &LightmapGI::set_directional);
|
||||
ClassDB::bind_method(D_METHOD("is_directional"), &LightmapGI::is_directional);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_shadowmask_mode", "mode"), &LightmapGI::set_shadowmask_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_shadowmask_mode"), &LightmapGI::get_shadowmask_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_texture_for_bounces", "use_texture_for_bounces"), &LightmapGI::set_use_texture_for_bounces);
|
||||
ClassDB::bind_method(D_METHOD("is_using_texture_for_bounces"), &LightmapGI::is_using_texture_for_bounces);
|
||||
|
||||
|
|
@ -1670,9 +1873,12 @@ void LightmapGI::_bind_methods() {
|
|||
|
||||
ADD_GROUP("Tweaks", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "quality", PROPERTY_HINT_ENUM, "Low,Medium,High,Ultra"), "set_bake_quality", "get_bake_quality");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "supersampling"), "set_supersampling_enabled", "is_supersampling_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "supersampling_factor", PROPERTY_HINT_RANGE, "1,8,1"), "set_supersampling_factor", "get_supersampling_factor");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,6,1,or_greater"), "set_bounces", "get_bounces");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce_indirect_energy", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_bounce_indirect_energy", "get_bounce_indirect_energy");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional"), "set_directional", "is_directional");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "shadowmask_mode", PROPERTY_HINT_ENUM, "None,Replace,Overlay"), "set_shadowmask_mode", "get_shadowmask_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_texture_for_bounces"), "set_use_texture_for_bounces", "is_using_texture_for_bounces");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser");
|
||||
|
|
|
|||
|
|
@ -43,12 +43,29 @@ class LightmapGIData : public Resource {
|
|||
GDCLASS(LightmapGIData, Resource);
|
||||
RES_BASE_EXTENSION("lmbake")
|
||||
|
||||
Ref<TextureLayered> light_texture;
|
||||
TypedArray<TextureLayered> light_textures;
|
||||
public:
|
||||
enum ShadowmaskMode {
|
||||
SHADOWMASK_MODE_NONE,
|
||||
SHADOWMASK_MODE_REPLACE,
|
||||
SHADOWMASK_MODE_OVERLAY,
|
||||
SHADOWMASK_MODE_ONLY,
|
||||
};
|
||||
|
||||
private:
|
||||
// The 'merged' texture atlases actually used by the renderer.
|
||||
Ref<TextureLayered> combined_light_texture;
|
||||
Ref<TextureLayered> combined_shadowmask_texture;
|
||||
|
||||
// The temporary texture atlas arrays which are used for storage.
|
||||
// If a single atlas is too large, it's split and recombined during loading.
|
||||
TypedArray<TextureLayered> storage_light_textures;
|
||||
TypedArray<TextureLayered> storage_shadowmask_textures;
|
||||
|
||||
bool uses_spherical_harmonics = false;
|
||||
bool interior = false;
|
||||
|
||||
bool _uses_packed_directional = false;
|
||||
|
||||
RID lightmap;
|
||||
AABB bounds;
|
||||
float baked_exposure = 1.0;
|
||||
|
|
@ -68,6 +85,7 @@ class LightmapGIData : public Resource {
|
|||
Dictionary _get_probe_data() const;
|
||||
|
||||
void _reset_lightmap_textures();
|
||||
void _reset_shadowmask_textures();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
|
@ -92,6 +110,12 @@ public:
|
|||
void set_uses_spherical_harmonics(bool p_enable);
|
||||
bool is_using_spherical_harmonics() const;
|
||||
|
||||
void _set_uses_packed_directional(bool p_enable);
|
||||
bool _is_using_packed_directional() const;
|
||||
|
||||
void update_shadowmask_mode(ShadowmaskMode p_mode);
|
||||
ShadowmaskMode get_shadowmask_mode() const;
|
||||
|
||||
bool is_interior() const;
|
||||
float get_baked_exposure() const;
|
||||
|
||||
|
|
@ -107,6 +131,11 @@ public:
|
|||
void set_lightmap_textures(const TypedArray<TextureLayered> &p_data);
|
||||
TypedArray<TextureLayered> get_lightmap_textures() const;
|
||||
|
||||
void set_shadowmask_textures(const TypedArray<TextureLayered> &p_data);
|
||||
TypedArray<TextureLayered> get_shadowmask_textures() const;
|
||||
void clear_shadowmask_textures();
|
||||
bool has_shadowmask_textures();
|
||||
|
||||
virtual RID get_rid() const override;
|
||||
LightmapGIData();
|
||||
~LightmapGIData();
|
||||
|
|
@ -163,6 +192,8 @@ private:
|
|||
float bias = 0.0005;
|
||||
float texel_scale = 1.0;
|
||||
int max_texture_size = 16384;
|
||||
bool supersampling_enabled = false;
|
||||
float supersampling_factor = 2.0;
|
||||
bool interior = false;
|
||||
EnvironmentMode environment_mode = ENVIRONMENT_MODE_SCENE;
|
||||
Ref<Sky> environment_custom_sky;
|
||||
|
|
@ -170,10 +201,12 @@ private:
|
|||
float environment_custom_energy = 1.0;
|
||||
bool directional = false;
|
||||
bool use_texture_for_bounces = true;
|
||||
LightmapGIData::ShadowmaskMode shadowmask_mode = LightmapGIData::SHADOWMASK_MODE_NONE;
|
||||
GenerateProbes gen_probes = GENERATE_PROBES_SUBDIV_8;
|
||||
Ref<CameraAttributes> camera_attributes;
|
||||
|
||||
Ref<LightmapGIData> light_data;
|
||||
Node *last_owner = nullptr;
|
||||
|
||||
struct LightsFound {
|
||||
Transform3D xform;
|
||||
|
|
@ -185,7 +218,7 @@ private:
|
|||
NodePath node_path;
|
||||
int32_t subindex = 0;
|
||||
Ref<Mesh> mesh;
|
||||
int32_t lightmap_scale = 0;
|
||||
float lightmap_scale = 0.0;
|
||||
Vector<Ref<Material>> overrides;
|
||||
};
|
||||
|
||||
|
|
@ -240,6 +273,8 @@ private:
|
|||
void _plot_triangle_into_octree(GenProbesOctree *p_cell, float p_cell_size, const Vector3 *p_triangle);
|
||||
void _gen_new_positions_from_octree(const GenProbesOctree *p_cell, float p_cell_size, const Vector<Vector3> &probe_positions, LocalVector<Vector3> &new_probe_positions, HashMap<Vector3i, bool> &positions_used, const AABB &p_bounds);
|
||||
|
||||
BakeError _save_and_reimport_atlas_textures(const Ref<Lightmapper> p_lightmapper, const String &p_base_name, TypedArray<TextureLayered> &r_textures, bool p_is_shadowmask = false) const;
|
||||
|
||||
protected:
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
static void _bind_methods();
|
||||
|
|
@ -264,6 +299,9 @@ public:
|
|||
void set_directional(bool p_enable);
|
||||
bool is_directional() const;
|
||||
|
||||
void set_shadowmask_mode(LightmapGIData::ShadowmaskMode p_mode);
|
||||
LightmapGIData::ShadowmaskMode get_shadowmask_mode() const;
|
||||
|
||||
void set_use_texture_for_bounces(bool p_enable);
|
||||
bool is_using_texture_for_bounces() const;
|
||||
|
||||
|
|
@ -297,6 +335,12 @@ public:
|
|||
void set_max_texture_size(int p_size);
|
||||
int get_max_texture_size() const;
|
||||
|
||||
void set_supersampling_enabled(bool p_enable);
|
||||
bool is_supersampling_enabled() const;
|
||||
|
||||
void set_supersampling_factor(float p_factor);
|
||||
float get_supersampling_factor() const;
|
||||
|
||||
void set_generate_probes(GenerateProbes p_generate_probes);
|
||||
GenerateProbes get_generate_probes() const;
|
||||
|
||||
|
|
@ -312,6 +356,7 @@ public:
|
|||
LightmapGI();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(LightmapGIData::ShadowmaskMode);
|
||||
VARIANT_ENUM_CAST(LightmapGI::BakeQuality);
|
||||
VARIANT_ENUM_CAST(LightmapGI::GenerateProbes);
|
||||
VARIANT_ENUM_CAST(LightmapGI::BakeError);
|
||||
|
|
|
|||
|
|
@ -133,7 +133,6 @@ public:
|
|||
GENERATE_PROBES_SUBDIV_8,
|
||||
GENERATE_PROBES_SUBDIV_16,
|
||||
GENERATE_PROBES_SUBDIV_32,
|
||||
|
||||
};
|
||||
|
||||
enum LightType {
|
||||
|
|
@ -147,6 +146,7 @@ public:
|
|||
BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE,
|
||||
BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES,
|
||||
BAKE_ERROR_ATLAS_TOO_SMALL,
|
||||
BAKE_ERROR_USER_ABORTED,
|
||||
};
|
||||
|
||||
enum BakeQuality {
|
||||
|
|
@ -171,20 +171,23 @@ public:
|
|||
Vector<Vector3> points;
|
||||
Vector<Vector2> uv2;
|
||||
Vector<Vector3> normal;
|
||||
Vector<RID> material;
|
||||
Ref<Image> albedo_on_uv2;
|
||||
Ref<Image> emission_on_uv2;
|
||||
Variant userdata;
|
||||
};
|
||||
|
||||
virtual void add_mesh(const MeshData &p_mesh) = 0;
|
||||
virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) = 0;
|
||||
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) = 0;
|
||||
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) = 0;
|
||||
virtual void add_directional_light(const String &p_name, bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) = 0;
|
||||
virtual void add_omni_light(const String &p_name, bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) = 0;
|
||||
virtual void add_spot_light(const String &p_name, bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) = 0;
|
||||
virtual void add_probe(const Vector3 &p_position) = 0;
|
||||
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0;
|
||||
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_bake_shadowmask, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0, float p_supersampling_factor = 1.0) = 0;
|
||||
|
||||
virtual int get_bake_texture_count() const = 0;
|
||||
virtual Ref<Image> get_bake_texture(int p_index) const = 0;
|
||||
virtual int get_shadowmask_texture_count() const = 0;
|
||||
virtual Ref<Image> get_shadowmask_texture(int p_index) const = 0;
|
||||
virtual int get_bake_mesh_count() const = 0;
|
||||
virtual Variant get_bake_mesh_userdata(int p_index) const = 0;
|
||||
virtual Rect2 get_bake_mesh_uv_scale(int p_index) const = 0;
|
||||
|
|
|
|||
774
engine/scene/3d/look_at_modifier_3d.cpp
Normal file
774
engine/scene/3d/look_at_modifier_3d.cpp
Normal file
|
|
@ -0,0 +1,774 @@
|
|||
/**************************************************************************/
|
||||
/* look_at_modifier_3d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "look_at_modifier_3d.h"
|
||||
|
||||
void LookAtModifier3D::_validate_property(PropertyInfo &p_property) const {
|
||||
SkeletonModifier3D::_validate_property(p_property);
|
||||
|
||||
if (p_property.name == "bone_name" || p_property.name == "origin_bone_name") {
|
||||
Skeleton3D *skeleton = get_skeleton();
|
||||
if (skeleton) {
|
||||
p_property.hint = PROPERTY_HINT_ENUM;
|
||||
p_property.hint_string = skeleton->get_concatenated_bone_names();
|
||||
} else {
|
||||
p_property.hint = PROPERTY_HINT_NONE;
|
||||
p_property.hint_string = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (origin_from == ORIGIN_FROM_SPECIFIC_BONE) {
|
||||
if (p_property.name == "origin_external_node") {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
} else if (origin_from == ORIGIN_FROM_EXTERNAL_NODE) {
|
||||
if (p_property.name == "origin_bone" || p_property.name == "origin_bone_name") {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
} else {
|
||||
if (p_property.name == "origin_external_node" || p_property.name == "origin_bone" || p_property.name == "origin_bone_name") {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!use_angle_limitation &&
|
||||
(p_property.name == "symmetry_limitation" || p_property.name.ends_with("limit_angle") || p_property.name.ends_with("damp_threshold"))) ||
|
||||
(!use_secondary_rotation && p_property.name.begins_with("secondary_")) ||
|
||||
(!symmetry_limitation && (p_property.name == "primary_limit_angle" || p_property.name == "primary_damp_threshold" || p_property.name == "secondary_limit_angle" || p_property.name == "secondary_damp_threshold")) ||
|
||||
(symmetry_limitation && (p_property.name.begins_with("primary_positive") || p_property.name.begins_with("primary_negative") || p_property.name.begins_with("secondary_positive") || (p_property.name.begins_with("secondary_negative"))))) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
PackedStringArray LookAtModifier3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings();
|
||||
if (get_axis_from_bone_axis(forward_axis) == primary_rotation_axis) {
|
||||
warnings.push_back(RTR("Forward axis and primary rotation axis must not be parallel."));
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_bone_name(const String &p_bone_name) {
|
||||
bone_name = p_bone_name;
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
set_bone(sk->find_bone(bone_name));
|
||||
}
|
||||
}
|
||||
|
||||
String LookAtModifier3D::get_bone_name() const {
|
||||
return bone_name;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_bone(int p_bone) {
|
||||
bone = p_bone;
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
if (bone <= -1 || bone >= sk->get_bone_count()) {
|
||||
WARN_PRINT("Bone index out of range!");
|
||||
bone = -1;
|
||||
} else {
|
||||
bone_name = sk->get_bone_name(bone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int LookAtModifier3D::get_bone() const {
|
||||
return bone;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_forward_axis(BoneAxis p_axis) {
|
||||
forward_axis = p_axis;
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
||||
SkeletonModifier3D::BoneAxis LookAtModifier3D::get_forward_axis() const {
|
||||
return forward_axis;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_primary_rotation_axis(Vector3::Axis p_axis) {
|
||||
primary_rotation_axis = p_axis;
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
||||
Vector3::Axis LookAtModifier3D::get_primary_rotation_axis() const {
|
||||
return primary_rotation_axis;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_use_secondary_rotation(bool p_enabled) {
|
||||
use_secondary_rotation = p_enabled;
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
bool LookAtModifier3D::is_using_secondary_rotation() const {
|
||||
return use_secondary_rotation;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_target_node(const NodePath &p_target_node) {
|
||||
if (target_node != p_target_node) {
|
||||
init_transition();
|
||||
}
|
||||
target_node = p_target_node;
|
||||
}
|
||||
|
||||
NodePath LookAtModifier3D::get_target_node() const {
|
||||
return target_node;
|
||||
}
|
||||
|
||||
// For origin settings.
|
||||
|
||||
void LookAtModifier3D::set_origin_from(OriginFrom p_origin_from) {
|
||||
origin_from = p_origin_from;
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
LookAtModifier3D::OriginFrom LookAtModifier3D::get_origin_from() const {
|
||||
return origin_from;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_origin_bone_name(const String &p_bone_name) {
|
||||
origin_bone_name = p_bone_name;
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
set_origin_bone(sk->find_bone(origin_bone_name));
|
||||
}
|
||||
}
|
||||
|
||||
String LookAtModifier3D::get_origin_bone_name() const {
|
||||
return origin_bone_name;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_origin_bone(int p_bone) {
|
||||
origin_bone = p_bone;
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
if (origin_bone <= -1 || origin_bone >= sk->get_bone_count()) {
|
||||
WARN_PRINT("Bone index out of range!");
|
||||
origin_bone = -1;
|
||||
} else {
|
||||
origin_bone_name = sk->get_bone_name(origin_bone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int LookAtModifier3D::get_origin_bone() const {
|
||||
return origin_bone;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_origin_external_node(const NodePath &p_external_node) {
|
||||
origin_external_node = p_external_node;
|
||||
}
|
||||
|
||||
NodePath LookAtModifier3D::get_origin_external_node() const {
|
||||
return origin_external_node;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_origin_offset(const Vector3 &p_offset) {
|
||||
origin_offset = p_offset;
|
||||
}
|
||||
|
||||
Vector3 LookAtModifier3D::get_origin_offset() const {
|
||||
return origin_offset;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_origin_safe_margin(float p_margin) {
|
||||
origin_safe_margin = p_margin;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_origin_safe_margin() const {
|
||||
return origin_safe_margin;
|
||||
}
|
||||
|
||||
// For time-based interpolation.
|
||||
|
||||
void LookAtModifier3D::set_duration(float p_duration) {
|
||||
duration = p_duration;
|
||||
if (Math::is_zero_approx(p_duration)) {
|
||||
time_step = 0;
|
||||
remaining = 0;
|
||||
} else {
|
||||
time_step = 1.0 / p_duration; // Cache to avoid division.
|
||||
}
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_duration() const {
|
||||
return duration;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_transition_type(Tween::TransitionType p_transition_type) {
|
||||
transition_type = p_transition_type;
|
||||
}
|
||||
|
||||
Tween::TransitionType LookAtModifier3D::get_transition_type() const {
|
||||
return transition_type;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_ease_type(Tween::EaseType p_ease_type) {
|
||||
ease_type = p_ease_type;
|
||||
}
|
||||
|
||||
Tween::EaseType LookAtModifier3D::get_ease_type() const {
|
||||
return ease_type;
|
||||
}
|
||||
|
||||
// For angle limitation.
|
||||
|
||||
void LookAtModifier3D::set_use_angle_limitation(bool p_enabled) {
|
||||
use_angle_limitation = p_enabled;
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
bool LookAtModifier3D::is_using_angle_limitation() const {
|
||||
return use_angle_limitation;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_symmetry_limitation(bool p_enabled) {
|
||||
symmetry_limitation = p_enabled;
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
bool LookAtModifier3D::is_limitation_symmetry() const {
|
||||
return symmetry_limitation;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_primary_limit_angle(float p_angle) {
|
||||
primary_limit_angle = p_angle;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_primary_limit_angle() const {
|
||||
return primary_limit_angle;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_primary_damp_threshold(float p_power) {
|
||||
primary_damp_threshold = p_power;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_primary_damp_threshold() const {
|
||||
return primary_damp_threshold;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_primary_positive_limit_angle(float p_angle) {
|
||||
primary_positive_limit_angle = p_angle;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_primary_positive_limit_angle() const {
|
||||
return primary_positive_limit_angle;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_primary_positive_damp_threshold(float p_power) {
|
||||
primary_positive_damp_threshold = p_power;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_primary_positive_damp_threshold() const {
|
||||
return primary_positive_damp_threshold;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_primary_negative_limit_angle(float p_angle) {
|
||||
primary_negative_limit_angle = p_angle;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_primary_negative_limit_angle() const {
|
||||
return primary_negative_limit_angle;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_primary_negative_damp_threshold(float p_power) {
|
||||
primary_negative_damp_threshold = p_power;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_primary_negative_damp_threshold() const {
|
||||
return primary_negative_damp_threshold;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_secondary_limit_angle(float p_angle) {
|
||||
secondary_limit_angle = p_angle;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_secondary_limit_angle() const {
|
||||
return secondary_limit_angle;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_secondary_damp_threshold(float p_power) {
|
||||
secondary_damp_threshold = p_power;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_secondary_damp_threshold() const {
|
||||
return secondary_damp_threshold;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_secondary_positive_limit_angle(float p_angle) {
|
||||
secondary_positive_limit_angle = p_angle;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_secondary_positive_limit_angle() const {
|
||||
return secondary_positive_limit_angle;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_secondary_positive_damp_threshold(float p_power) {
|
||||
secondary_positive_damp_threshold = p_power;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_secondary_positive_damp_threshold() const {
|
||||
return secondary_positive_damp_threshold;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_secondary_negative_limit_angle(float p_angle) {
|
||||
secondary_negative_limit_angle = p_angle;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_secondary_negative_limit_angle() const {
|
||||
return secondary_negative_limit_angle;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::set_secondary_negative_damp_threshold(float p_power) {
|
||||
secondary_negative_damp_threshold = p_power;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_secondary_negative_damp_threshold() const {
|
||||
return secondary_negative_damp_threshold;
|
||||
}
|
||||
|
||||
bool LookAtModifier3D::is_target_within_limitation() const {
|
||||
return is_within_limitations;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::get_interpolation_remaining() const {
|
||||
return remaining * duration;
|
||||
}
|
||||
|
||||
bool LookAtModifier3D::is_interpolating() const {
|
||||
return Math::is_zero_approx(remaining);
|
||||
}
|
||||
|
||||
// General API.
|
||||
|
||||
void LookAtModifier3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_target_node", "target_node"), &LookAtModifier3D::set_target_node);
|
||||
ClassDB::bind_method(D_METHOD("get_target_node"), &LookAtModifier3D::get_target_node);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &LookAtModifier3D::set_bone_name);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_name"), &LookAtModifier3D::get_bone_name);
|
||||
ClassDB::bind_method(D_METHOD("set_bone", "bone"), &LookAtModifier3D::set_bone);
|
||||
ClassDB::bind_method(D_METHOD("get_bone"), &LookAtModifier3D::get_bone);
|
||||
ClassDB::bind_method(D_METHOD("set_forward_axis", "forward_axis"), &LookAtModifier3D::set_forward_axis);
|
||||
ClassDB::bind_method(D_METHOD("get_forward_axis"), &LookAtModifier3D::get_forward_axis);
|
||||
ClassDB::bind_method(D_METHOD("set_primary_rotation_axis", "axis"), &LookAtModifier3D::set_primary_rotation_axis);
|
||||
ClassDB::bind_method(D_METHOD("get_primary_rotation_axis"), &LookAtModifier3D::get_primary_rotation_axis);
|
||||
ClassDB::bind_method(D_METHOD("set_use_secondary_rotation", "enabled"), &LookAtModifier3D::set_use_secondary_rotation);
|
||||
ClassDB::bind_method(D_METHOD("is_using_secondary_rotation"), &LookAtModifier3D::is_using_secondary_rotation);
|
||||
ClassDB::bind_method(D_METHOD("set_origin_safe_margin", "margin"), &LookAtModifier3D::set_origin_safe_margin);
|
||||
ClassDB::bind_method(D_METHOD("get_origin_safe_margin"), &LookAtModifier3D::get_origin_safe_margin);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_origin_from", "origin_from"), &LookAtModifier3D::set_origin_from);
|
||||
ClassDB::bind_method(D_METHOD("get_origin_from"), &LookAtModifier3D::get_origin_from);
|
||||
ClassDB::bind_method(D_METHOD("set_origin_bone_name", "bone_name"), &LookAtModifier3D::set_origin_bone_name);
|
||||
ClassDB::bind_method(D_METHOD("get_origin_bone_name"), &LookAtModifier3D::get_origin_bone_name);
|
||||
ClassDB::bind_method(D_METHOD("set_origin_bone", "bone"), &LookAtModifier3D::set_origin_bone);
|
||||
ClassDB::bind_method(D_METHOD("get_origin_bone"), &LookAtModifier3D::get_origin_bone);
|
||||
ClassDB::bind_method(D_METHOD("set_origin_external_node", "external_node"), &LookAtModifier3D::set_origin_external_node);
|
||||
ClassDB::bind_method(D_METHOD("get_origin_external_node"), &LookAtModifier3D::get_origin_external_node);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_origin_offset", "offset"), &LookAtModifier3D::set_origin_offset);
|
||||
ClassDB::bind_method(D_METHOD("get_origin_offset"), &LookAtModifier3D::get_origin_offset);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_duration", "duration"), &LookAtModifier3D::set_duration);
|
||||
ClassDB::bind_method(D_METHOD("get_duration"), &LookAtModifier3D::get_duration);
|
||||
ClassDB::bind_method(D_METHOD("set_transition_type", "transition_type"), &LookAtModifier3D::set_transition_type);
|
||||
ClassDB::bind_method(D_METHOD("get_transition_type"), &LookAtModifier3D::get_transition_type);
|
||||
ClassDB::bind_method(D_METHOD("set_ease_type", "ease_type"), &LookAtModifier3D::set_ease_type);
|
||||
ClassDB::bind_method(D_METHOD("get_ease_type"), &LookAtModifier3D::get_ease_type);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_angle_limitation", "enabled"), &LookAtModifier3D::set_use_angle_limitation);
|
||||
ClassDB::bind_method(D_METHOD("is_using_angle_limitation"), &LookAtModifier3D::is_using_angle_limitation);
|
||||
ClassDB::bind_method(D_METHOD("set_symmetry_limitation", "enabled"), &LookAtModifier3D::set_symmetry_limitation);
|
||||
ClassDB::bind_method(D_METHOD("is_limitation_symmetry"), &LookAtModifier3D::is_limitation_symmetry);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_primary_limit_angle", "angle"), &LookAtModifier3D::set_primary_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("get_primary_limit_angle"), &LookAtModifier3D::get_primary_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("set_primary_damp_threshold", "power"), &LookAtModifier3D::set_primary_damp_threshold);
|
||||
ClassDB::bind_method(D_METHOD("get_primary_damp_threshold"), &LookAtModifier3D::get_primary_damp_threshold);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_primary_positive_limit_angle", "angle"), &LookAtModifier3D::set_primary_positive_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("get_primary_positive_limit_angle"), &LookAtModifier3D::get_primary_positive_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("set_primary_positive_damp_threshold", "power"), &LookAtModifier3D::set_primary_positive_damp_threshold);
|
||||
ClassDB::bind_method(D_METHOD("get_primary_positive_damp_threshold"), &LookAtModifier3D::get_primary_positive_damp_threshold);
|
||||
ClassDB::bind_method(D_METHOD("set_primary_negative_limit_angle", "angle"), &LookAtModifier3D::set_primary_negative_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("get_primary_negative_limit_angle"), &LookAtModifier3D::get_primary_negative_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("set_primary_negative_damp_threshold", "power"), &LookAtModifier3D::set_primary_negative_damp_threshold);
|
||||
ClassDB::bind_method(D_METHOD("get_primary_negative_damp_threshold"), &LookAtModifier3D::get_primary_negative_damp_threshold);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_secondary_limit_angle", "angle"), &LookAtModifier3D::set_secondary_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("get_secondary_limit_angle"), &LookAtModifier3D::get_secondary_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("set_secondary_damp_threshold", "power"), &LookAtModifier3D::set_secondary_damp_threshold);
|
||||
ClassDB::bind_method(D_METHOD("get_secondary_damp_threshold"), &LookAtModifier3D::get_secondary_damp_threshold);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_secondary_positive_limit_angle", "angle"), &LookAtModifier3D::set_secondary_positive_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("get_secondary_positive_limit_angle"), &LookAtModifier3D::get_secondary_positive_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("set_secondary_positive_damp_threshold", "power"), &LookAtModifier3D::set_secondary_positive_damp_threshold);
|
||||
ClassDB::bind_method(D_METHOD("get_secondary_positive_damp_threshold"), &LookAtModifier3D::get_secondary_positive_damp_threshold);
|
||||
ClassDB::bind_method(D_METHOD("set_secondary_negative_limit_angle", "angle"), &LookAtModifier3D::set_secondary_negative_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("get_secondary_negative_limit_angle"), &LookAtModifier3D::get_secondary_negative_limit_angle);
|
||||
ClassDB::bind_method(D_METHOD("set_secondary_negative_damp_threshold", "power"), &LookAtModifier3D::set_secondary_negative_damp_threshold);
|
||||
ClassDB::bind_method(D_METHOD("get_secondary_negative_damp_threshold"), &LookAtModifier3D::get_secondary_negative_damp_threshold);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_interpolation_remaining"), &LookAtModifier3D::get_interpolation_remaining);
|
||||
ClassDB::bind_method(D_METHOD("is_interpolating"), &LookAtModifier3D::is_interpolating);
|
||||
ClassDB::bind_method(D_METHOD("is_target_within_limitation"), &LookAtModifier3D::is_target_within_limitation);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_TYPE, "Node3D"), "set_target_node", "get_target_node");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "bone_name", PROPERTY_HINT_ENUM_SUGGESTION, ""), "set_bone_name", "get_bone_name");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_bone", "get_bone");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "forward_axis", PROPERTY_HINT_ENUM, "+X,-X,+Y,-Y,+Z,-Z"), "set_forward_axis", "get_forward_axis");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "primary_rotation_axis", PROPERTY_HINT_ENUM, "X,Y,Z"), "set_primary_rotation_axis", "get_primary_rotation_axis");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_secondary_rotation"), "set_use_secondary_rotation", "is_using_secondary_rotation");
|
||||
|
||||
ADD_GROUP("Origin Settings", "origin_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "origin_from", PROPERTY_HINT_ENUM, "Self,SpecificBone,ExternalNode"), "set_origin_from", "get_origin_from");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "origin_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, ""), "set_origin_bone_name", "get_origin_bone_name");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "origin_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_origin_bone", "get_origin_bone");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "origin_external_node", PROPERTY_HINT_NODE_TYPE, "Node3D"), "set_origin_external_node", "get_origin_external_node");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "origin_offset"), "set_origin_offset", "get_origin_offset");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "origin_safe_margin", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater,suffix:m"), "set_origin_safe_margin", "get_origin_safe_margin");
|
||||
|
||||
ADD_GROUP("Time Based Interpolation", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "duration", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater,suffix:s"), "set_duration", "get_duration");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "transition_type", PROPERTY_HINT_ENUM, "Linear,Sine,Quint,Quart,Quad,Expo,Elastic,Cubic,Circ,Bounce,Back,Spring"), "set_transition_type", "get_transition_type");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "ease_type", PROPERTY_HINT_ENUM, "In,Out,InOut,OutIn"), "set_ease_type", "get_ease_type");
|
||||
|
||||
ADD_GROUP("Angle Limitation", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_angle_limitation"), "set_use_angle_limitation", "is_using_angle_limitation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symmetry_limitation"), "set_symmetry_limitation", "is_limitation_symmetry");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_limit_angle", PROPERTY_HINT_RANGE, "0,360,0.01,radians_as_degrees"), "set_primary_limit_angle", "get_primary_limit_angle");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_primary_damp_threshold", "get_primary_damp_threshold");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_positive_limit_angle", PROPERTY_HINT_RANGE, "0,180,0.01,radians_as_degrees"), "set_primary_positive_limit_angle", "get_primary_positive_limit_angle");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_positive_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_primary_positive_damp_threshold", "get_primary_positive_damp_threshold");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_negative_limit_angle", PROPERTY_HINT_RANGE, "0,180,0.01,radians_as_degrees"), "set_primary_negative_limit_angle", "get_primary_negative_limit_angle");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_negative_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_primary_negative_damp_threshold", "get_primary_negative_damp_threshold");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_limit_angle", PROPERTY_HINT_RANGE, "0,360,0.01,radians_as_degrees"), "set_secondary_limit_angle", "get_secondary_limit_angle");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_secondary_damp_threshold", "get_secondary_damp_threshold");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_positive_limit_angle", PROPERTY_HINT_RANGE, "0,180,0.01,radians_as_degrees"), "set_secondary_positive_limit_angle", "get_secondary_positive_limit_angle");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_positive_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_secondary_positive_damp_threshold", "get_secondary_positive_damp_threshold");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_negative_limit_angle", PROPERTY_HINT_RANGE, "0,180,0.01,radians_as_degrees"), "set_secondary_negative_limit_angle", "get_secondary_negative_limit_angle");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_negative_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_secondary_negative_damp_threshold", "get_secondary_negative_damp_threshold");
|
||||
|
||||
BIND_ENUM_CONSTANT(ORIGIN_FROM_SELF);
|
||||
BIND_ENUM_CONSTANT(ORIGIN_FROM_SPECIFIC_BONE);
|
||||
BIND_ENUM_CONSTANT(ORIGIN_FROM_EXTERNAL_NODE);
|
||||
}
|
||||
|
||||
void LookAtModifier3D::_process_modification() {
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Skeleton3D *skeleton = get_skeleton();
|
||||
if (!skeleton || bone < 0 || bone >= skeleton->get_bone_count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate bone rest space in the world.
|
||||
Transform3D bone_rest_space;
|
||||
int parent_bone = skeleton->get_bone_parent(bone);
|
||||
if (parent_bone < 0) {
|
||||
bone_rest_space = skeleton->get_global_transform();
|
||||
bone_rest_space.origin += skeleton->get_bone_rest(bone).origin;
|
||||
} else {
|
||||
bone_rest_space = skeleton->get_global_transform() * skeleton->get_bone_global_pose(parent_bone);
|
||||
bone_rest_space.origin += skeleton->get_bone_rest(bone).origin;
|
||||
}
|
||||
|
||||
// Calculate forward_vector and destination.
|
||||
is_within_limitations = true;
|
||||
Vector3 prev_forward_vector = forward_vector;
|
||||
Quaternion destination;
|
||||
Node3D *target = Object::cast_to<Node3D>(get_node_or_null(target_node));
|
||||
if (!target) {
|
||||
destination = skeleton->get_bone_pose_rotation(bone);
|
||||
} else {
|
||||
Transform3D origin_tr;
|
||||
if (origin_from == ORIGIN_FROM_SPECIFIC_BONE && origin_bone >= 0 && origin_bone < skeleton->get_bone_count()) {
|
||||
origin_tr = skeleton->get_global_transform() * skeleton->get_bone_global_pose(origin_bone);
|
||||
} else if (origin_from == ORIGIN_FROM_EXTERNAL_NODE) {
|
||||
Node3D *origin_src = Object::cast_to<Node3D>(get_node_or_null(origin_external_node));
|
||||
if (origin_src) {
|
||||
origin_tr = origin_src->get_global_transform();
|
||||
} else {
|
||||
origin_tr = bone_rest_space;
|
||||
}
|
||||
} else {
|
||||
origin_tr = bone_rest_space;
|
||||
}
|
||||
forward_vector = bone_rest_space.orthonormalized().basis.xform_inv((target->get_global_position() - origin_tr.translated_local(origin_offset).origin));
|
||||
forward_vector_nrm = forward_vector.normalized();
|
||||
if (forward_vector_nrm.abs().is_equal_approx(get_vector_from_axis(primary_rotation_axis))) {
|
||||
destination = skeleton->get_bone_pose_rotation(bone);
|
||||
forward_vector = Vector3(0, 0, 0); // The zero-vector to be used for checking in the line immediately below to avoid animation glitch.
|
||||
} else {
|
||||
destination = look_at_with_axes(skeleton->get_bone_rest(bone)).basis.get_rotation_quaternion();
|
||||
}
|
||||
}
|
||||
|
||||
// Detect flipping.
|
||||
bool is_not_max_influence = influence < 1.0;
|
||||
bool is_flippable = use_angle_limitation || is_not_max_influence;
|
||||
Vector3::Axis current_forward_axis = get_axis_from_bone_axis(forward_axis);
|
||||
if (is_intersecting_axis(prev_forward_vector, forward_vector, current_forward_axis, secondary_rotation_axis) ||
|
||||
is_intersecting_axis(prev_forward_vector, forward_vector, primary_rotation_axis, primary_rotation_axis, true) ||
|
||||
is_intersecting_axis(prev_forward_vector, forward_vector, secondary_rotation_axis, current_forward_axis) ||
|
||||
(prev_forward_vector != Vector3(0, 0, 0) && forward_vector == Vector3(0, 0, 0)) ||
|
||||
(prev_forward_vector == Vector3(0, 0, 0) && forward_vector != Vector3(0, 0, 0))) {
|
||||
init_transition();
|
||||
} else if (is_flippable && signbit(prev_forward_vector[secondary_rotation_axis]) != signbit(forward_vector[secondary_rotation_axis])) {
|
||||
// Flipping by angle_limitation can be detected by sign of secondary rotation axes during forward_vector is rotated more than 90 degree from forward_axis (means dot production is negative).
|
||||
Vector3 prev_forward_vector_nrm = forward_vector.normalized();
|
||||
Vector3 rest_forward_vector = get_vector_from_bone_axis(forward_axis);
|
||||
if (symmetry_limitation) {
|
||||
if ((is_not_max_influence || !Math::is_equal_approx(primary_limit_angle, (float)Math_TAU)) &&
|
||||
prev_forward_vector_nrm.dot(rest_forward_vector) < 0 &&
|
||||
forward_vector_nrm.dot(rest_forward_vector) < 0) {
|
||||
init_transition();
|
||||
}
|
||||
} else {
|
||||
if ((is_not_max_influence || !Math::is_equal_approx(primary_positive_limit_angle + primary_negative_limit_angle, (float)Math_TAU)) &&
|
||||
prev_forward_vector_nrm.dot(rest_forward_vector) < 0 &&
|
||||
forward_vector_nrm.dot(rest_forward_vector) < 0) {
|
||||
init_transition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do time-based interpolation.
|
||||
if (remaining > 0) {
|
||||
double delta = 0.0;
|
||||
if (skeleton->get_modifier_callback_mode_process() == Skeleton3D::MODIFIER_CALLBACK_MODE_PROCESS_IDLE) {
|
||||
delta = get_process_delta_time();
|
||||
} else {
|
||||
delta = get_physics_process_delta_time();
|
||||
}
|
||||
remaining = MAX(0, remaining - time_step * delta);
|
||||
if (is_flippable) {
|
||||
// Interpolate through the rest same as AnimationTree blending for preventing to penetrate the bone into the body.
|
||||
Quaternion rest = skeleton->get_bone_rest(bone).basis.get_rotation_quaternion();
|
||||
float weight = Tween::run_equation(transition_type, ease_type, 1 - remaining, 0.0, 1.0, 1.0);
|
||||
destination = rest * Quaternion().slerp(rest.inverse() * from_q, 1 - weight) * Quaternion().slerp(rest.inverse() * destination, weight);
|
||||
} else {
|
||||
destination = from_q.slerp(destination, Tween::run_equation(transition_type, ease_type, 1 - remaining, 0.0, 1.0, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
skeleton->set_bone_pose_rotation(bone, destination);
|
||||
prev_q = destination;
|
||||
}
|
||||
|
||||
bool LookAtModifier3D::is_intersecting_axis(const Vector3 &p_prev, const Vector3 &p_current, Vector3::Axis p_flipping_axis, Vector3::Axis p_check_axis, bool p_check_plane) const {
|
||||
// Prevent that the angular velocity does not become too large.
|
||||
// Check that is p_flipping_axis flipped nearby p_check_axis (close than origin_safe_margin) or not. If p_check_plane is true, check two axes of crossed plane.
|
||||
if (p_check_plane) {
|
||||
if (get_projection_vector(p_prev, p_check_axis).length() > origin_safe_margin && get_projection_vector(p_current, p_check_axis).length() > origin_safe_margin) {
|
||||
return false;
|
||||
}
|
||||
} else if (Math::abs(p_prev[p_check_axis]) > origin_safe_margin && Math::abs(p_current[p_check_axis]) > origin_safe_margin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return signbit(p_prev[p_flipping_axis]) != signbit(p_current[p_flipping_axis]);
|
||||
}
|
||||
|
||||
Vector3 LookAtModifier3D::get_basis_vector_from_bone_axis(const Basis &p_basis, BoneAxis p_axis) {
|
||||
Vector3 ret;
|
||||
switch (p_axis) {
|
||||
case BONE_AXIS_PLUS_X: {
|
||||
ret = p_basis.get_column(0);
|
||||
} break;
|
||||
case BONE_AXIS_MINUS_X: {
|
||||
ret = -p_basis.get_column(0);
|
||||
} break;
|
||||
case BONE_AXIS_PLUS_Y: {
|
||||
ret = p_basis.get_column(1);
|
||||
} break;
|
||||
case BONE_AXIS_MINUS_Y: {
|
||||
ret = -p_basis.get_column(1);
|
||||
} break;
|
||||
case BONE_AXIS_PLUS_Z: {
|
||||
ret = p_basis.get_column(2);
|
||||
} break;
|
||||
case BONE_AXIS_MINUS_Z: {
|
||||
ret = -p_basis.get_column(2);
|
||||
} break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vector3::Axis LookAtModifier3D::get_secondary_rotation_axis(BoneAxis p_forward_axis, Vector3::Axis p_primary_rotation_axis) {
|
||||
Vector3 secondary_plane = get_vector_from_bone_axis(p_forward_axis) + get_vector_from_axis(p_primary_rotation_axis);
|
||||
return Math::is_zero_approx(secondary_plane.x) ? Vector3::AXIS_X : (Math::is_zero_approx(secondary_plane.y) ? Vector3::AXIS_Y : Vector3::AXIS_Z);
|
||||
}
|
||||
|
||||
Vector2 LookAtModifier3D::get_projection_vector(const Vector3 &p_vector, Vector3::Axis p_axis) {
|
||||
// NOTE: axis is swapped between 2D and 3D.
|
||||
Vector2 ret;
|
||||
switch (p_axis) {
|
||||
case Vector3::AXIS_X: {
|
||||
ret = Vector2(p_vector.z, p_vector.y);
|
||||
} break;
|
||||
case Vector3::AXIS_Y: {
|
||||
ret = Vector2(p_vector.x, p_vector.z);
|
||||
} break;
|
||||
case Vector3::AXIS_Z: {
|
||||
ret = Vector2(p_vector.y, p_vector.x);
|
||||
} break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
float LookAtModifier3D::remap_damped(float p_from, float p_to, float p_damp_threshold, float p_value) const {
|
||||
float sign = signbit(p_value) ? -1.0f : 1.0f;
|
||||
float abs_value = Math::abs(p_value);
|
||||
|
||||
if (Math::is_equal_approx(p_damp_threshold, 1.0f) || Math::is_zero_approx(p_to)) {
|
||||
return sign * CLAMP(abs_value, p_from, p_to); // Avoid division by zero.
|
||||
}
|
||||
|
||||
float value = Math::inverse_lerp(p_from, p_to, abs_value);
|
||||
|
||||
if (value <= p_damp_threshold) {
|
||||
return sign * CLAMP(abs_value, p_from, p_to);
|
||||
}
|
||||
|
||||
double limit = Math_PI;
|
||||
double inv_to = 1.0 / p_to;
|
||||
double end_x = limit * inv_to;
|
||||
double position = abs_value * inv_to;
|
||||
Vector2 start = Vector2(p_damp_threshold, p_damp_threshold);
|
||||
Vector2 mid = Vector2(1.0, 1.0);
|
||||
Vector2 end = Vector2(end_x, 1.0);
|
||||
value = get_bspline_y(start, mid, end, position);
|
||||
|
||||
return sign * Math::lerp(p_from, p_to, value);
|
||||
}
|
||||
|
||||
double LookAtModifier3D::get_bspline_y(const Vector2 &p_from, const Vector2 &p_control, const Vector2 &p_to, double p_x) const {
|
||||
double a = p_from.x - 2.0 * p_control.x + p_to.x;
|
||||
double b = -2.0 * p_from.x + 2.0 * p_control.x;
|
||||
double c = p_from.x - p_x;
|
||||
double t = 0.0;
|
||||
if (Math::is_zero_approx(a)) {
|
||||
t = -c / b; // Almost linear.
|
||||
} else {
|
||||
double discriminant = b * b - 4.0 * a * c;
|
||||
double sqrt_discriminant = Math::sqrt(discriminant);
|
||||
double e = 1.0 / (2.0 * a);
|
||||
double t1 = (-b + sqrt_discriminant) * e;
|
||||
t = (0.0 <= t1 && t1 <= 1.0) ? t1 : (-b - sqrt_discriminant) * e;
|
||||
}
|
||||
double u = 1.0 - t;
|
||||
double y = u * u * p_from.y + 2.0 * u * t * p_control.y + t * t * p_to.y;
|
||||
return y;
|
||||
}
|
||||
|
||||
Transform3D LookAtModifier3D::look_at_with_axes(const Transform3D &p_rest) {
|
||||
// Primary rotation by projection to 2D plane by xform_inv and picking elements.
|
||||
Vector3 current_vector = get_basis_vector_from_bone_axis(p_rest.basis, forward_axis).normalized();
|
||||
Vector2 src_vec2 = get_projection_vector(p_rest.basis.xform_inv(forward_vector_nrm), primary_rotation_axis).normalized();
|
||||
Vector2 dst_vec2 = get_projection_vector(p_rest.basis.xform_inv(current_vector), primary_rotation_axis).normalized();
|
||||
real_t calculated_angle = src_vec2.angle_to(dst_vec2);
|
||||
Transform3D primary_result = p_rest.rotated_local(get_vector_from_axis(primary_rotation_axis), calculated_angle);
|
||||
Transform3D current_result = primary_result; // primary_result will be used by calculation of secondary rotation, current_result is rotated by that.
|
||||
float limit_angle = 0.0;
|
||||
float damp_threshold = 0.0;
|
||||
|
||||
if (use_angle_limitation) {
|
||||
if (symmetry_limitation) {
|
||||
limit_angle = primary_limit_angle * 0.5f;
|
||||
damp_threshold = primary_damp_threshold;
|
||||
} else {
|
||||
if (signbit(calculated_angle)) {
|
||||
limit_angle = primary_negative_limit_angle;
|
||||
damp_threshold = primary_negative_damp_threshold;
|
||||
} else {
|
||||
limit_angle = primary_positive_limit_angle;
|
||||
damp_threshold = primary_positive_damp_threshold;
|
||||
}
|
||||
}
|
||||
if (Math::abs(calculated_angle) > limit_angle) {
|
||||
is_within_limitations = false;
|
||||
}
|
||||
calculated_angle = remap_damped(0, limit_angle, damp_threshold, calculated_angle);
|
||||
current_result = p_rest.rotated_local(get_vector_from_axis(primary_rotation_axis), calculated_angle);
|
||||
}
|
||||
|
||||
// Needs for detecting flipping even if use_secondary_rotation is false.
|
||||
secondary_rotation_axis = get_secondary_rotation_axis(forward_axis, primary_rotation_axis);
|
||||
|
||||
if (!use_secondary_rotation) {
|
||||
return current_result;
|
||||
}
|
||||
|
||||
// Secondary rotation by projection to 2D plane by xform_inv and picking elements.
|
||||
current_vector = get_basis_vector_from_bone_axis(primary_result.basis, forward_axis).normalized();
|
||||
src_vec2 = get_projection_vector(primary_result.basis.xform_inv(forward_vector_nrm), secondary_rotation_axis).normalized();
|
||||
dst_vec2 = get_projection_vector(primary_result.basis.xform_inv(current_vector), secondary_rotation_axis).normalized();
|
||||
calculated_angle = src_vec2.angle_to(dst_vec2);
|
||||
|
||||
if (use_angle_limitation) {
|
||||
if (symmetry_limitation) {
|
||||
limit_angle = secondary_limit_angle * 0.5f;
|
||||
damp_threshold = secondary_damp_threshold;
|
||||
} else {
|
||||
if (signbit(calculated_angle)) {
|
||||
limit_angle = secondary_negative_limit_angle;
|
||||
damp_threshold = secondary_negative_damp_threshold;
|
||||
} else {
|
||||
limit_angle = secondary_positive_limit_angle;
|
||||
damp_threshold = secondary_positive_damp_threshold;
|
||||
}
|
||||
}
|
||||
if (Math::abs(calculated_angle) > limit_angle) {
|
||||
is_within_limitations = false;
|
||||
}
|
||||
calculated_angle = remap_damped(0, limit_angle, damp_threshold, calculated_angle);
|
||||
}
|
||||
|
||||
current_result = current_result.rotated_local(get_vector_from_axis(secondary_rotation_axis), calculated_angle);
|
||||
|
||||
return current_result;
|
||||
}
|
||||
|
||||
void LookAtModifier3D::init_transition() {
|
||||
if (Math::is_zero_approx(duration)) {
|
||||
return;
|
||||
}
|
||||
from_q = prev_q;
|
||||
remaining = 1.0;
|
||||
}
|
||||
194
engine/scene/3d/look_at_modifier_3d.h
Normal file
194
engine/scene/3d/look_at_modifier_3d.h
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
/**************************************************************************/
|
||||
/* look_at_modifier_3d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef LOOK_AT_MODIFIER_3D_H
|
||||
#define LOOK_AT_MODIFIER_3D_H
|
||||
|
||||
#include "scene/3d/skeleton_modifier_3d.h"
|
||||
#include "scene/animation/tween.h"
|
||||
|
||||
class LookAtModifier3D : public SkeletonModifier3D {
|
||||
GDCLASS(LookAtModifier3D, SkeletonModifier3D);
|
||||
|
||||
public:
|
||||
enum OriginFrom {
|
||||
ORIGIN_FROM_SELF,
|
||||
ORIGIN_FROM_SPECIFIC_BONE,
|
||||
ORIGIN_FROM_EXTERNAL_NODE,
|
||||
};
|
||||
|
||||
private:
|
||||
String bone_name;
|
||||
int bone = -1;
|
||||
|
||||
Vector3 forward_vector;
|
||||
Vector3 forward_vector_nrm;
|
||||
BoneAxis forward_axis = BONE_AXIS_PLUS_Z;
|
||||
Vector3::Axis primary_rotation_axis = Vector3::AXIS_Y;
|
||||
Vector3::Axis secondary_rotation_axis = Vector3::AXIS_X;
|
||||
bool use_secondary_rotation = true;
|
||||
|
||||
OriginFrom origin_from = ORIGIN_FROM_SELF;
|
||||
String origin_bone_name;
|
||||
int origin_bone = -1;
|
||||
NodePath origin_external_node;
|
||||
|
||||
Vector3 origin_offset;
|
||||
float origin_safe_margin = 0.1;
|
||||
|
||||
NodePath target_node;
|
||||
|
||||
float duration = 0;
|
||||
Tween::TransitionType transition_type = Tween::TRANS_LINEAR;
|
||||
Tween::EaseType ease_type = Tween::EASE_IN;
|
||||
|
||||
bool use_angle_limitation = false;
|
||||
bool symmetry_limitation = true;
|
||||
|
||||
float primary_limit_angle = Math_TAU;
|
||||
float primary_damp_threshold = 1.0f;
|
||||
float primary_positive_limit_angle = Math_PI;
|
||||
float primary_positive_damp_threshold = 1.0f;
|
||||
float primary_negative_limit_angle = Math_PI;
|
||||
float primary_negative_damp_threshold = 1.0f;
|
||||
|
||||
float secondary_limit_angle = Math_TAU;
|
||||
float secondary_damp_threshold = 1.0f;
|
||||
float secondary_positive_limit_angle = Math_PI;
|
||||
float secondary_positive_damp_threshold = 1.0f;
|
||||
float secondary_negative_limit_angle = Math_PI;
|
||||
float secondary_negative_damp_threshold = 1.0f;
|
||||
|
||||
bool is_within_limitations = false;
|
||||
|
||||
// For time-based interpolation.
|
||||
Quaternion from_q;
|
||||
Quaternion prev_q;
|
||||
|
||||
float remaining = 0;
|
||||
float time_step = 1.0;
|
||||
|
||||
float remap_damped(float p_from, float p_to, float p_damp_threshold, float p_value) const;
|
||||
double get_bspline_y(const Vector2 &p_from, const Vector2 &p_control, const Vector2 &p_to, double p_x) const;
|
||||
bool is_intersecting_axis(const Vector3 &p_prev, const Vector3 &p_current, Vector3::Axis p_flipping_axis, Vector3::Axis p_check_axis, bool p_check_plane = false) const;
|
||||
|
||||
Transform3D look_at_with_axes(const Transform3D &p_rest);
|
||||
void init_transition();
|
||||
|
||||
protected:
|
||||
virtual PackedStringArray get_configuration_warnings() const override;
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
virtual void _process_modification() override;
|
||||
|
||||
public:
|
||||
void set_bone_name(const String &p_bone_name);
|
||||
String get_bone_name() const;
|
||||
void set_bone(int p_bone);
|
||||
int get_bone() const;
|
||||
|
||||
void set_forward_axis(BoneAxis p_axis);
|
||||
BoneAxis get_forward_axis() const;
|
||||
void set_primary_rotation_axis(Vector3::Axis p_axis);
|
||||
Vector3::Axis get_primary_rotation_axis() const;
|
||||
void set_use_secondary_rotation(bool p_enabled);
|
||||
bool is_using_secondary_rotation() const;
|
||||
|
||||
void set_origin_from(OriginFrom p_origin_from);
|
||||
OriginFrom get_origin_from() const;
|
||||
void set_origin_bone_name(const String &p_bone_name);
|
||||
String get_origin_bone_name() const;
|
||||
void set_origin_bone(int p_bone);
|
||||
int get_origin_bone() const;
|
||||
void set_origin_external_node(const NodePath &p_external_node);
|
||||
NodePath get_origin_external_node() const;
|
||||
|
||||
void set_origin_offset(const Vector3 &p_offset);
|
||||
Vector3 get_origin_offset() const;
|
||||
void set_origin_safe_margin(float p_margin);
|
||||
float get_origin_safe_margin() const;
|
||||
|
||||
void set_target_node(const NodePath &p_target_node);
|
||||
NodePath get_target_node() const;
|
||||
|
||||
void set_duration(float p_duration);
|
||||
float get_duration() const;
|
||||
void set_transition_type(Tween::TransitionType p_transition_type);
|
||||
Tween::TransitionType get_transition_type() const;
|
||||
void set_ease_type(Tween::EaseType p_ease_type);
|
||||
Tween::EaseType get_ease_type() const;
|
||||
|
||||
void set_use_angle_limitation(bool p_enabled);
|
||||
bool is_using_angle_limitation() const;
|
||||
void set_symmetry_limitation(bool p_enabled);
|
||||
bool is_limitation_symmetry() const;
|
||||
|
||||
void set_primary_limit_angle(float p_angle);
|
||||
float get_primary_limit_angle() const;
|
||||
void set_primary_damp_threshold(float p_power);
|
||||
float get_primary_damp_threshold() const;
|
||||
|
||||
void set_primary_positive_limit_angle(float p_angle);
|
||||
float get_primary_positive_limit_angle() const;
|
||||
void set_primary_positive_damp_threshold(float p_power);
|
||||
float get_primary_positive_damp_threshold() const;
|
||||
void set_primary_negative_limit_angle(float p_angle);
|
||||
float get_primary_negative_limit_angle() const;
|
||||
void set_primary_negative_damp_threshold(float p_power);
|
||||
float get_primary_negative_damp_threshold() const;
|
||||
|
||||
void set_secondary_limit_angle(float p_angle);
|
||||
float get_secondary_limit_angle() const;
|
||||
void set_secondary_damp_threshold(float p_power);
|
||||
float get_secondary_damp_threshold() const;
|
||||
|
||||
void set_secondary_positive_limit_angle(float p_angle);
|
||||
float get_secondary_positive_limit_angle() const;
|
||||
void set_secondary_positive_damp_threshold(float p_power);
|
||||
float get_secondary_positive_damp_threshold() const;
|
||||
void set_secondary_negative_limit_angle(float p_angle);
|
||||
float get_secondary_negative_limit_angle() const;
|
||||
void set_secondary_negative_damp_threshold(float p_power);
|
||||
float get_secondary_negative_damp_threshold() const;
|
||||
|
||||
float get_interpolation_remaining() const;
|
||||
bool is_interpolating() const;
|
||||
bool is_target_within_limitation() const;
|
||||
|
||||
static Vector3::Axis get_secondary_rotation_axis(BoneAxis p_forward_axis, Vector3::Axis p_primary_rotation_axis);
|
||||
static Vector3 get_basis_vector_from_bone_axis(const Basis &p_basis, BoneAxis p_axis);
|
||||
static Vector2 get_projection_vector(const Vector3 &p_vector, Vector3::Axis p_axis);
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(LookAtModifier3D::OriginFrom);
|
||||
|
||||
#endif // LOOK_AT_MODIFIER_3D_H
|
||||
|
|
@ -36,6 +36,13 @@
|
|||
#include "scene/resources/3d/concave_polygon_shape_3d.h"
|
||||
#include "scene/resources/3d/convex_polygon_shape_3d.h"
|
||||
|
||||
#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h"
|
||||
#include "scene/resources/navigation_mesh.h"
|
||||
#include "servers/navigation_server_3d.h"
|
||||
|
||||
Callable MeshInstance3D::_navmesh_source_geometry_parsing_callback;
|
||||
RID MeshInstance3D::_navmesh_source_geometry_parser;
|
||||
|
||||
bool MeshInstance3D::_set(const StringName &p_name, const Variant &p_value) {
|
||||
//this is not _too_ bad performance wise, really. it only arrives here if the property was not set anywhere else.
|
||||
//add to it that it's probably found on first call to _set anyway.
|
||||
|
|
@ -87,17 +94,9 @@ bool MeshInstance3D::_get(const StringName &p_name, Variant &r_ret) const {
|
|||
}
|
||||
|
||||
void MeshInstance3D::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
List<String> ls;
|
||||
for (const KeyValue<StringName, int> &E : blend_shape_properties) {
|
||||
ls.push_back(E.key);
|
||||
for (uint32_t i = 0; i < blend_shape_tracks.size(); i++) {
|
||||
p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("blend_shapes/%s", String(mesh->get_blend_shape_name(i))), PROPERTY_HINT_RANGE, "-1,1,0.00001"));
|
||||
}
|
||||
|
||||
ls.sort();
|
||||
|
||||
for (const String &E : ls) {
|
||||
p_list->push_back(PropertyInfo(Variant::FLOAT, E, PROPERTY_HINT_RANGE, "-1,1,0.00001"));
|
||||
}
|
||||
|
||||
if (mesh.is_valid()) {
|
||||
for (int i = 0; i < mesh->get_surface_count(); i++) {
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("%s/%d", PNAME("surface_material_override"), i), PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT));
|
||||
|
|
@ -142,6 +141,7 @@ int MeshInstance3D::get_blend_shape_count() const {
|
|||
}
|
||||
return mesh->get_blend_shape_count();
|
||||
}
|
||||
|
||||
int MeshInstance3D::find_blend_shape_by_name(const StringName &p_name) {
|
||||
if (mesh.is_null()) {
|
||||
return -1;
|
||||
|
|
@ -153,11 +153,13 @@ int MeshInstance3D::find_blend_shape_by_name(const StringName &p_name) {
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
float MeshInstance3D::get_blend_shape_value(int p_blend_shape) const {
|
||||
ERR_FAIL_COND_V(mesh.is_null(), 0.0);
|
||||
ERR_FAIL_INDEX_V(p_blend_shape, (int)blend_shape_tracks.size(), 0);
|
||||
return blend_shape_tracks[p_blend_shape];
|
||||
}
|
||||
|
||||
void MeshInstance3D::set_blend_shape_value(int p_blend_shape, float p_value) {
|
||||
ERR_FAIL_COND(mesh.is_null());
|
||||
ERR_FAIL_INDEX(p_blend_shape, (int)blend_shape_tracks.size());
|
||||
|
|
@ -221,7 +223,7 @@ NodePath MeshInstance3D::get_skeleton_path() {
|
|||
}
|
||||
|
||||
AABB MeshInstance3D::get_aabb() const {
|
||||
if (!mesh.is_null()) {
|
||||
if (mesh.is_valid()) {
|
||||
return mesh->get_aabb();
|
||||
}
|
||||
|
||||
|
|
@ -386,21 +388,24 @@ Ref<Material> MeshInstance3D::get_active_material(int p_surface) const {
|
|||
|
||||
void MeshInstance3D::_mesh_changed() {
|
||||
ERR_FAIL_COND(mesh.is_null());
|
||||
surface_override_materials.resize(mesh->get_surface_count());
|
||||
const int surface_count = mesh->get_surface_count();
|
||||
|
||||
surface_override_materials.resize(surface_count);
|
||||
|
||||
uint32_t initialize_bs_from = blend_shape_tracks.size();
|
||||
blend_shape_tracks.resize(mesh->get_blend_shape_count());
|
||||
|
||||
for (uint32_t i = 0; i < blend_shape_tracks.size(); i++) {
|
||||
blend_shape_properties["blend_shapes/" + String(mesh->get_blend_shape_name(i))] = i;
|
||||
if (i < initialize_bs_from) {
|
||||
set_blend_shape_value(i, blend_shape_tracks[i]);
|
||||
} else {
|
||||
set_blend_shape_value(i, 0);
|
||||
if (surface_count > 0) {
|
||||
for (uint32_t i = 0; i < blend_shape_tracks.size(); i++) {
|
||||
blend_shape_properties["blend_shapes/" + String(mesh->get_blend_shape_name(i))] = i;
|
||||
if (i < initialize_bs_from) {
|
||||
set_blend_shape_value(i, blend_shape_tracks[i]);
|
||||
} else {
|
||||
set_blend_shape_value(i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int surface_count = mesh->get_surface_count();
|
||||
for (int surface_index = 0; surface_index < surface_count; ++surface_index) {
|
||||
if (surface_override_materials[surface_index].is_valid()) {
|
||||
RS::get_singleton()->instance_set_surface_override_material(get_instance(), surface_index, surface_override_materials[surface_index]->get_rid());
|
||||
|
|
@ -415,7 +420,7 @@ MeshInstance3D *MeshInstance3D::create_debug_tangents_node() {
|
|||
Vector<Color> colors;
|
||||
|
||||
Ref<Mesh> m = get_mesh();
|
||||
if (!m.is_valid()) {
|
||||
if (m.is_null()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -517,12 +522,12 @@ bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_p
|
|||
|
||||
Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing) {
|
||||
Ref<ArrayMesh> source_mesh = get_mesh();
|
||||
ERR_FAIL_NULL_V_MSG(source_mesh, Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
|
||||
ERR_FAIL_COND_V_MSG(source_mesh.is_null(), Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
|
||||
|
||||
Ref<ArrayMesh> bake_mesh;
|
||||
|
||||
if (p_existing.is_valid()) {
|
||||
ERR_FAIL_NULL_V_MSG(p_existing, Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh.");
|
||||
ERR_FAIL_COND_V_MSG(p_existing.is_null(), Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh.");
|
||||
ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh.");
|
||||
|
||||
bake_mesh = p_existing;
|
||||
|
|
@ -671,6 +676,205 @@ Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayM
|
|||
return bake_mesh;
|
||||
}
|
||||
|
||||
Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_skeleton_pose(Ref<ArrayMesh> p_existing) {
|
||||
Ref<ArrayMesh> source_mesh = get_mesh();
|
||||
ERR_FAIL_COND_V_MSG(source_mesh.is_null(), Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
|
||||
|
||||
Ref<ArrayMesh> bake_mesh;
|
||||
|
||||
if (p_existing.is_valid()) {
|
||||
ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh.");
|
||||
|
||||
bake_mesh = p_existing;
|
||||
} else {
|
||||
bake_mesh.instantiate();
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(skin_ref.is_null(), Ref<ArrayMesh>(), "The source mesh must have a valid skin.");
|
||||
ERR_FAIL_COND_V_MSG(skin_internal.is_null(), Ref<ArrayMesh>(), "The source mesh must have a valid skin.");
|
||||
RID skeleton = skin_ref->get_skeleton();
|
||||
ERR_FAIL_COND_V_MSG(!skeleton.is_valid(), Ref<ArrayMesh>(), "The source mesh must have its skin registered with a valid skeleton.");
|
||||
|
||||
const int bone_count = RenderingServer::get_singleton()->skeleton_get_bone_count(skeleton);
|
||||
ERR_FAIL_COND_V(bone_count <= 0, Ref<ArrayMesh>());
|
||||
ERR_FAIL_COND_V(bone_count < skin_internal->get_bind_count(), Ref<ArrayMesh>());
|
||||
|
||||
LocalVector<Transform3D> bone_transforms;
|
||||
bone_transforms.resize(bone_count);
|
||||
for (int bone_index = 0; bone_index < bone_count; bone_index++) {
|
||||
bone_transforms[bone_index] = RenderingServer::get_singleton()->skeleton_bone_get_transform(skeleton, bone_index);
|
||||
}
|
||||
|
||||
bake_mesh->clear_surfaces();
|
||||
|
||||
int mesh_surface_count = source_mesh->get_surface_count();
|
||||
|
||||
for (int surface_index = 0; surface_index < mesh_surface_count; surface_index++) {
|
||||
ERR_CONTINUE(source_mesh->surface_get_primitive_type(surface_index) != Mesh::PRIMITIVE_TRIANGLES);
|
||||
|
||||
uint32_t surface_format = source_mesh->surface_get_format(surface_index);
|
||||
|
||||
ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_VERTEX));
|
||||
ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_BONES));
|
||||
ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_WEIGHTS));
|
||||
|
||||
unsigned int bones_per_vertex = surface_format & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS ? 8 : 4;
|
||||
|
||||
surface_format &= ~Mesh::ARRAY_FORMAT_BONES;
|
||||
surface_format &= ~Mesh::ARRAY_FORMAT_WEIGHTS;
|
||||
|
||||
const Array &source_mesh_arrays = source_mesh->surface_get_arrays(surface_index);
|
||||
|
||||
ERR_FAIL_COND_V(source_mesh_arrays.size() != RS::ARRAY_MAX, Ref<ArrayMesh>());
|
||||
|
||||
const Vector<Vector3> &source_mesh_vertex_array = source_mesh_arrays[Mesh::ARRAY_VERTEX];
|
||||
const Vector<Vector3> &source_mesh_normal_array = source_mesh_arrays[Mesh::ARRAY_NORMAL];
|
||||
const Vector<float> &source_mesh_tangent_array = source_mesh_arrays[Mesh::ARRAY_TANGENT];
|
||||
const Vector<int> &source_mesh_bones_array = source_mesh_arrays[Mesh::ARRAY_BONES];
|
||||
const Vector<float> &source_mesh_weights_array = source_mesh_arrays[Mesh::ARRAY_WEIGHTS];
|
||||
|
||||
unsigned int vertex_count = source_mesh_vertex_array.size();
|
||||
int expected_bone_array_size = vertex_count * bones_per_vertex;
|
||||
ERR_CONTINUE(source_mesh_bones_array.size() != expected_bone_array_size);
|
||||
ERR_CONTINUE(source_mesh_weights_array.size() != expected_bone_array_size);
|
||||
|
||||
Array new_mesh_arrays;
|
||||
new_mesh_arrays.resize(Mesh::ARRAY_MAX);
|
||||
for (int i = 0; i < source_mesh_arrays.size(); i++) {
|
||||
if (i == Mesh::ARRAY_VERTEX || i == Mesh::ARRAY_NORMAL || i == Mesh::ARRAY_TANGENT || i == Mesh::ARRAY_BONES || i == Mesh::ARRAY_WEIGHTS) {
|
||||
continue;
|
||||
}
|
||||
new_mesh_arrays[i] = source_mesh_arrays[i];
|
||||
}
|
||||
|
||||
bool use_normal_array = source_mesh_normal_array.size() == source_mesh_vertex_array.size();
|
||||
bool use_tangent_array = source_mesh_tangent_array.size() / 4 == source_mesh_vertex_array.size();
|
||||
|
||||
Vector<Vector3> lerped_vertex_array = source_mesh_vertex_array;
|
||||
Vector<Vector3> lerped_normal_array = source_mesh_normal_array;
|
||||
Vector<float> lerped_tangent_array = source_mesh_tangent_array;
|
||||
|
||||
const Vector3 *source_vertices_ptr = source_mesh_vertex_array.ptr();
|
||||
const Vector3 *source_normals_ptr = source_mesh_normal_array.ptr();
|
||||
const float *source_tangents_ptr = source_mesh_tangent_array.ptr();
|
||||
const int *source_bones_ptr = source_mesh_bones_array.ptr();
|
||||
const float *source_weights_ptr = source_mesh_weights_array.ptr();
|
||||
|
||||
Vector3 *lerped_vertices_ptrw = lerped_vertex_array.ptrw();
|
||||
Vector3 *lerped_normals_ptrw = lerped_normal_array.ptrw();
|
||||
float *lerped_tangents_ptrw = lerped_tangent_array.ptrw();
|
||||
|
||||
for (unsigned int vertex_index = 0; vertex_index < vertex_count; vertex_index++) {
|
||||
Vector3 lerped_vertex;
|
||||
Vector3 lerped_normal;
|
||||
Vector3 lerped_tangent;
|
||||
|
||||
const Vector3 &source_vertex = source_vertices_ptr[vertex_index];
|
||||
|
||||
Vector3 source_normal;
|
||||
if (use_normal_array) {
|
||||
source_normal = source_normals_ptr[vertex_index];
|
||||
}
|
||||
|
||||
int tangent_index = vertex_index * 4;
|
||||
Vector4 source_tangent;
|
||||
Vector3 source_tangent_vec3;
|
||||
if (use_tangent_array) {
|
||||
source_tangent = Vector4(
|
||||
source_tangents_ptr[tangent_index],
|
||||
source_tangents_ptr[tangent_index + 1],
|
||||
source_tangents_ptr[tangent_index + 2],
|
||||
source_tangents_ptr[tangent_index + 3]);
|
||||
|
||||
DEV_ASSERT(source_tangent.w == 1.0 || source_tangent.w == -1.0);
|
||||
|
||||
source_tangent_vec3 = Vector3(source_tangent.x, source_tangent.y, source_tangent.z);
|
||||
}
|
||||
|
||||
for (unsigned int weight_index = 0; weight_index < bones_per_vertex; weight_index++) {
|
||||
float bone_weight = source_weights_ptr[vertex_index * bones_per_vertex + weight_index];
|
||||
if (bone_weight < FLT_EPSILON) {
|
||||
continue;
|
||||
}
|
||||
int vertex_bone_index = source_bones_ptr[vertex_index * bones_per_vertex + weight_index];
|
||||
const Transform3D &bone_transform = bone_transforms[vertex_bone_index];
|
||||
const Basis bone_basis = bone_transform.basis.orthonormalized();
|
||||
|
||||
ERR_FAIL_INDEX_V(vertex_bone_index, static_cast<int>(bone_transforms.size()), Ref<ArrayMesh>());
|
||||
|
||||
lerped_vertex += source_vertex.lerp(bone_transform.xform(source_vertex), bone_weight) - source_vertex;
|
||||
;
|
||||
|
||||
if (use_normal_array) {
|
||||
lerped_normal += source_normal.lerp(bone_basis.xform(source_normal), bone_weight) - source_normal;
|
||||
}
|
||||
|
||||
if (use_tangent_array) {
|
||||
lerped_tangent += source_tangent_vec3.lerp(bone_basis.xform(source_tangent_vec3), bone_weight) - source_tangent_vec3;
|
||||
}
|
||||
}
|
||||
|
||||
lerped_vertices_ptrw[vertex_index] += lerped_vertex;
|
||||
|
||||
if (use_normal_array) {
|
||||
lerped_normals_ptrw[vertex_index] = (source_normal + lerped_normal).normalized();
|
||||
}
|
||||
|
||||
if (use_tangent_array) {
|
||||
lerped_tangent = (source_tangent_vec3 + lerped_tangent).normalized();
|
||||
lerped_tangents_ptrw[tangent_index] = lerped_tangent.x;
|
||||
lerped_tangents_ptrw[tangent_index + 1] = lerped_tangent.y;
|
||||
lerped_tangents_ptrw[tangent_index + 2] = lerped_tangent.z;
|
||||
}
|
||||
}
|
||||
|
||||
new_mesh_arrays[Mesh::ARRAY_VERTEX] = lerped_vertex_array;
|
||||
if (use_normal_array) {
|
||||
new_mesh_arrays[Mesh::ARRAY_NORMAL] = lerped_normal_array;
|
||||
}
|
||||
if (use_tangent_array) {
|
||||
new_mesh_arrays[Mesh::ARRAY_TANGENT] = lerped_tangent_array;
|
||||
}
|
||||
|
||||
bake_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, new_mesh_arrays, Array(), Dictionary(), surface_format);
|
||||
}
|
||||
|
||||
return bake_mesh;
|
||||
}
|
||||
|
||||
Ref<TriangleMesh> MeshInstance3D::generate_triangle_mesh() const {
|
||||
if (mesh.is_valid()) {
|
||||
return mesh->generate_triangle_mesh();
|
||||
}
|
||||
return Ref<TriangleMesh>();
|
||||
}
|
||||
|
||||
void MeshInstance3D::navmesh_parse_init() {
|
||||
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
|
||||
if (!_navmesh_source_geometry_parser.is_valid()) {
|
||||
_navmesh_source_geometry_parsing_callback = callable_mp_static(&MeshInstance3D::navmesh_parse_source_geometry);
|
||||
_navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create();
|
||||
NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshInstance3D::navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
|
||||
MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_node);
|
||||
|
||||
if (mesh_instance == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
|
||||
|
||||
if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
|
||||
Ref<Mesh> mesh = mesh_instance->get_mesh();
|
||||
if (mesh.is_valid()) {
|
||||
p_source_geometry_data->add_mesh(mesh, mesh_instance->get_global_transform());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MeshInstance3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance3D::set_mesh);
|
||||
ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance3D::get_mesh);
|
||||
|
|
@ -700,6 +904,7 @@ void MeshInstance3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("bake_mesh_from_current_blend_shape_mix", "existing"), &MeshInstance3D::bake_mesh_from_current_blend_shape_mix, DEFVAL(Ref<ArrayMesh>()));
|
||||
ClassDB::bind_method(D_METHOD("bake_mesh_from_current_skeleton_pose", "existing"), &MeshInstance3D::bake_mesh_from_current_skeleton_pose, DEFVAL(Ref<ArrayMesh>()));
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
|
||||
ADD_GROUP("Skeleton", "");
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@
|
|||
|
||||
#include "core/templates/local_vector.h"
|
||||
#include "scene/3d/visual_instance_3d.h"
|
||||
|
||||
class NavigationMesh;
|
||||
class NavigationMeshSourceGeometryData3D;
|
||||
class Skin;
|
||||
class SkinReference;
|
||||
|
||||
|
|
@ -102,6 +105,17 @@ public:
|
|||
virtual AABB get_aabb() const override;
|
||||
|
||||
Ref<ArrayMesh> bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>());
|
||||
Ref<ArrayMesh> bake_mesh_from_current_skeleton_pose(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>());
|
||||
|
||||
virtual Ref<TriangleMesh> generate_triangle_mesh() const override;
|
||||
|
||||
private:
|
||||
static Callable _navmesh_source_geometry_parsing_callback;
|
||||
static RID _navmesh_source_geometry_parser;
|
||||
|
||||
public:
|
||||
static void navmesh_parse_init();
|
||||
static void navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
|
||||
|
||||
MeshInstance3D();
|
||||
~MeshInstance3D();
|
||||
|
|
|
|||
|
|
@ -30,16 +30,42 @@
|
|||
|
||||
#include "multimesh_instance_3d.h"
|
||||
|
||||
#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h"
|
||||
#include "scene/resources/navigation_mesh.h"
|
||||
#include "servers/navigation_server_3d.h"
|
||||
|
||||
Callable MultiMeshInstance3D::_navmesh_source_geometry_parsing_callback;
|
||||
RID MultiMeshInstance3D::_navmesh_source_geometry_parser;
|
||||
|
||||
void MultiMeshInstance3D::_refresh_interpolated() {
|
||||
if (is_inside_tree() && multimesh.is_valid()) {
|
||||
bool interpolated = is_physics_interpolated_and_enabled();
|
||||
multimesh->set_physics_interpolated(interpolated);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiMeshInstance3D::_physics_interpolated_changed() {
|
||||
VisualInstance3D::_physics_interpolated_changed();
|
||||
_refresh_interpolated();
|
||||
}
|
||||
|
||||
void MultiMeshInstance3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_multimesh", "multimesh"), &MultiMeshInstance3D::set_multimesh);
|
||||
ClassDB::bind_method(D_METHOD("get_multimesh"), &MultiMeshInstance3D::get_multimesh);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multimesh", PROPERTY_HINT_RESOURCE_TYPE, "MultiMesh"), "set_multimesh", "get_multimesh");
|
||||
}
|
||||
|
||||
void MultiMeshInstance3D::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_ENTER_TREE) {
|
||||
_refresh_interpolated();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiMeshInstance3D::set_multimesh(const Ref<MultiMesh> &p_multimesh) {
|
||||
multimesh = p_multimesh;
|
||||
if (multimesh.is_valid()) {
|
||||
set_base(multimesh->get_rid());
|
||||
_refresh_interpolated();
|
||||
} else {
|
||||
set_base(RID());
|
||||
}
|
||||
|
|
@ -77,6 +103,41 @@ AABB MultiMeshInstance3D::get_aabb() const {
|
|||
}
|
||||
}
|
||||
|
||||
void MultiMeshInstance3D::navmesh_parse_init() {
|
||||
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
|
||||
if (!_navmesh_source_geometry_parser.is_valid()) {
|
||||
_navmesh_source_geometry_parsing_callback = callable_mp_static(&MultiMeshInstance3D::navmesh_parse_source_geometry);
|
||||
_navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create();
|
||||
NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiMeshInstance3D::navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
|
||||
MultiMeshInstance3D *multimesh_instance = Object::cast_to<MultiMeshInstance3D>(p_node);
|
||||
|
||||
if (multimesh_instance == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
|
||||
|
||||
if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
|
||||
Ref<MultiMesh> multimesh = multimesh_instance->get_multimesh();
|
||||
if (multimesh.is_valid()) {
|
||||
Ref<Mesh> mesh = multimesh->get_mesh();
|
||||
if (mesh.is_valid()) {
|
||||
int n = multimesh->get_visible_instance_count();
|
||||
if (n == -1) {
|
||||
n = multimesh->get_instance_count();
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
p_source_geometry_data->add_mesh(mesh, multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MultiMeshInstance3D::MultiMeshInstance3D() {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,14 +34,20 @@
|
|||
#include "scene/3d/visual_instance_3d.h"
|
||||
#include "scene/resources/multimesh.h"
|
||||
|
||||
class NavigationMesh;
|
||||
class NavigationMeshSourceGeometryData3D;
|
||||
|
||||
class MultiMeshInstance3D : public GeometryInstance3D {
|
||||
GDCLASS(MultiMeshInstance3D, GeometryInstance3D);
|
||||
|
||||
Ref<MultiMesh> multimesh;
|
||||
|
||||
void _refresh_interpolated();
|
||||
|
||||
protected:
|
||||
virtual void _physics_interpolated_changed() override;
|
||||
static void _bind_methods();
|
||||
// bind helpers
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void set_multimesh(const Ref<MultiMesh> &p_multimesh);
|
||||
|
|
@ -51,6 +57,14 @@ public:
|
|||
|
||||
virtual AABB get_aabb() const override;
|
||||
|
||||
private:
|
||||
static Callable _navmesh_source_geometry_parsing_callback;
|
||||
static RID _navmesh_source_geometry_parser;
|
||||
|
||||
public:
|
||||
static void navmesh_parse_init();
|
||||
static void navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
|
||||
|
||||
MultiMeshInstance3D();
|
||||
~MultiMeshInstance3D();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ void NavigationAgent3D::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_max_distance", PROPERTY_HINT_RANGE, "0.01,100,0.1,or_greater,suffix:m"), "set_path_max_distance", "get_path_max_distance");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "pathfinding_algorithm", PROPERTY_HINT_ENUM, "AStar"), "set_pathfinding_algorithm", "get_pathfinding_algorithm");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "path_postprocessing", PROPERTY_HINT_ENUM, "Corridorfunnel,Edgecentered"), "set_path_postprocessing", "get_path_postprocessing");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "path_postprocessing", PROPERTY_HINT_ENUM, "Corridorfunnel,Edgecentered,None"), "set_path_postprocessing", "get_path_postprocessing");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "path_metadata_flags", PROPERTY_HINT_FLAGS, "Include Types,Include RIDs,Include Owners"), "set_path_metadata_flags", "get_path_metadata_flags");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "simplify_path"), "set_simplify_path", "get_simplify_path");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "simplify_epsilon", PROPERTY_HINT_RANGE, "0.0,10.0,0.001,or_greater,suffix:m"), "set_simplify_epsilon", "get_simplify_epsilon");
|
||||
|
|
@ -272,12 +272,20 @@ void NavigationAgent3D::_notification(int p_what) {
|
|||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_SUSPENDED:
|
||||
case NOTIFICATION_PAUSED: {
|
||||
if (agent_parent) {
|
||||
NavigationServer3D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process());
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_UNSUSPENDED: {
|
||||
if (get_tree()->is_paused()) {
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
|
||||
case NOTIFICATION_UNPAUSED: {
|
||||
if (agent_parent) {
|
||||
NavigationServer3D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process());
|
||||
|
|
@ -1073,8 +1081,8 @@ void NavigationAgent3D::_update_debug_path() {
|
|||
debug_path_instance = RenderingServer::get_singleton()->instance_create();
|
||||
}
|
||||
|
||||
if (!debug_path_mesh.is_valid()) {
|
||||
debug_path_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
||||
if (debug_path_mesh.is_null()) {
|
||||
debug_path_mesh.instantiate();
|
||||
}
|
||||
|
||||
debug_path_mesh->clear_surfaces();
|
||||
|
|
@ -1108,7 +1116,7 @@ void NavigationAgent3D::_update_debug_path() {
|
|||
|
||||
Ref<StandardMaterial3D> debug_agent_path_line_material = NavigationServer3D::get_singleton()->get_debug_navigation_agent_path_line_material();
|
||||
if (debug_use_custom) {
|
||||
if (!debug_agent_path_line_custom_material.is_valid()) {
|
||||
if (debug_agent_path_line_custom_material.is_null()) {
|
||||
debug_agent_path_line_custom_material = debug_agent_path_line_material->duplicate();
|
||||
}
|
||||
debug_agent_path_line_custom_material->set_albedo(debug_path_custom_color);
|
||||
|
|
@ -1132,7 +1140,7 @@ void NavigationAgent3D::_update_debug_path() {
|
|||
|
||||
Ref<StandardMaterial3D> debug_agent_path_point_material = NavigationServer3D::get_singleton()->get_debug_navigation_agent_path_point_material();
|
||||
if (debug_use_custom) {
|
||||
if (!debug_agent_path_point_custom_material.is_valid()) {
|
||||
if (debug_agent_path_point_custom_material.is_null()) {
|
||||
debug_agent_path_point_custom_material = debug_agent_path_point_material->duplicate();
|
||||
}
|
||||
debug_agent_path_point_custom_material->set_albedo(debug_path_custom_color);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
#include "navigation_link_3d.h"
|
||||
|
||||
#include "mesh_instance_3d.h"
|
||||
#include "servers/navigation_server_3d.h"
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
|
@ -56,8 +55,8 @@ void NavigationLink3D::_update_debug_mesh() {
|
|||
debug_instance = RenderingServer::get_singleton()->instance_create();
|
||||
}
|
||||
|
||||
if (!debug_mesh.is_valid()) {
|
||||
debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
||||
if (debug_mesh.is_null()) {
|
||||
debug_mesh.instantiate();
|
||||
}
|
||||
|
||||
RID nav_map = get_world_3d()->get_navigation_map();
|
||||
|
|
@ -123,6 +122,34 @@ void NavigationLink3D::_update_debug_mesh() {
|
|||
}
|
||||
}
|
||||
|
||||
const Vector3 link_segment = end_position - start_position;
|
||||
const Vector3 up = Vector3(0.0, 1.0, 0.0);
|
||||
const float arror_len = 0.5;
|
||||
|
||||
{
|
||||
Vector3 anchor = start_position + (link_segment * 0.75);
|
||||
Vector3 direction = start_position.direction_to(end_position);
|
||||
Vector3 arrow_dir = direction.cross(up);
|
||||
lines.push_back(anchor);
|
||||
lines.push_back(anchor + (arrow_dir - direction) * arror_len);
|
||||
|
||||
arrow_dir = -direction.cross(up);
|
||||
lines.push_back(anchor);
|
||||
lines.push_back(anchor + (arrow_dir - direction) * arror_len);
|
||||
}
|
||||
|
||||
if (is_bidirectional()) {
|
||||
Vector3 anchor = start_position + (link_segment * 0.25);
|
||||
Vector3 direction = end_position.direction_to(start_position);
|
||||
Vector3 arrow_dir = direction.cross(up);
|
||||
lines.push_back(anchor);
|
||||
lines.push_back(anchor + (arrow_dir - direction) * arror_len);
|
||||
|
||||
arrow_dir = -direction.cross(up);
|
||||
lines.push_back(anchor);
|
||||
lines.push_back(anchor + (arrow_dir - direction) * arror_len);
|
||||
}
|
||||
|
||||
Array mesh_array;
|
||||
mesh_array.resize(Mesh::ARRAY_MAX);
|
||||
mesh_array[Mesh::ARRAY_VERTEX] = lines;
|
||||
|
|
@ -152,6 +179,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 +247,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 +256,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 +322,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;
|
||||
|
|
@ -328,6 +349,12 @@ void NavigationLink3D::set_bidirectional(bool p_bidirectional) {
|
|||
bidirectional = p_bidirectional;
|
||||
|
||||
NavigationServer3D::get_singleton()->link_set_bidirectional(link, bidirectional);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
_update_debug_mesh();
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
void NavigationLink3D::set_navigation_layers(uint32_t p_navigation_layers) {
|
||||
|
|
@ -459,7 +486,7 @@ void NavigationLink3D::set_travel_cost(real_t p_travel_cost) {
|
|||
}
|
||||
|
||||
PackedStringArray NavigationLink3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = Node3D::get_configuration_warnings();
|
||||
|
||||
if (start_position.is_equal_approx(end_position)) {
|
||||
warnings.push_back(RTR("NavigationLink3D start position should be different than the end position to be useful."));
|
||||
|
|
@ -467,3 +494,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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -31,8 +31,13 @@
|
|||
#include "navigation_obstacle_3d.h"
|
||||
|
||||
#include "core/math/geometry_2d.h"
|
||||
#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h"
|
||||
#include "scene/resources/navigation_mesh.h"
|
||||
#include "servers/navigation_server_3d.h"
|
||||
|
||||
Callable NavigationObstacle3D::_navmesh_source_geometry_parsing_callback;
|
||||
RID NavigationObstacle3D::_navmesh_source_geometry_parser;
|
||||
|
||||
void NavigationObstacle3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_rid"), &NavigationObstacle3D::get_rid);
|
||||
|
||||
|
|
@ -92,33 +97,30 @@ void NavigationObstacle3D::_notification(int p_what) {
|
|||
} else {
|
||||
_update_map(RID());
|
||||
}
|
||||
previous_transform = get_global_transform();
|
||||
// need to trigger map controlled agent assignment somehow for the fake_agent since obstacles use no callback like regular agents
|
||||
NavigationServer3D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
|
||||
_update_position(get_global_transform().origin);
|
||||
_update_transform();
|
||||
set_physics_process_internal(true);
|
||||
#ifdef DEBUG_ENABLED
|
||||
if ((NavigationServer3D::get_singleton()->get_debug_avoidance_enabled()) &&
|
||||
(NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius())) {
|
||||
_update_fake_agent_radius_debug();
|
||||
_update_static_obstacle_debug();
|
||||
}
|
||||
_update_debug();
|
||||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
case NOTIFICATION_TRANSFORM_CHANGED: {
|
||||
update_gizmos();
|
||||
} break;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
set_physics_process_internal(false);
|
||||
_update_map(RID());
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (fake_agent_radius_debug_instance.is_valid()) {
|
||||
RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, false);
|
||||
}
|
||||
if (static_obstacle_debug_instance.is_valid()) {
|
||||
RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, false);
|
||||
}
|
||||
_clear_debug();
|
||||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_SUSPENDED:
|
||||
case NOTIFICATION_PAUSED: {
|
||||
if (!can_process()) {
|
||||
map_before_pause = map_current;
|
||||
|
|
@ -130,6 +132,13 @@ void NavigationObstacle3D::_notification(int p_what) {
|
|||
NavigationServer3D::get_singleton()->obstacle_set_paused(obstacle, !can_process());
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_UNSUSPENDED: {
|
||||
if (get_tree()->is_paused()) {
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
|
||||
case NOTIFICATION_UNPAUSED: {
|
||||
if (!can_process()) {
|
||||
map_before_pause = map_current;
|
||||
|
|
@ -143,20 +152,13 @@ void NavigationObstacle3D::_notification(int p_what) {
|
|||
|
||||
#ifdef DEBUG_ENABLED
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
if (is_inside_tree()) {
|
||||
if (fake_agent_radius_debug_instance.is_valid()) {
|
||||
RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, is_visible_in_tree());
|
||||
}
|
||||
if (static_obstacle_debug_instance.is_valid()) {
|
||||
RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, is_visible_in_tree());
|
||||
}
|
||||
}
|
||||
_update_debug();
|
||||
} break;
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
|
||||
if (is_inside_tree()) {
|
||||
_update_position(get_global_transform().origin);
|
||||
_update_transform();
|
||||
|
||||
if (velocity_submitted) {
|
||||
velocity_submitted = false;
|
||||
|
|
@ -167,15 +169,23 @@ void NavigationObstacle3D::_notification(int p_what) {
|
|||
previous_velocity = velocity;
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (fake_agent_radius_debug_instance.is_valid() && radius > 0.0) {
|
||||
Transform3D debug_transform;
|
||||
debug_transform.origin = get_global_position();
|
||||
RS::get_singleton()->instance_set_transform(fake_agent_radius_debug_instance, debug_transform);
|
||||
if (fake_agent_radius_debug_instance_rid.is_valid() && radius > 0.0) {
|
||||
// Prevent non-positive scaling.
|
||||
const Vector3 safe_scale = get_global_basis().get_scale().abs().maxf(0.001);
|
||||
// Agent radius is a scalar value and does not support non-uniform scaling, choose the largest axis.
|
||||
const float scaling_max_value = safe_scale[safe_scale.max_axis_index()];
|
||||
const Vector3 uniform_max_scale = Vector3(scaling_max_value, scaling_max_value, scaling_max_value);
|
||||
const Transform3D debug_transform = Transform3D(Basis().scaled(uniform_max_scale), get_global_position());
|
||||
|
||||
RS::get_singleton()->instance_set_transform(fake_agent_radius_debug_instance_rid, debug_transform);
|
||||
}
|
||||
if (static_obstacle_debug_instance.is_valid() && get_vertices().size() > 0) {
|
||||
Transform3D debug_transform;
|
||||
debug_transform.origin = get_global_position();
|
||||
RS::get_singleton()->instance_set_transform(static_obstacle_debug_instance, debug_transform);
|
||||
if (static_obstacle_debug_instance_rid.is_valid() && get_vertices().size() > 0) {
|
||||
// Prevent non-positive scaling.
|
||||
const Vector3 safe_scale = get_global_basis().get_scale().abs().maxf(0.001);
|
||||
// Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account.
|
||||
const Transform3D debug_transform = Transform3D(Basis().scaled(safe_scale).rotated(Vector3(0.0, 1.0, 0.0), get_global_rotation().y), get_global_position());
|
||||
|
||||
RS::get_singleton()->instance_set_transform(static_obstacle_debug_instance_rid, debug_transform);
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
|
@ -184,53 +194,96 @@ void NavigationObstacle3D::_notification(int p_what) {
|
|||
}
|
||||
|
||||
NavigationObstacle3D::NavigationObstacle3D() {
|
||||
obstacle = NavigationServer3D::get_singleton()->obstacle_create();
|
||||
NavigationServer3D *ns3d = NavigationServer3D::get_singleton();
|
||||
|
||||
NavigationServer3D::get_singleton()->obstacle_set_height(obstacle, height);
|
||||
NavigationServer3D::get_singleton()->obstacle_set_radius(obstacle, radius);
|
||||
NavigationServer3D::get_singleton()->obstacle_set_vertices(obstacle, vertices);
|
||||
NavigationServer3D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
|
||||
NavigationServer3D::get_singleton()->obstacle_set_use_3d_avoidance(obstacle, use_3d_avoidance);
|
||||
NavigationServer3D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
|
||||
obstacle = ns3d->obstacle_create();
|
||||
|
||||
ns3d->obstacle_set_height(obstacle, height);
|
||||
ns3d->obstacle_set_radius(obstacle, radius);
|
||||
ns3d->obstacle_set_vertices(obstacle, vertices);
|
||||
ns3d->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
|
||||
ns3d->obstacle_set_use_3d_avoidance(obstacle, use_3d_avoidance);
|
||||
ns3d->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
NavigationServer3D::get_singleton()->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug));
|
||||
NavigationServer3D::get_singleton()->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_static_obstacle_debug));
|
||||
RenderingServer *rs = RenderingServer::get_singleton();
|
||||
|
||||
fake_agent_radius_debug_mesh_rid = rs->mesh_create();
|
||||
static_obstacle_debug_mesh_rid = rs->mesh_create();
|
||||
|
||||
fake_agent_radius_debug_instance_rid = rs->instance_create();
|
||||
static_obstacle_debug_instance_rid = rs->instance_create();
|
||||
|
||||
rs->instance_set_base(fake_agent_radius_debug_instance_rid, fake_agent_radius_debug_mesh_rid);
|
||||
rs->instance_set_base(static_obstacle_debug_instance_rid, static_obstacle_debug_mesh_rid);
|
||||
|
||||
ns3d->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug));
|
||||
ns3d->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_static_obstacle_debug));
|
||||
_update_fake_agent_radius_debug();
|
||||
_update_static_obstacle_debug();
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
set_notify_transform(true);
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
NavigationObstacle3D::~NavigationObstacle3D() {
|
||||
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
|
||||
NavigationServer3D *ns3d = NavigationServer3D::get_singleton();
|
||||
ERR_FAIL_NULL(ns3d);
|
||||
|
||||
NavigationServer3D::get_singleton()->free(obstacle);
|
||||
ns3d->free(obstacle);
|
||||
obstacle = RID();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
NavigationServer3D::get_singleton()->disconnect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug));
|
||||
NavigationServer3D::get_singleton()->disconnect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_static_obstacle_debug));
|
||||
if (fake_agent_radius_debug_instance.is_valid()) {
|
||||
RenderingServer::get_singleton()->free(fake_agent_radius_debug_instance);
|
||||
}
|
||||
if (fake_agent_radius_debug_mesh.is_valid()) {
|
||||
RenderingServer::get_singleton()->free(fake_agent_radius_debug_mesh->get_rid());
|
||||
}
|
||||
ns3d->disconnect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug));
|
||||
ns3d->disconnect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_static_obstacle_debug));
|
||||
|
||||
if (static_obstacle_debug_instance.is_valid()) {
|
||||
RenderingServer::get_singleton()->free(static_obstacle_debug_instance);
|
||||
RenderingServer *rs = RenderingServer::get_singleton();
|
||||
ERR_FAIL_NULL(rs);
|
||||
if (fake_agent_radius_debug_instance_rid.is_valid()) {
|
||||
rs->free(fake_agent_radius_debug_instance_rid);
|
||||
fake_agent_radius_debug_instance_rid = RID();
|
||||
}
|
||||
if (static_obstacle_debug_mesh.is_valid()) {
|
||||
RenderingServer::get_singleton()->free(static_obstacle_debug_mesh->get_rid());
|
||||
if (fake_agent_radius_debug_mesh_rid.is_valid()) {
|
||||
rs->free(fake_agent_radius_debug_mesh_rid);
|
||||
fake_agent_radius_debug_mesh_rid = RID();
|
||||
}
|
||||
if (static_obstacle_debug_instance_rid.is_valid()) {
|
||||
rs->free(static_obstacle_debug_instance_rid);
|
||||
static_obstacle_debug_instance_rid = RID();
|
||||
}
|
||||
if (static_obstacle_debug_mesh_rid.is_valid()) {
|
||||
rs->free(static_obstacle_debug_mesh_rid);
|
||||
static_obstacle_debug_mesh_rid = RID();
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void NavigationObstacle3D::set_vertices(const Vector<Vector3> &p_vertices) {
|
||||
vertices = p_vertices;
|
||||
NavigationServer3D::get_singleton()->obstacle_set_vertices(obstacle, vertices);
|
||||
|
||||
Vector<Vector2> vertices_2d;
|
||||
vertices_2d.resize(vertices.size());
|
||||
|
||||
const Vector3 *vertices_ptr = vertices.ptr();
|
||||
Vector2 *vertices_2d_ptrw = vertices_2d.ptrw();
|
||||
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
vertices_2d_ptrw[i] = Vector2(vertices_ptr[i].x, vertices_ptr[i].z);
|
||||
}
|
||||
|
||||
vertices_are_clockwise = !Geometry2D::is_polygon_clockwise(vertices_2d); // Geometry2D is inverted.
|
||||
vertices_are_valid = !Geometry2D::triangulate_polygon(vertices_2d).is_empty();
|
||||
|
||||
const Basis basis = is_inside_tree() ? get_global_basis() : get_basis();
|
||||
const float rotation_y = is_inside_tree() ? get_global_rotation().y : get_rotation().y;
|
||||
const Vector3 safe_scale = basis.get_scale().abs().maxf(0.001);
|
||||
const Transform3D safe_transform = Transform3D(Basis().scaled(safe_scale).rotated(Vector3(0.0, 1.0, 0.0), rotation_y), Vector3());
|
||||
NavigationServer3D::get_singleton()->obstacle_set_vertices(obstacle, safe_transform.xform(vertices));
|
||||
#ifdef DEBUG_ENABLED
|
||||
_update_static_obstacle_debug();
|
||||
update_gizmos();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
|
|
@ -258,10 +311,14 @@ void NavigationObstacle3D::set_radius(real_t p_radius) {
|
|||
}
|
||||
|
||||
radius = p_radius;
|
||||
NavigationServer3D::get_singleton()->obstacle_set_radius(obstacle, radius);
|
||||
|
||||
// Prevent non-positive or non-uniform scaling of dynamic obstacle radius.
|
||||
const Vector3 safe_scale = (is_inside_tree() ? get_global_basis() : get_basis()).get_scale().abs().maxf(0.001);
|
||||
NavigationServer3D::get_singleton()->obstacle_set_radius(obstacle, safe_scale[safe_scale.max_axis_index()] * radius);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
_update_fake_agent_radius_debug();
|
||||
update_gizmos();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
|
|
@ -272,10 +329,12 @@ void NavigationObstacle3D::set_height(real_t p_height) {
|
|||
}
|
||||
|
||||
height = p_height;
|
||||
NavigationServer3D::get_singleton()->obstacle_set_height(obstacle, height);
|
||||
const float scale_factor = MAX(Math::abs((is_inside_tree() ? get_global_basis() : get_basis()).get_scale().y), 0.001);
|
||||
NavigationServer3D::get_singleton()->obstacle_set_height(obstacle, scale_factor * height);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
_update_static_obstacle_debug();
|
||||
update_gizmos();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
|
|
@ -346,6 +405,97 @@ bool NavigationObstacle3D::get_carve_navigation_mesh() const {
|
|||
return carve_navigation_mesh;
|
||||
}
|
||||
|
||||
PackedStringArray NavigationObstacle3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node3D::get_configuration_warnings();
|
||||
|
||||
if (get_global_rotation().x != 0.0 || get_global_rotation().z != 0.0) {
|
||||
warnings.push_back(RTR("NavigationObstacle3D only takes global rotation around the y-axis into account. Rotations around the x-axis or z-axis might lead to unexpected results."));
|
||||
}
|
||||
|
||||
const Vector3 global_scale = get_global_basis().get_scale();
|
||||
if (global_scale.x < 0.001 || global_scale.y < 0.001 || global_scale.z < 0.001) {
|
||||
warnings.push_back(RTR("NavigationObstacle3D does not support negative or zero scaling."));
|
||||
}
|
||||
|
||||
if (radius > 0.0 && !get_global_basis().is_conformal()) {
|
||||
warnings.push_back(RTR("The agent radius can only be scaled uniformly. The largest scale value along the three axes will be used."));
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void NavigationObstacle3D::navmesh_parse_init() {
|
||||
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
|
||||
if (!_navmesh_source_geometry_parser.is_valid()) {
|
||||
_navmesh_source_geometry_parsing_callback = callable_mp_static(&NavigationObstacle3D::navmesh_parse_source_geometry);
|
||||
_navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create();
|
||||
NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationObstacle3D::navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
|
||||
NavigationObstacle3D *obstacle = Object::cast_to<NavigationObstacle3D>(p_node);
|
||||
|
||||
if (obstacle == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!obstacle->get_affect_navigation_mesh()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float elevation = obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y;
|
||||
// Prevent non-positive scaling.
|
||||
const Vector3 safe_scale = obstacle->get_global_basis().get_scale().abs().maxf(0.001);
|
||||
const float obstacle_radius = obstacle->get_radius();
|
||||
|
||||
if (obstacle_radius > 0.0) {
|
||||
// Radius defined obstacle should be uniformly scaled from obstacle basis max scale axis.
|
||||
const float scaling_max_value = safe_scale[safe_scale.max_axis_index()];
|
||||
const Vector3 uniform_max_scale = Vector3(scaling_max_value, scaling_max_value, scaling_max_value);
|
||||
const Transform3D obstacle_circle_transform = p_source_geometry_data->root_node_transform * Transform3D(Basis().scaled(uniform_max_scale), obstacle->get_global_position());
|
||||
|
||||
Vector<Vector3> obstruction_circle_vertices;
|
||||
|
||||
// The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding.
|
||||
// Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck.
|
||||
// No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty.
|
||||
static const int circle_points = 12;
|
||||
|
||||
obstruction_circle_vertices.resize(circle_points);
|
||||
Vector3 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw();
|
||||
const real_t circle_point_step = Math_TAU / circle_points;
|
||||
|
||||
for (int i = 0; i < circle_points; i++) {
|
||||
const float angle = i * circle_point_step;
|
||||
circle_vertices_ptrw[i] = obstacle_circle_transform.xform(Vector3(Math::cos(angle) * obstacle_radius, 0.0, Math::sin(angle) * obstacle_radius));
|
||||
}
|
||||
|
||||
p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, elevation - obstacle_radius, scaling_max_value * obstacle_radius, obstacle->get_carve_navigation_mesh());
|
||||
}
|
||||
|
||||
// Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account.
|
||||
const Transform3D node_xform = p_source_geometry_data->root_node_transform * Transform3D(Basis().scaled(safe_scale).rotated(Vector3(0.0, 1.0, 0.0), obstacle->get_global_rotation().y), obstacle->get_global_position());
|
||||
|
||||
const Vector<Vector3> &obstacle_vertices = obstacle->get_vertices();
|
||||
|
||||
if (obstacle_vertices.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<Vector3> obstruction_shape_vertices;
|
||||
obstruction_shape_vertices.resize(obstacle_vertices.size());
|
||||
|
||||
const Vector3 *obstacle_vertices_ptr = obstacle_vertices.ptr();
|
||||
Vector3 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw();
|
||||
|
||||
for (int i = 0; i < obstacle_vertices.size(); i++) {
|
||||
obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]);
|
||||
obstruction_shape_vertices_ptrw[i].y = 0.0;
|
||||
}
|
||||
p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, elevation, safe_scale.y * obstacle->get_height(), obstacle->get_carve_navigation_mesh());
|
||||
}
|
||||
|
||||
void NavigationObstacle3D::_update_map(RID p_map) {
|
||||
NavigationServer3D::get_singleton()->obstacle_set_map(obstacle, p_map);
|
||||
map_current = p_map;
|
||||
|
|
@ -355,37 +505,64 @@ void NavigationObstacle3D::_update_position(const Vector3 p_position) {
|
|||
NavigationServer3D::get_singleton()->obstacle_set_position(obstacle, p_position);
|
||||
}
|
||||
|
||||
void NavigationObstacle3D::_update_transform() {
|
||||
_update_position(get_global_position());
|
||||
|
||||
// Prevent non-positive or non-uniform scaling of dynamic obstacle radius.
|
||||
const Vector3 safe_scale = get_global_basis().get_scale().abs().maxf(0.001);
|
||||
const float scaling_max_value = safe_scale[safe_scale.max_axis_index()];
|
||||
NavigationServer3D::get_singleton()->obstacle_set_radius(obstacle, scaling_max_value * radius);
|
||||
|
||||
// Apply modified node transform which only takes y-axis rotation into account to vertices.
|
||||
const Transform3D safe_transform = Transform3D(Basis().scaled(safe_scale).rotated(Vector3(0.0, 1.0, 0.0), get_global_rotation().y), Vector3());
|
||||
NavigationServer3D::get_singleton()->obstacle_set_vertices(obstacle, safe_transform.xform(vertices));
|
||||
NavigationServer3D::get_singleton()->obstacle_set_height(obstacle, safe_scale.y * height);
|
||||
}
|
||||
|
||||
void NavigationObstacle3D::_update_use_3d_avoidance(bool p_use_3d_avoidance) {
|
||||
NavigationServer3D::get_singleton()->obstacle_set_use_3d_avoidance(obstacle, use_3d_avoidance);
|
||||
_update_map(map_current);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationObstacle3D::_update_debug() {
|
||||
RenderingServer *rs = RenderingServer::get_singleton();
|
||||
if (is_inside_tree()) {
|
||||
rs->instance_set_visible(fake_agent_radius_debug_instance_rid, is_visible_in_tree());
|
||||
rs->instance_set_visible(static_obstacle_debug_instance_rid, is_visible_in_tree());
|
||||
rs->instance_set_scenario(fake_agent_radius_debug_instance_rid, get_world_3d()->get_scenario());
|
||||
rs->instance_set_scenario(static_obstacle_debug_instance_rid, get_world_3d()->get_scenario());
|
||||
rs->instance_set_transform(fake_agent_radius_debug_instance_rid, Transform3D(Basis(), get_global_position()));
|
||||
rs->instance_set_transform(static_obstacle_debug_instance_rid, Transform3D(Basis(), get_global_position()));
|
||||
_update_fake_agent_radius_debug();
|
||||
_update_static_obstacle_debug();
|
||||
} else {
|
||||
rs->mesh_clear(fake_agent_radius_debug_mesh_rid);
|
||||
rs->mesh_clear(static_obstacle_debug_mesh_rid);
|
||||
rs->instance_set_scenario(fake_agent_radius_debug_instance_rid, RID());
|
||||
rs->instance_set_scenario(static_obstacle_debug_instance_rid, RID());
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationObstacle3D::_update_fake_agent_radius_debug() {
|
||||
NavigationServer3D *ns3d = NavigationServer3D::get_singleton();
|
||||
RenderingServer *rs = RenderingServer::get_singleton();
|
||||
|
||||
bool is_debug_enabled = false;
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
is_debug_enabled = true;
|
||||
} else if (NavigationServer3D::get_singleton()->get_debug_enabled() &&
|
||||
NavigationServer3D::get_singleton()->get_debug_avoidance_enabled() &&
|
||||
NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius()) {
|
||||
} else if (ns3d->get_debug_enabled() &&
|
||||
ns3d->get_debug_avoidance_enabled() &&
|
||||
ns3d->get_debug_navigation_avoidance_enable_obstacles_radius()) {
|
||||
is_debug_enabled = true;
|
||||
}
|
||||
|
||||
if (is_debug_enabled == false) {
|
||||
if (fake_agent_radius_debug_instance.is_valid()) {
|
||||
RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, false);
|
||||
}
|
||||
rs->mesh_clear(fake_agent_radius_debug_mesh_rid);
|
||||
|
||||
if (!is_debug_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fake_agent_radius_debug_instance.is_valid()) {
|
||||
fake_agent_radius_debug_instance = RenderingServer::get_singleton()->instance_create();
|
||||
}
|
||||
if (!fake_agent_radius_debug_mesh.is_valid()) {
|
||||
fake_agent_radius_debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
||||
}
|
||||
fake_agent_radius_debug_mesh->clear_surfaces();
|
||||
|
||||
Vector<Vector3> face_vertex_array;
|
||||
Vector<int> face_indices_array;
|
||||
|
||||
|
|
@ -439,147 +616,108 @@ void NavigationObstacle3D::_update_fake_agent_radius_debug() {
|
|||
face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array;
|
||||
face_mesh_array[Mesh::ARRAY_INDEX] = face_indices_array;
|
||||
|
||||
fake_agent_radius_debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, face_mesh_array);
|
||||
Ref<StandardMaterial3D> face_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_obstacles_radius_material();
|
||||
fake_agent_radius_debug_mesh->surface_set_material(0, face_material);
|
||||
rs->mesh_add_surface_from_arrays(fake_agent_radius_debug_mesh_rid, RS::PRIMITIVE_TRIANGLES, face_mesh_array);
|
||||
|
||||
Ref<StandardMaterial3D> face_material = ns3d->get_debug_navigation_avoidance_obstacles_radius_material();
|
||||
rs->instance_set_surface_override_material(fake_agent_radius_debug_instance_rid, 0, face_material->get_rid());
|
||||
|
||||
RS::get_singleton()->instance_set_base(fake_agent_radius_debug_instance, fake_agent_radius_debug_mesh->get_rid());
|
||||
if (is_inside_tree()) {
|
||||
RS::get_singleton()->instance_set_scenario(fake_agent_radius_debug_instance, get_world_3d()->get_scenario());
|
||||
RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, is_visible_in_tree());
|
||||
rs->instance_set_scenario(fake_agent_radius_debug_instance_rid, get_world_3d()->get_scenario());
|
||||
rs->instance_set_visible(fake_agent_radius_debug_instance_rid, is_visible_in_tree());
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationObstacle3D::_update_static_obstacle_debug() {
|
||||
bool is_debug_enabled = false;
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
is_debug_enabled = true;
|
||||
} else if (NavigationServer3D::get_singleton()->get_debug_enabled() &&
|
||||
NavigationServer3D::get_singleton()->get_debug_avoidance_enabled() &&
|
||||
NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_static()) {
|
||||
// Don't update inside Editor as Node3D gizmo takes care of this.
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationServer3D *ns3d = NavigationServer3D::get_singleton();
|
||||
RenderingServer *rs = RenderingServer::get_singleton();
|
||||
|
||||
bool is_debug_enabled = false;
|
||||
if (ns3d->get_debug_enabled() &&
|
||||
ns3d->get_debug_avoidance_enabled() &&
|
||||
ns3d->get_debug_navigation_avoidance_enable_obstacles_static()) {
|
||||
is_debug_enabled = true;
|
||||
}
|
||||
|
||||
if (is_debug_enabled == false) {
|
||||
if (static_obstacle_debug_instance.is_valid()) {
|
||||
RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, false);
|
||||
rs->mesh_clear(static_obstacle_debug_mesh_rid);
|
||||
|
||||
if (!is_debug_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int vertex_count = vertices.size();
|
||||
|
||||
if (vertex_count < 3) {
|
||||
if (static_obstacle_debug_instance_rid.is_valid()) {
|
||||
rs->instance_set_visible(static_obstacle_debug_instance_rid, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (vertices.size() < 3) {
|
||||
if (static_obstacle_debug_instance.is_valid()) {
|
||||
RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!static_obstacle_debug_instance.is_valid()) {
|
||||
static_obstacle_debug_instance = RenderingServer::get_singleton()->instance_create();
|
||||
}
|
||||
if (!static_obstacle_debug_mesh.is_valid()) {
|
||||
static_obstacle_debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
||||
}
|
||||
static_obstacle_debug_mesh->clear_surfaces();
|
||||
|
||||
Vector<Vector2> polygon_2d_vertices;
|
||||
polygon_2d_vertices.resize(vertices.size());
|
||||
Vector2 *polygon_2d_vertices_ptr = polygon_2d_vertices.ptrw();
|
||||
|
||||
for (int i = 0; i < vertices.size(); ++i) {
|
||||
Vector3 obstacle_vertex = vertices[i];
|
||||
Vector2 obstacle_vertex_2d = Vector2(obstacle_vertex.x, obstacle_vertex.z);
|
||||
polygon_2d_vertices_ptr[i] = obstacle_vertex_2d;
|
||||
}
|
||||
|
||||
Vector<int> triangulated_polygon_2d_indices = Geometry2D::triangulate_polygon(polygon_2d_vertices);
|
||||
|
||||
if (triangulated_polygon_2d_indices.is_empty()) {
|
||||
// failed triangulation
|
||||
return;
|
||||
}
|
||||
|
||||
bool obstacle_pushes_inward = Geometry2D::is_polygon_clockwise(polygon_2d_vertices);
|
||||
|
||||
Vector<Vector3> face_vertex_array;
|
||||
Vector<int> face_indices_array;
|
||||
|
||||
face_vertex_array.resize(polygon_2d_vertices.size());
|
||||
face_indices_array.resize(triangulated_polygon_2d_indices.size());
|
||||
|
||||
Vector3 *face_vertex_array_ptr = face_vertex_array.ptrw();
|
||||
int *face_indices_array_ptr = face_indices_array.ptrw();
|
||||
|
||||
for (int i = 0; i < triangulated_polygon_2d_indices.size(); ++i) {
|
||||
int vertex_index = triangulated_polygon_2d_indices[i];
|
||||
const Vector2 &vertex_2d = polygon_2d_vertices[vertex_index];
|
||||
Vector3 vertex_3d = Vector3(vertex_2d.x, 0.0, vertex_2d.y);
|
||||
face_vertex_array_ptr[vertex_index] = vertex_3d;
|
||||
face_indices_array_ptr[i] = vertex_index;
|
||||
}
|
||||
|
||||
Array face_mesh_array;
|
||||
face_mesh_array.resize(Mesh::ARRAY_MAX);
|
||||
face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array;
|
||||
face_mesh_array[Mesh::ARRAY_INDEX] = face_indices_array;
|
||||
|
||||
static_obstacle_debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, face_mesh_array);
|
||||
|
||||
Vector<Vector3> edge_vertex_array;
|
||||
edge_vertex_array.resize(vertex_count * 8);
|
||||
|
||||
for (int i = 0; i < polygon_2d_vertices.size(); ++i) {
|
||||
int from_index = i - 1;
|
||||
int to_index = i;
|
||||
Vector3 *edge_vertex_array_ptrw = edge_vertex_array.ptrw();
|
||||
|
||||
if (i == 0) {
|
||||
from_index = polygon_2d_vertices.size() - 1;
|
||||
}
|
||||
int vertex_index = 0;
|
||||
|
||||
const Vector2 &vertex_2d_from = polygon_2d_vertices[from_index];
|
||||
const Vector2 &vertex_2d_to = polygon_2d_vertices[to_index];
|
||||
for (int i = 0; i < vertex_count; i++) {
|
||||
Vector3 point = vertices[i];
|
||||
Vector3 next_point = vertices[(i + 1) % vertex_count];
|
||||
|
||||
Vector3 vertex_3d_ground_from = Vector3(vertex_2d_from.x, 0.0, vertex_2d_from.y);
|
||||
Vector3 vertex_3d_ground_to = Vector3(vertex_2d_to.x, 0.0, vertex_2d_to.y);
|
||||
Vector3 direction = next_point.direction_to(point);
|
||||
Vector3 arrow_dir = direction.cross(Vector3(0.0, 1.0, 0.0));
|
||||
Vector3 edge_middle = point + ((next_point - point) * 0.5);
|
||||
|
||||
edge_vertex_array.push_back(vertex_3d_ground_from);
|
||||
edge_vertex_array.push_back(vertex_3d_ground_to);
|
||||
edge_vertex_array_ptrw[vertex_index++] = edge_middle;
|
||||
edge_vertex_array_ptrw[vertex_index++] = edge_middle + (arrow_dir * 0.5);
|
||||
|
||||
Vector3 vertex_3d_height_from = Vector3(vertex_2d_from.x, height, vertex_2d_from.y);
|
||||
Vector3 vertex_3d_height_to = Vector3(vertex_2d_to.x, height, vertex_2d_to.y);
|
||||
edge_vertex_array_ptrw[vertex_index++] = point;
|
||||
edge_vertex_array_ptrw[vertex_index++] = next_point;
|
||||
|
||||
edge_vertex_array.push_back(vertex_3d_height_from);
|
||||
edge_vertex_array.push_back(vertex_3d_height_to);
|
||||
edge_vertex_array_ptrw[vertex_index++] = Vector3(point.x, height, point.z);
|
||||
edge_vertex_array_ptrw[vertex_index++] = Vector3(next_point.x, height, next_point.z);
|
||||
|
||||
edge_vertex_array.push_back(vertex_3d_ground_from);
|
||||
edge_vertex_array.push_back(vertex_3d_height_from);
|
||||
edge_vertex_array_ptrw[vertex_index++] = point;
|
||||
edge_vertex_array_ptrw[vertex_index++] = Vector3(point.x, height, point.z);
|
||||
}
|
||||
|
||||
Array edge_mesh_array;
|
||||
edge_mesh_array.resize(Mesh::ARRAY_MAX);
|
||||
edge_mesh_array[Mesh::ARRAY_VERTEX] = edge_vertex_array;
|
||||
|
||||
static_obstacle_debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, edge_mesh_array);
|
||||
rs->mesh_add_surface_from_arrays(static_obstacle_debug_mesh_rid, RS::PRIMITIVE_LINES, edge_mesh_array);
|
||||
|
||||
Ref<StandardMaterial3D> face_material;
|
||||
Ref<StandardMaterial3D> edge_material;
|
||||
|
||||
if (obstacle_pushes_inward) {
|
||||
face_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_face_material();
|
||||
edge_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_edge_material();
|
||||
if (are_vertices_valid()) {
|
||||
edge_material = ns3d->get_debug_navigation_avoidance_static_obstacle_pushout_edge_material();
|
||||
} else {
|
||||
face_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_face_material();
|
||||
edge_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_edge_material();
|
||||
edge_material = ns3d->get_debug_navigation_avoidance_static_obstacle_pushin_edge_material();
|
||||
}
|
||||
|
||||
static_obstacle_debug_mesh->surface_set_material(0, face_material);
|
||||
static_obstacle_debug_mesh->surface_set_material(1, edge_material);
|
||||
rs->instance_set_surface_override_material(static_obstacle_debug_instance_rid, 0, edge_material->get_rid());
|
||||
|
||||
RS::get_singleton()->instance_set_base(static_obstacle_debug_instance, static_obstacle_debug_mesh->get_rid());
|
||||
if (is_inside_tree()) {
|
||||
RS::get_singleton()->instance_set_scenario(static_obstacle_debug_instance, get_world_3d()->get_scenario());
|
||||
RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, is_visible_in_tree());
|
||||
rs->instance_set_scenario(static_obstacle_debug_instance_rid, get_world_3d()->get_scenario());
|
||||
rs->instance_set_visible(static_obstacle_debug_instance_rid, is_visible_in_tree());
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationObstacle3D::_clear_debug() {
|
||||
RenderingServer *rs = RenderingServer::get_singleton();
|
||||
ERR_FAIL_NULL(rs);
|
||||
rs->mesh_clear(fake_agent_radius_debug_mesh_rid);
|
||||
rs->mesh_clear(static_obstacle_debug_mesh_rid);
|
||||
rs->instance_set_scenario(fake_agent_radius_debug_instance_rid, RID());
|
||||
rs->instance_set_scenario(static_obstacle_debug_instance_rid, RID());
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@
|
|||
|
||||
#include "scene/3d/node_3d.h"
|
||||
|
||||
class NavigationMesh;
|
||||
class NavigationMeshSourceGeometryData3D;
|
||||
|
||||
class NavigationObstacle3D : public Node3D {
|
||||
GDCLASS(NavigationObstacle3D, Node3D);
|
||||
|
||||
|
|
@ -45,14 +48,14 @@ class NavigationObstacle3D : public Node3D {
|
|||
real_t radius = 0.0;
|
||||
|
||||
Vector<Vector3> vertices;
|
||||
bool vertices_are_clockwise = true;
|
||||
bool vertices_are_valid = true;
|
||||
|
||||
bool avoidance_enabled = true;
|
||||
uint32_t avoidance_layers = 1;
|
||||
|
||||
bool use_3d_avoidance = false;
|
||||
|
||||
Transform3D previous_transform;
|
||||
|
||||
Vector3 velocity;
|
||||
Vector3 previous_velocity;
|
||||
bool velocity_submitted = false;
|
||||
|
|
@ -61,15 +64,17 @@ class NavigationObstacle3D : public Node3D {
|
|||
bool carve_navigation_mesh = false;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
RID fake_agent_radius_debug_instance;
|
||||
Ref<ArrayMesh> fake_agent_radius_debug_mesh;
|
||||
RID fake_agent_radius_debug_instance_rid;
|
||||
RID fake_agent_radius_debug_mesh_rid;
|
||||
|
||||
RID static_obstacle_debug_instance;
|
||||
Ref<ArrayMesh> static_obstacle_debug_mesh;
|
||||
RID static_obstacle_debug_instance_rid;
|
||||
RID static_obstacle_debug_mesh_rid;
|
||||
|
||||
private:
|
||||
void _update_debug();
|
||||
void _update_fake_agent_radius_debug();
|
||||
void _update_static_obstacle_debug();
|
||||
void _clear_debug();
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
protected:
|
||||
|
|
@ -95,7 +100,10 @@ public:
|
|||
real_t get_height() const { return height; }
|
||||
|
||||
void set_vertices(const Vector<Vector3> &p_vertices);
|
||||
const Vector<Vector3> &get_vertices() const { return vertices; };
|
||||
const Vector<Vector3> &get_vertices() const { return vertices; }
|
||||
|
||||
bool are_vertices_clockwise() const { return vertices_are_clockwise; }
|
||||
bool are_vertices_valid() const { return vertices_are_valid; }
|
||||
|
||||
void set_avoidance_layers(uint32_t p_layers);
|
||||
uint32_t get_avoidance_layers() const;
|
||||
|
|
@ -107,7 +115,7 @@ public:
|
|||
bool get_use_3d_avoidance() const { return use_3d_avoidance; }
|
||||
|
||||
void set_velocity(const Vector3 p_velocity);
|
||||
Vector3 get_velocity() const { return velocity; };
|
||||
Vector3 get_velocity() const { return velocity; }
|
||||
|
||||
void _avoidance_done(Vector3 p_new_velocity); // Dummy
|
||||
|
||||
|
|
@ -117,9 +125,20 @@ public:
|
|||
void set_carve_navigation_mesh(bool p_enabled);
|
||||
bool get_carve_navigation_mesh() const;
|
||||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
private:
|
||||
static Callable _navmesh_source_geometry_parsing_callback;
|
||||
static RID _navmesh_source_geometry_parser;
|
||||
|
||||
public:
|
||||
static void navmesh_parse_init();
|
||||
static void navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
|
||||
|
||||
private:
|
||||
void _update_map(RID p_map);
|
||||
void _update_position(const Vector3 p_position);
|
||||
void _update_transform();
|
||||
void _update_use_3d_avoidance(bool p_use_3d_avoidance);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -193,6 +193,8 @@ void NavigationRegion3D::set_navigation_mesh(const Ref<NavigationMesh> &p_naviga
|
|||
navigation_mesh->connect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed));
|
||||
}
|
||||
|
||||
_update_bounds();
|
||||
|
||||
NavigationServer3D::get_singleton()->region_set_navigation_mesh(region, p_navigation_mesh);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
|
@ -242,7 +244,7 @@ RID NavigationRegion3D::get_navigation_map() const {
|
|||
|
||||
void NavigationRegion3D::bake_navigation_mesh(bool p_on_thread) {
|
||||
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
|
||||
ERR_FAIL_COND_MSG(!navigation_mesh.is_valid(), "Baking the navigation mesh requires a valid `NavigationMesh` resource.");
|
||||
ERR_FAIL_COND_MSG(navigation_mesh.is_null(), "Baking the navigation mesh requires a valid `NavigationMesh` resource.");
|
||||
|
||||
Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
|
||||
source_geometry_data.instantiate();
|
||||
|
|
@ -271,10 +273,10 @@ bool NavigationRegion3D::is_baking() const {
|
|||
}
|
||||
|
||||
PackedStringArray NavigationRegion3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = Node3D::get_configuration_warnings();
|
||||
|
||||
if (is_visible_in_tree() && is_inside_tree()) {
|
||||
if (!navigation_mesh.is_valid()) {
|
||||
if (navigation_mesh.is_null()) {
|
||||
warnings.push_back(RTR("A NavigationMesh resource must be set or created for this node to work."));
|
||||
}
|
||||
}
|
||||
|
|
@ -314,6 +316,8 @@ void NavigationRegion3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("bake_navigation_mesh", "on_thread"), &NavigationRegion3D::bake_navigation_mesh, DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("is_baking"), &NavigationRegion3D::is_baking);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationRegion3D::get_bounds);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navigation_mesh", PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh"), "set_navigation_mesh", "get_navigation_mesh");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_edge_connections"), "set_use_edge_connections", "get_use_edge_connections");
|
||||
|
|
@ -481,7 +485,7 @@ void NavigationRegion3D::_update_debug_mesh() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!navigation_mesh.is_valid()) {
|
||||
if (navigation_mesh.is_null()) {
|
||||
if (debug_instance.is_valid()) {
|
||||
RS::get_singleton()->instance_set_visible(debug_instance, false);
|
||||
}
|
||||
|
|
@ -492,8 +496,8 @@ void NavigationRegion3D::_update_debug_mesh() {
|
|||
debug_instance = RenderingServer::get_singleton()->instance_create();
|
||||
}
|
||||
|
||||
if (!debug_mesh.is_valid()) {
|
||||
debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
||||
if (debug_mesh.is_null()) {
|
||||
debug_mesh.instantiate();
|
||||
}
|
||||
|
||||
debug_mesh->clear_surfaces();
|
||||
|
|
@ -658,7 +662,7 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!navigation_mesh.is_valid()) {
|
||||
if (navigation_mesh.is_null()) {
|
||||
if (debug_edge_connections_instance.is_valid()) {
|
||||
RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false);
|
||||
}
|
||||
|
|
@ -669,8 +673,8 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
|
|||
debug_edge_connections_instance = RenderingServer::get_singleton()->instance_create();
|
||||
}
|
||||
|
||||
if (!debug_edge_connections_mesh.is_valid()) {
|
||||
debug_edge_connections_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
||||
if (debug_edge_connections_mesh.is_null()) {
|
||||
debug_edge_connections_mesh.instantiate();
|
||||
}
|
||||
|
||||
debug_edge_connections_mesh->clear_surfaces();
|
||||
|
|
@ -686,6 +690,8 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
|
|||
|
||||
Vector<Vector3> vertex_array;
|
||||
vertex_array.resize(connections_count * 6);
|
||||
Vector3 *vertex_array_ptrw = vertex_array.ptrw();
|
||||
int vertex_array_index = 0;
|
||||
|
||||
for (int i = 0; i < connections_count; i++) {
|
||||
Vector3 connection_pathway_start = NavigationServer3D::get_singleton()->region_get_connection_pathway_start(region, i);
|
||||
|
|
@ -705,13 +711,12 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
|
|||
Vector3 left_end_pos = connection_pathway_end + (end_right_dir * half_edge_connection_margin);
|
||||
Vector3 right_end_pos = connection_pathway_end + (end_left_dir * half_edge_connection_margin);
|
||||
|
||||
vertex_array.push_back(right_end_pos);
|
||||
vertex_array.push_back(left_start_pos);
|
||||
vertex_array.push_back(right_start_pos);
|
||||
|
||||
vertex_array.push_back(left_end_pos);
|
||||
vertex_array.push_back(right_end_pos);
|
||||
vertex_array.push_back(right_start_pos);
|
||||
vertex_array_ptrw[vertex_array_index++] = connection_pathway_start;
|
||||
vertex_array_ptrw[vertex_array_index++] = connection_pathway_end;
|
||||
vertex_array_ptrw[vertex_array_index++] = left_start_pos;
|
||||
vertex_array_ptrw[vertex_array_index++] = right_start_pos;
|
||||
vertex_array_ptrw[vertex_array_index++] = left_end_pos;
|
||||
vertex_array_ptrw[vertex_array_index++] = right_end_pos;
|
||||
}
|
||||
|
||||
if (vertex_array.size() == 0) {
|
||||
|
|
@ -724,7 +729,7 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
|
|||
mesh_array.resize(Mesh::ARRAY_MAX);
|
||||
mesh_array[Mesh::ARRAY_VERTEX] = vertex_array;
|
||||
|
||||
debug_edge_connections_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, mesh_array);
|
||||
debug_edge_connections_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, mesh_array);
|
||||
debug_edge_connections_mesh->surface_set_material(0, edge_connections_material);
|
||||
|
||||
RS::get_singleton()->instance_set_base(debug_edge_connections_instance, debug_edge_connections_mesh->get_rid());
|
||||
|
|
@ -739,3 +744,26 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
|
|||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
void NavigationRegion3D::_update_bounds() {
|
||||
if (navigation_mesh.is_null()) {
|
||||
bounds = AABB();
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector<Vector3> &vertices = navigation_mesh->get_vertices();
|
||||
if (vertices.is_empty()) {
|
||||
bounds = AABB();
|
||||
return;
|
||||
}
|
||||
|
||||
const Transform3D gt = is_inside_tree() ? get_global_transform() : get_transform();
|
||||
|
||||
AABB new_bounds;
|
||||
new_bounds.position = gt.xform(vertices[0]);
|
||||
|
||||
for (const Vector3 &vertex : vertices) {
|
||||
new_bounds.expand_to(gt.xform(vertex));
|
||||
}
|
||||
bounds = new_bounds;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ class NavigationRegion3D : public Node3D {
|
|||
|
||||
void _navigation_mesh_changed();
|
||||
|
||||
AABB bounds;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
RID debug_instance;
|
||||
RID debug_edge_connections_instance;
|
||||
|
|
@ -110,10 +112,13 @@ public:
|
|||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
AABB get_bounds() const { return bounds; }
|
||||
|
||||
NavigationRegion3D();
|
||||
~NavigationRegion3D();
|
||||
|
||||
private:
|
||||
void _update_bounds();
|
||||
void _region_enter_navigation_map();
|
||||
void _region_exit_navigation_map();
|
||||
void _region_update_transform();
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
@ -720,10 +841,10 @@ void Node3D::reparent(Node *p_parent, bool p_keep_global_transform) {
|
|||
ERR_THREAD_GUARD;
|
||||
if (p_keep_global_transform) {
|
||||
Transform3D temp = get_global_transform();
|
||||
Node::reparent(p_parent);
|
||||
Node::reparent(p_parent, p_keep_global_transform);
|
||||
set_global_transform(temp);
|
||||
} else {
|
||||
Node::reparent(p_parent);
|
||||
Node::reparent(p_parent, p_keep_global_transform);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -945,7 +1066,6 @@ void Node3D::look_at_from_position(const Vector3 &p_pos, const Vector3 &p_target
|
|||
ERR_THREAD_GUARD;
|
||||
ERR_FAIL_COND_MSG(p_pos.is_equal_approx(p_target), "Node origin and target are in the same position, look_at() failed.");
|
||||
ERR_FAIL_COND_MSG(p_up.is_zero_approx(), "The up vector can't be zero, look_at() failed.");
|
||||
ERR_FAIL_COND_MSG(p_up.cross(p_target - p_pos).is_zero_approx(), "Up vector and direction between node origin and target are aligned, look_at() failed.");
|
||||
|
||||
Vector3 forward = p_target - p_pos;
|
||||
Basis lookat_basis = Basis::looking_at(forward, p_up, p_use_model_front);
|
||||
|
|
@ -1060,15 +1180,16 @@ void Node3D::_validate_property(PropertyInfo &p_property) const {
|
|||
}
|
||||
|
||||
bool Node3D::_property_can_revert(const StringName &p_name) const {
|
||||
if (p_name == "basis") {
|
||||
const String sname = p_name;
|
||||
if (sname == "basis") {
|
||||
return true;
|
||||
} else if (p_name == "scale") {
|
||||
} else if (sname == "scale") {
|
||||
return true;
|
||||
} else if (p_name == "quaternion") {
|
||||
} else if (sname == "quaternion") {
|
||||
return true;
|
||||
} else if (p_name == "rotation") {
|
||||
} else if (sname == "rotation") {
|
||||
return true;
|
||||
} else if (p_name == "position") {
|
||||
} else if (sname == "position") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -1077,35 +1198,36 @@ bool Node3D::_property_can_revert(const StringName &p_name) const {
|
|||
bool Node3D::_property_get_revert(const StringName &p_name, Variant &r_property) const {
|
||||
bool valid = false;
|
||||
|
||||
if (p_name == "basis") {
|
||||
const String sname = p_name;
|
||||
if (sname == "basis") {
|
||||
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
|
||||
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
|
||||
r_property = Transform3D(variant).get_basis();
|
||||
} else {
|
||||
r_property = Basis();
|
||||
}
|
||||
} else if (p_name == "scale") {
|
||||
} else if (sname == "scale") {
|
||||
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
|
||||
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
|
||||
r_property = Transform3D(variant).get_basis().get_scale();
|
||||
} else {
|
||||
r_property = Vector3(1.0, 1.0, 1.0);
|
||||
}
|
||||
} else if (p_name == "quaternion") {
|
||||
} else if (sname == "quaternion") {
|
||||
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
|
||||
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
|
||||
r_property = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion());
|
||||
} else {
|
||||
r_property = Quaternion();
|
||||
}
|
||||
} else if (p_name == "rotation") {
|
||||
} else if (sname == "rotation") {
|
||||
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
|
||||
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
|
||||
r_property = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order);
|
||||
} else {
|
||||
r_property = Vector3();
|
||||
}
|
||||
} else if (p_name == "position") {
|
||||
} else if (sname == "position") {
|
||||
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
|
||||
if (valid) {
|
||||
r_property = Transform3D(variant).get_origin();
|
||||
|
|
@ -1140,6 +1262,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);
|
||||
|
|
@ -1214,7 +1337,7 @@ void Node3D::_bind_methods() {
|
|||
ADD_GROUP("Transform", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NO_EDITOR), "set_transform", "get_transform");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_less,hide_slider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,or_greater,or_less,hide_slider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians_as_degrees", PROPERTY_USAGE_EDITOR), "set_rotation", "get_rotation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation_degrees", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_rotation_degrees", "get_rotation_degrees");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "quaternion", PROPERTY_HINT_HIDE_QUATERNION_EDIT, "", PROPERTY_USAGE_EDITOR), "set_quaternion", "get_quaternion");
|
||||
|
|
@ -1236,4 +1359,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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,11 +227,14 @@ 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;
|
||||
virtual void set_transform_gizmo_visible(bool p_enabled) { data.transform_gizmo_visible = p_enabled; };
|
||||
virtual bool is_transform_gizmo_visible() const { return data.transform_gizmo_visible; };
|
||||
virtual void set_transform_gizmo_visible(bool p_enabled) { data.transform_gizmo_visible = p_enabled; }
|
||||
virtual bool is_transform_gizmo_visible() const { return data.transform_gizmo_visible; }
|
||||
#endif
|
||||
virtual void reparent(Node *p_parent, bool p_keep_global_transform = true) override;
|
||||
|
||||
|
|
@ -279,6 +301,7 @@ public:
|
|||
NodePath get_visibility_parent() const;
|
||||
|
||||
Node3D();
|
||||
~Node3D();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(Node3D::RotationEditMode)
|
||||
|
|
|
|||
|
|
@ -129,9 +129,6 @@ void Occluder3D::_notification(int p_what) {
|
|||
}
|
||||
}
|
||||
|
||||
void Occluder3D::_bind_methods() {
|
||||
}
|
||||
|
||||
Occluder3D::Occluder3D() {
|
||||
occluder = RS::get_singleton()->occluder_create();
|
||||
}
|
||||
|
|
@ -694,7 +691,7 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node,
|
|||
}
|
||||
|
||||
PackedStringArray OccluderInstance3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
|
||||
|
||||
if (!bool(GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"))) {
|
||||
warnings.push_back(RTR("Occlusion culling is disabled in the Project Settings, which means occlusion culling won't be performed in the root viewport.\nTo resolve this, open the Project Settings and enable Rendering > Occlusion Culling > Use Occlusion Culling."));
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -88,11 +88,11 @@ void Path3D::_update_debug_mesh() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!debug_mesh.is_valid()) {
|
||||
debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
||||
if (debug_mesh.is_null()) {
|
||||
debug_mesh.instantiate();
|
||||
}
|
||||
|
||||
if (!(curve.is_valid())) {
|
||||
if (curve.is_null()) {
|
||||
RS::get_singleton()->instance_set_visible(debug_instance, false);
|
||||
return;
|
||||
}
|
||||
|
|
@ -131,16 +131,19 @@ void Path3D::_update_debug_mesh() {
|
|||
// Path3D as a ribbon.
|
||||
ribbon_ptr[i] = p1;
|
||||
|
||||
// Fish Bone.
|
||||
const Vector3 p_left = p1 + (side + forward - up * 0.3) * 0.06;
|
||||
const Vector3 p_right = p1 + (-side + forward - up * 0.3) * 0.06;
|
||||
if (i % 4 == 0) {
|
||||
// Draw fish bone every 4 points to reduce visual noise and performance impact
|
||||
// (compared to drawing it for every point).
|
||||
const Vector3 p_left = p1 + (side + forward - up * 0.3) * 0.06;
|
||||
const Vector3 p_right = p1 + (-side + forward - up * 0.3) * 0.06;
|
||||
|
||||
const int bone_idx = i * 4;
|
||||
const int bone_idx = i * 4;
|
||||
|
||||
bones_ptr[bone_idx] = p1;
|
||||
bones_ptr[bone_idx + 1] = p_left;
|
||||
bones_ptr[bone_idx + 2] = p1;
|
||||
bones_ptr[bone_idx + 3] = p_right;
|
||||
bones_ptr[bone_idx] = p1;
|
||||
bones_ptr[bone_idx + 1] = p_left;
|
||||
bones_ptr[bone_idx + 2] = p1;
|
||||
bones_ptr[bone_idx + 3] = p_right;
|
||||
}
|
||||
}
|
||||
|
||||
Array ribbon_array;
|
||||
|
|
@ -216,30 +219,13 @@ void Path3D::_bind_methods() {
|
|||
ADD_SIGNAL(MethodInfo("curve_changed"));
|
||||
}
|
||||
|
||||
// Update transform, in deferred mode by default to avoid superfluity.
|
||||
void PathFollow3D::update_transform(bool p_immediate) {
|
||||
transform_dirty = true;
|
||||
|
||||
if (p_immediate) {
|
||||
_update_transform();
|
||||
} else {
|
||||
callable_mp(this, &PathFollow3D::_update_transform).call_deferred();
|
||||
}
|
||||
}
|
||||
|
||||
// Update transform immediately .
|
||||
void PathFollow3D::_update_transform() {
|
||||
if (!transform_dirty) {
|
||||
return;
|
||||
}
|
||||
transform_dirty = false;
|
||||
|
||||
void PathFollow3D::update_transform() {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<Curve3D> c = path->get_curve();
|
||||
if (!c.is_valid()) {
|
||||
if (c.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -286,9 +272,7 @@ void PathFollow3D::_notification(int p_what) {
|
|||
Node *parent = get_parent();
|
||||
if (parent) {
|
||||
path = Object::cast_to<Path3D>(parent);
|
||||
if (path) {
|
||||
update_transform();
|
||||
}
|
||||
update_transform();
|
||||
}
|
||||
} break;
|
||||
|
||||
|
|
@ -318,7 +302,7 @@ void PathFollow3D::_validate_property(PropertyInfo &p_property) const {
|
|||
}
|
||||
|
||||
PackedStringArray PathFollow3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = Node3D::get_configuration_warnings();
|
||||
|
||||
if (is_visible_in_tree() && is_inside_tree()) {
|
||||
if (!Object::cast_to<Path3D>(get_parent())) {
|
||||
|
|
@ -414,6 +398,9 @@ void PathFollow3D::_bind_methods() {
|
|||
|
||||
void PathFollow3D::set_progress(real_t p_progress) {
|
||||
ERR_FAIL_COND(!isfinite(p_progress));
|
||||
if (progress == p_progress) {
|
||||
return;
|
||||
}
|
||||
progress = p_progress;
|
||||
|
||||
if (path) {
|
||||
|
|
@ -435,10 +422,11 @@ void PathFollow3D::set_progress(real_t p_progress) {
|
|||
}
|
||||
|
||||
void PathFollow3D::set_h_offset(real_t p_h_offset) {
|
||||
h_offset = p_h_offset;
|
||||
if (path) {
|
||||
update_transform();
|
||||
if (h_offset == p_h_offset) {
|
||||
return;
|
||||
}
|
||||
h_offset = p_h_offset;
|
||||
update_transform();
|
||||
}
|
||||
|
||||
real_t PathFollow3D::get_h_offset() const {
|
||||
|
|
@ -446,10 +434,11 @@ real_t PathFollow3D::get_h_offset() const {
|
|||
}
|
||||
|
||||
void PathFollow3D::set_v_offset(real_t p_v_offset) {
|
||||
v_offset = p_v_offset;
|
||||
if (path) {
|
||||
update_transform();
|
||||
if (v_offset == p_v_offset) {
|
||||
return;
|
||||
}
|
||||
v_offset = p_v_offset;
|
||||
update_transform();
|
||||
}
|
||||
|
||||
real_t PathFollow3D::get_v_offset() const {
|
||||
|
|
@ -461,9 +450,10 @@ real_t PathFollow3D::get_progress() const {
|
|||
}
|
||||
|
||||
void PathFollow3D::set_progress_ratio(real_t p_ratio) {
|
||||
if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) {
|
||||
set_progress(p_ratio * path->get_curve()->get_baked_length());
|
||||
}
|
||||
ERR_FAIL_NULL_MSG(path, "Can only set progress ratio on a PathFollow3D that is the child of a Path3D which is itself part of the scene tree.");
|
||||
ERR_FAIL_COND_MSG(path->get_curve().is_null(), "Can't set progress ratio on a PathFollow3D that does not have a Curve.");
|
||||
ERR_FAIL_COND_MSG(!path->get_curve()->get_baked_length(), "Can't set progress ratio on a PathFollow3D that has a 0 length curve.");
|
||||
set_progress(p_ratio * path->get_curve()->get_baked_length());
|
||||
}
|
||||
|
||||
real_t PathFollow3D::get_progress_ratio() const {
|
||||
|
|
@ -475,6 +465,9 @@ real_t PathFollow3D::get_progress_ratio() const {
|
|||
}
|
||||
|
||||
void PathFollow3D::set_rotation_mode(RotationMode p_rotation_mode) {
|
||||
if (rotation_mode == p_rotation_mode) {
|
||||
return;
|
||||
}
|
||||
rotation_mode = p_rotation_mode;
|
||||
|
||||
update_configuration_warnings();
|
||||
|
|
@ -486,6 +479,9 @@ PathFollow3D::RotationMode PathFollow3D::get_rotation_mode() const {
|
|||
}
|
||||
|
||||
void PathFollow3D::set_use_model_front(bool p_use_model_front) {
|
||||
if (use_model_front == p_use_model_front) {
|
||||
return;
|
||||
}
|
||||
use_model_front = p_use_model_front;
|
||||
update_transform();
|
||||
}
|
||||
|
|
@ -495,6 +491,9 @@ bool PathFollow3D::is_using_model_front() const {
|
|||
}
|
||||
|
||||
void PathFollow3D::set_loop(bool p_loop) {
|
||||
if (loop == p_loop) {
|
||||
return;
|
||||
}
|
||||
loop = p_loop;
|
||||
update_transform();
|
||||
}
|
||||
|
|
@ -504,6 +503,9 @@ bool PathFollow3D::has_loop() const {
|
|||
}
|
||||
|
||||
void PathFollow3D::set_tilt_enabled(bool p_enabled) {
|
||||
if (tilt_enabled == p_enabled) {
|
||||
return;
|
||||
}
|
||||
tilt_enabled = p_enabled;
|
||||
update_transform();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ protected:
|
|||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
void _notification(int p_what);
|
||||
void _update_transform();
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
|
|
@ -124,7 +123,7 @@ public:
|
|||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
void update_transform(bool p_immediate = false);
|
||||
void update_transform();
|
||||
|
||||
static Transform3D correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@
|
|||
|
||||
#include "physical_bone_simulator_3d.h"
|
||||
|
||||
#include "scene/3d/physics/physical_bone_3d.h"
|
||||
|
||||
void PhysicalBoneSimulator3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) {
|
||||
if (p_old) {
|
||||
if (p_old->is_connected(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed))) {
|
||||
|
|
@ -73,10 +75,15 @@ void PhysicalBoneSimulator3D::_pose_updated() {
|
|||
}
|
||||
ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
|
||||
for (int i = 0; i < skeleton->get_bone_count(); i++) {
|
||||
bones.write[i].global_pose = skeleton->get_bone_global_pose(i);
|
||||
_bone_pose_updated(skeleton, i);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalBoneSimulator3D::_bone_pose_updated(Skeleton3D *p_skeleton, int p_bone_id) {
|
||||
ERR_FAIL_INDEX(p_bone_id, bones.size());
|
||||
bones.write[p_bone_id].global_pose = p_skeleton->get_bone_global_pose(p_bone_id);
|
||||
}
|
||||
|
||||
void PhysicalBoneSimulator3D::_set_active(bool p_active) {
|
||||
if (!Engine::get_singleton()->is_editor_hint()) {
|
||||
_reset_physical_bones_state();
|
||||
|
|
@ -285,11 +292,11 @@ void _pb_start_simulation(const PhysicalBoneSimulator3D *p_simulator, Node *p_no
|
|||
}
|
||||
|
||||
void PhysicalBoneSimulator3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) {
|
||||
_pose_updated();
|
||||
|
||||
simulating = true;
|
||||
_reset_physical_bones_state();
|
||||
|
||||
_pose_updated();
|
||||
|
||||
Vector<int> sim_bones;
|
||||
if (p_bones.size() > 0) {
|
||||
sim_bones.resize(p_bones.size());
|
||||
|
|
@ -357,47 +364,17 @@ void PhysicalBoneSimulator3D::_process_modification() {
|
|||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
if (!enabled) {
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
if (bones[i].physical_bone) {
|
||||
if (bones[i].physical_bone->is_simulating_physics() == false) {
|
||||
bones[i].physical_bone->reset_to_rest_position();
|
||||
}
|
||||
}
|
||||
ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
|
||||
for (int i = 0; i < skeleton->get_bone_count(); i++) {
|
||||
if (!bones[i].physical_bone) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
|
||||
for (int i = 0; i < skeleton->get_bone_count(); i++) {
|
||||
if (!bones[i].physical_bone) {
|
||||
continue;
|
||||
}
|
||||
if (bones[i].physical_bone->is_simulating_physics() == false) {
|
||||
_bone_pose_updated(skeleton, i);
|
||||
bones[i].physical_bone->reset_to_rest_position();
|
||||
} else if (simulating) {
|
||||
skeleton->set_bone_global_pose(i, bones[i].global_pose);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// The above method is performance heavy and needs to be improved.
|
||||
// Ideally, the processing of set_bone_global_pose within Skeleton3D should be improved,
|
||||
// but the workaround available now is to convert the global pose to a local pose on the SkeletonModifier side.
|
||||
// However, the follow method needs recursive processing for deformations within PhysicalBoneSimulator3D to account for update order.
|
||||
/*
|
||||
ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
|
||||
LocalVector<Transform3D> local_poses;
|
||||
for (int i = 0; i < skeleton->get_bone_count(); i++) {
|
||||
Transform3D pt;
|
||||
if (skeleton->get_bone_parent(i) >= 0) {
|
||||
pt = get_bone_global_pose(skeleton->get_bone_parent(i));
|
||||
}
|
||||
local_poses.push_back(pt.affine_inverse() * bones[i].global_pose);
|
||||
}
|
||||
for (int i = 0; i < skeleton->get_bone_count(); i++) {
|
||||
if (!bones[i].physical_bone) {
|
||||
continue;
|
||||
}
|
||||
skeleton->set_bone_pose_position(i, local_poses[i].origin);
|
||||
skeleton->set_bone_pose_rotation(i, local_poses[i].basis.get_rotation_quaternion());
|
||||
skeleton->set_bone_pose_scale(i, local_poses[i].basis.get_scale());
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,15 +33,12 @@
|
|||
|
||||
#include "scene/3d/skeleton_modifier_3d.h"
|
||||
|
||||
#include "scene/3d/physics/physical_bone_3d.h"
|
||||
|
||||
class PhysicalBone3D;
|
||||
|
||||
class PhysicalBoneSimulator3D : public SkeletonModifier3D {
|
||||
GDCLASS(PhysicalBoneSimulator3D, SkeletonModifier3D);
|
||||
|
||||
bool simulating = false;
|
||||
bool enabled = true;
|
||||
|
||||
struct SimulatedBone {
|
||||
int parent;
|
||||
|
|
@ -74,6 +71,7 @@ protected:
|
|||
|
||||
void _bone_list_changed();
|
||||
void _pose_updated();
|
||||
void _bone_pose_updated(Skeleton3D *skeleton, int p_bone_id);
|
||||
|
||||
virtual void _process_modification() override;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
|
|
|
|||
|
|
@ -60,8 +60,13 @@ bool CharacterBody3D::move_and_slide() {
|
|||
|
||||
// We need to check the platform_rid object still exists before accessing.
|
||||
// A valid RID is no guarantee that the object has not been deleted.
|
||||
if (ObjectDB::get_instance(platform_object_id)) {
|
||||
//this approach makes sure there is less delay between the actual body velocity and the one we saved
|
||||
|
||||
// We can only perform the ObjectDB lifetime check on Object derived objects.
|
||||
// Note that physics also creates RIDs for non-Object derived objects, these cannot
|
||||
// be lifetime checked through ObjectDB, and therefore there is a still a vulnerability
|
||||
// to dangling RIDs (access after free) in this scenario.
|
||||
if (platform_object_id.is_null() || ObjectDB::get_instance(platform_object_id)) {
|
||||
// This approach makes sure there is less delay between the actual body velocity and the one we saved.
|
||||
bs = PhysicsServer3D::get_singleton()->body_get_direct_state(platform_rid);
|
||||
}
|
||||
|
||||
|
|
@ -464,14 +469,14 @@ void CharacterBody3D::apply_floor_snap() {
|
|||
_set_collision_direction(result, result_state, CollisionState(true, false, false));
|
||||
|
||||
if (result_state.floor) {
|
||||
if (floor_stop_on_slope) {
|
||||
// move and collide may stray the object a bit because of pre un-stucking,
|
||||
// so only ensure that motion happens on floor direction in this case.
|
||||
if (result.travel.length() > margin) {
|
||||
result.travel = up_direction * up_direction.dot(result.travel);
|
||||
} else {
|
||||
result.travel = Vector3();
|
||||
}
|
||||
// Ensure that we only move the body along the up axis, because
|
||||
// move_and_collide may stray the object a bit when getting it unstuck.
|
||||
// Canceling this motion should not affect move_and_slide, as previous
|
||||
// calls to move_and_collide already took care of freeing the body.
|
||||
if (result.travel.length() > margin) {
|
||||
result.travel = up_direction * up_direction.dot(result.travel);
|
||||
} else {
|
||||
result.travel = Vector3();
|
||||
}
|
||||
|
||||
parameters.from.origin += result.travel;
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ void CollisionObject3D::_notification(int p_what) {
|
|||
|
||||
if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) {
|
||||
Ref<World3D> world_ref = get_world_3d();
|
||||
ERR_FAIL_COND(!world_ref.is_valid());
|
||||
ERR_FAIL_COND(world_ref.is_null());
|
||||
RID space = world_ref->get_space();
|
||||
if (area) {
|
||||
PhysicsServer3D::get_singleton()->area_set_space(rid, space);
|
||||
|
|
@ -731,7 +731,7 @@ bool CollisionObject3D::get_capture_input_on_drag() const {
|
|||
}
|
||||
|
||||
PackedStringArray CollisionObject3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = Node3D::get_configuration_warnings();
|
||||
|
||||
if (shapes.is_empty()) {
|
||||
warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape3D or CollisionPolygon3D as a child to define its shape."));
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ void CollisionPolygon3D::_build_polygon() {
|
|||
|
||||
convex->set_points(cp);
|
||||
convex->set_margin(margin);
|
||||
convex->set_debug_color(debug_color);
|
||||
convex->set_debug_fill(debug_fill);
|
||||
collision_object->shape_owner_add_shape(owner_id, convex);
|
||||
collision_object->shape_owner_set_disabled(owner_id, disabled);
|
||||
}
|
||||
|
|
@ -157,6 +159,68 @@ bool CollisionPolygon3D::is_disabled() const {
|
|||
return disabled;
|
||||
}
|
||||
|
||||
Color CollisionPolygon3D::_get_default_debug_color() const {
|
||||
const SceneTree *st = SceneTree::get_singleton();
|
||||
return st ? st->get_debug_collisions_color() : Color(0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
void CollisionPolygon3D::set_debug_color(const Color &p_color) {
|
||||
if (debug_color == p_color) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug_color = p_color;
|
||||
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
Color CollisionPolygon3D::get_debug_color() const {
|
||||
return debug_color;
|
||||
}
|
||||
|
||||
void CollisionPolygon3D::set_debug_fill_enabled(bool p_enable) {
|
||||
if (debug_fill == p_enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug_fill = p_enable;
|
||||
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
bool CollisionPolygon3D::get_debug_fill_enabled() const {
|
||||
return debug_fill;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
||||
bool CollisionPolygon3D::_property_can_revert(const StringName &p_name) const {
|
||||
if (p_name == "debug_color") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CollisionPolygon3D::_property_get_revert(const StringName &p_name, Variant &r_property) const {
|
||||
if (p_name == "debug_color") {
|
||||
r_property = _get_default_debug_color();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CollisionPolygon3D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "debug_color") {
|
||||
if (debug_color == _get_default_debug_color()) {
|
||||
p_property.usage = PROPERTY_USAGE_DEFAULT & ~PROPERTY_USAGE_STORAGE;
|
||||
} else {
|
||||
p_property.usage = PROPERTY_USAGE_DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
real_t CollisionPolygon3D::get_margin() const {
|
||||
return margin;
|
||||
}
|
||||
|
|
@ -169,7 +233,7 @@ void CollisionPolygon3D::set_margin(real_t p_margin) {
|
|||
}
|
||||
|
||||
PackedStringArray CollisionPolygon3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = Node3D::get_configuration_warnings();
|
||||
|
||||
if (!Object::cast_to<CollisionObject3D>(get_parent())) {
|
||||
warnings.push_back(RTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node.\nPlease only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape."));
|
||||
|
|
@ -201,6 +265,12 @@ void CollisionPolygon3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &CollisionPolygon3D::set_disabled);
|
||||
ClassDB::bind_method(D_METHOD("is_disabled"), &CollisionPolygon3D::is_disabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_debug_color", "color"), &CollisionPolygon3D::set_debug_color);
|
||||
ClassDB::bind_method(D_METHOD("get_debug_color"), &CollisionPolygon3D::get_debug_color);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_enable_debug_fill", "enable"), &CollisionPolygon3D::set_debug_fill_enabled);
|
||||
ClassDB::bind_method(D_METHOD("get_enable_debug_fill"), &CollisionPolygon3D::get_debug_fill_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_margin", "margin"), &CollisionPolygon3D::set_margin);
|
||||
ClassDB::bind_method(D_METHOD("get_margin"), &CollisionPolygon3D::get_margin);
|
||||
|
||||
|
|
@ -210,8 +280,15 @@ void CollisionPolygon3D::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0.001,10,0.001,suffix:m"), "set_margin", "get_margin");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "debug_color"), "set_debug_color", "get_debug_color");
|
||||
// Default value depends on a project setting, override for doc generation purposes.
|
||||
ADD_PROPERTY_DEFAULT("debug_color", Color(0.0, 0.0, 0.0, 0.0));
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_fill"), "set_enable_debug_fill", "get_enable_debug_fill");
|
||||
}
|
||||
|
||||
CollisionPolygon3D::CollisionPolygon3D() {
|
||||
set_notify_local_transform(true);
|
||||
debug_color = _get_default_debug_color();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@
|
|||
#define COLLISION_POLYGON_3D_H
|
||||
|
||||
#include "scene/3d/node_3d.h"
|
||||
#include "scene/resources/3d/shape_3d.h"
|
||||
|
||||
class CollisionObject3D;
|
||||
class CollisionPolygon3D : public Node3D {
|
||||
|
|
@ -47,6 +46,11 @@ protected:
|
|||
uint32_t owner_id = 0;
|
||||
CollisionObject3D *collision_object = nullptr;
|
||||
|
||||
Color debug_color;
|
||||
bool debug_fill = true;
|
||||
|
||||
Color _get_default_debug_color() const;
|
||||
|
||||
bool disabled = false;
|
||||
|
||||
void _build_polygon();
|
||||
|
|
@ -59,6 +63,12 @@ protected:
|
|||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool _property_can_revert(const StringName &p_name) const;
|
||||
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
public:
|
||||
void set_depth(real_t p_depth);
|
||||
real_t get_depth() const;
|
||||
|
|
@ -69,6 +79,12 @@ public:
|
|||
void set_disabled(bool p_disabled);
|
||||
bool is_disabled() const;
|
||||
|
||||
void set_debug_color(const Color &p_color);
|
||||
Color get_debug_color() const;
|
||||
|
||||
void set_debug_fill_enabled(bool p_enable);
|
||||
bool get_debug_fill_enabled() const;
|
||||
|
||||
virtual AABB get_item_rect() const;
|
||||
|
||||
real_t get_margin() const;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@
|
|||
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/3d/physics/character_body_3d.h"
|
||||
#include "scene/3d/physics/physics_body_3d.h"
|
||||
#include "scene/3d/physics/vehicle_body_3d.h"
|
||||
#include "scene/resources/3d/concave_polygon_shape_3d.h"
|
||||
#include "scene/resources/3d/convex_polygon_shape_3d.h"
|
||||
|
|
@ -87,6 +86,7 @@ void CollisionShape3D::_notification(int p_what) {
|
|||
if (shape.is_valid()) {
|
||||
collision_object->shape_owner_add_shape(owner_id, shape);
|
||||
}
|
||||
|
||||
_update_in_shape_owner();
|
||||
}
|
||||
} break;
|
||||
|
|
@ -120,14 +120,14 @@ void CollisionShape3D::resource_changed(Ref<Resource> res) {
|
|||
#endif
|
||||
|
||||
PackedStringArray CollisionShape3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = Node3D::get_configuration_warnings();
|
||||
|
||||
CollisionObject3D *col_object = Object::cast_to<CollisionObject3D>(get_parent());
|
||||
if (col_object == nullptr) {
|
||||
warnings.push_back(RTR("CollisionShape3D only serves to provide a collision shape to a CollisionObject3D derived node.\nPlease only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape."));
|
||||
}
|
||||
|
||||
if (!shape.is_valid()) {
|
||||
if (shape.is_null()) {
|
||||
warnings.push_back(RTR("A shape must be provided for CollisionShape3D to function. Please create a shape resource for it."));
|
||||
}
|
||||
|
||||
|
|
@ -166,11 +166,24 @@ void CollisionShape3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("get_shape"), &CollisionShape3D::get_shape);
|
||||
ClassDB::bind_method(D_METHOD("set_disabled", "enable"), &CollisionShape3D::set_disabled);
|
||||
ClassDB::bind_method(D_METHOD("is_disabled"), &CollisionShape3D::is_disabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("make_convex_from_siblings"), &CollisionShape3D::make_convex_from_siblings);
|
||||
ClassDB::set_method_flags("CollisionShape3D", "make_convex_from_siblings", METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape3D"), "set_shape", "get_shape");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_debug_color", "color"), &CollisionShape3D::set_debug_color);
|
||||
ClassDB::bind_method(D_METHOD("get_debug_color"), &CollisionShape3D::get_debug_color);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_enable_debug_fill", "enable"), &CollisionShape3D::set_debug_fill_enabled);
|
||||
ClassDB::bind_method(D_METHOD("get_enable_debug_fill"), &CollisionShape3D::get_debug_fill_enabled);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "debug_color"), "set_debug_color", "get_debug_color");
|
||||
// Default value depends on a project setting, override for doc generation purposes.
|
||||
ADD_PROPERTY_DEFAULT("debug_color", Color(0.0, 0.0, 0.0, 0.0));
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_fill"), "set_enable_debug_fill", "get_enable_debug_fill");
|
||||
}
|
||||
|
||||
void CollisionShape3D::set_shape(const Ref<Shape3D> &p_shape) {
|
||||
|
|
@ -178,11 +191,27 @@ void CollisionShape3D::set_shape(const Ref<Shape3D> &p_shape) {
|
|||
return;
|
||||
}
|
||||
if (shape.is_valid()) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
shape->disconnect_changed(callable_mp(this, &CollisionShape3D::_shape_changed));
|
||||
#endif // DEBUG_ENABLED
|
||||
shape->disconnect_changed(callable_mp((Node3D *)this, &Node3D::update_gizmos));
|
||||
}
|
||||
shape = p_shape;
|
||||
if (shape.is_valid()) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (shape->are_debug_properties_edited()) {
|
||||
set_debug_color(shape->get_debug_color());
|
||||
set_debug_fill_enabled(shape->get_debug_fill());
|
||||
} else {
|
||||
shape->set_debug_color(debug_color);
|
||||
shape->set_debug_fill(debug_fill);
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
shape->connect_changed(callable_mp((Node3D *)this, &Node3D::update_gizmos));
|
||||
#ifdef DEBUG_ENABLED
|
||||
shape->connect_changed(callable_mp(this, &CollisionShape3D::_shape_changed));
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
update_gizmos();
|
||||
if (collision_object) {
|
||||
|
|
@ -215,9 +244,85 @@ bool CollisionShape3D::is_disabled() const {
|
|||
return disabled;
|
||||
}
|
||||
|
||||
Color CollisionShape3D::_get_default_debug_color() const {
|
||||
const SceneTree *st = SceneTree::get_singleton();
|
||||
return st ? st->get_debug_collisions_color() : Color(0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
void CollisionShape3D::set_debug_color(const Color &p_color) {
|
||||
if (debug_color == p_color) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug_color = p_color;
|
||||
|
||||
if (shape.is_valid()) {
|
||||
shape->set_debug_color(p_color);
|
||||
}
|
||||
}
|
||||
|
||||
Color CollisionShape3D::get_debug_color() const {
|
||||
return debug_color;
|
||||
}
|
||||
|
||||
void CollisionShape3D::set_debug_fill_enabled(bool p_enable) {
|
||||
if (debug_fill == p_enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug_fill = p_enable;
|
||||
|
||||
if (shape.is_valid()) {
|
||||
shape->set_debug_fill(p_enable);
|
||||
}
|
||||
}
|
||||
|
||||
bool CollisionShape3D::get_debug_fill_enabled() const {
|
||||
return debug_fill;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
||||
bool CollisionShape3D::_property_can_revert(const StringName &p_name) const {
|
||||
if (p_name == "debug_color") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CollisionShape3D::_property_get_revert(const StringName &p_name, Variant &r_property) const {
|
||||
if (p_name == "debug_color") {
|
||||
r_property = _get_default_debug_color();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CollisionShape3D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "debug_color") {
|
||||
if (debug_color == _get_default_debug_color()) {
|
||||
p_property.usage = PROPERTY_USAGE_DEFAULT & ~PROPERTY_USAGE_STORAGE;
|
||||
} else {
|
||||
p_property.usage = PROPERTY_USAGE_DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollisionShape3D::_shape_changed() {
|
||||
if (shape->get_debug_color() != debug_color) {
|
||||
set_debug_color(shape->get_debug_color());
|
||||
}
|
||||
if (shape->get_debug_fill() != debug_fill) {
|
||||
set_debug_fill_enabled(shape->get_debug_fill());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
CollisionShape3D::CollisionShape3D() {
|
||||
//indicator = RenderingServer::get_singleton()->mesh_create();
|
||||
set_notify_local_transform(true);
|
||||
debug_color = _get_default_debug_color();
|
||||
}
|
||||
|
||||
CollisionShape3D::~CollisionShape3D() {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ class CollisionShape3D : public Node3D {
|
|||
uint32_t owner_id = 0;
|
||||
CollisionObject3D *collision_object = nullptr;
|
||||
|
||||
Color debug_color;
|
||||
bool debug_fill = true;
|
||||
|
||||
Color _get_default_debug_color() const;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void _shape_changed();
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void resource_changed(Ref<Resource> res);
|
||||
#endif
|
||||
|
|
@ -55,6 +64,12 @@ protected:
|
|||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool _property_can_revert(const StringName &p_name) const;
|
||||
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
public:
|
||||
void make_convex_from_siblings();
|
||||
|
||||
|
|
@ -64,6 +79,12 @@ public:
|
|||
void set_disabled(bool p_disabled);
|
||||
bool is_disabled() const;
|
||||
|
||||
void set_debug_color(const Color &p_color);
|
||||
Color get_debug_color() const;
|
||||
|
||||
void set_debug_fill_enabled(bool p_enable);
|
||||
bool get_debug_fill_enabled() const;
|
||||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
CollisionShape3D();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
#include "kinematic_collision_3d.h"
|
||||
|
||||
#include "scene/3d/physics/character_body_3d.h"
|
||||
#include "scene/3d/physics/physics_body_3d.h"
|
||||
|
||||
Vector3 KinematicCollision3D::get_travel() const {
|
||||
|
|
|
|||
|
|
@ -34,9 +34,6 @@
|
|||
#include "core/object/ref_counted.h"
|
||||
#include "servers/physics_server_3d.h"
|
||||
|
||||
class CharacterBody3D;
|
||||
class PhysicsBody3D;
|
||||
|
||||
class KinematicCollision3D : public RefCounted {
|
||||
GDCLASS(KinematicCollision3D, RefCounted);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@
|
|||
/**************************************************************************/
|
||||
|
||||
#include "physical_bone_3d.h"
|
||||
|
||||
#include "scene/3d/physical_bone_simulator_3d.h"
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#endif //_DISABLE_DEPRECATED
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@
|
|||
#ifndef PHYSICAL_BONE_3D_H
|
||||
#define PHYSICAL_BONE_3D_H
|
||||
|
||||
#include "scene/3d/physical_bone_simulator_3d.h"
|
||||
#include "scene/3d/physics/physics_body_3d.h"
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
|
||||
class PhysicalBoneSimulator3D;
|
||||
|
||||
|
|
|
|||
|
|
@ -167,8 +167,7 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion
|
|||
PhysicsServer3D::MotionResult *r = nullptr;
|
||||
PhysicsServer3D::MotionResult temp_result;
|
||||
if (r_collision.is_valid()) {
|
||||
// Needs const_cast because method bindings don't support non-const Ref.
|
||||
r = const_cast<PhysicsServer3D::MotionResult *>(&r_collision->result);
|
||||
r = &r_collision->result;
|
||||
} else {
|
||||
r = &temp_result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@
|
|||
#ifndef PHYSICS_BODY_3D_H
|
||||
#define PHYSICS_BODY_3D_H
|
||||
|
||||
#include "core/templates/vset.h"
|
||||
#include "scene/3d/physics/collision_object_3d.h"
|
||||
#include "scene/3d/physics/kinematic_collision_3d.h"
|
||||
#include "scene/resources/physics_material.h"
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
#include "ray_cast_3d.h"
|
||||
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/3d/physics/collision_object_3d.h"
|
||||
|
||||
void RayCast3D::set_target_position(const Vector3 &p_point) {
|
||||
|
|
@ -464,12 +463,12 @@ void RayCast3D::_create_debug_shape() {
|
|||
}
|
||||
|
||||
if (debug_mesh.is_null()) {
|
||||
debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
||||
debug_mesh.instantiate();
|
||||
}
|
||||
}
|
||||
|
||||
void RayCast3D::_update_debug_shape_material(bool p_check_collision) {
|
||||
if (!debug_material.is_valid()) {
|
||||
if (debug_material.is_null()) {
|
||||
Ref<StandardMaterial3D> material = memnew(StandardMaterial3D);
|
||||
debug_material = material;
|
||||
|
||||
|
|
|
|||
|
|
@ -659,7 +659,7 @@ void RigidBody3D::_reload_physics_characteristics() {
|
|||
}
|
||||
|
||||
PackedStringArray RigidBody3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = CollisionObject3D::get_configuration_warnings();
|
||||
PackedStringArray warnings = PhysicsBody3D::get_configuration_warnings();
|
||||
|
||||
Vector3 scale = get_transform().get_basis().get_scale();
|
||||
if (ABS(scale.x - 1.0) > 0.05 || ABS(scale.y - 1.0) > 0.05 || ABS(scale.z - 1.0) > 0.05) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@
|
|||
#ifndef RIGID_BODY_3D_H
|
||||
#define RIGID_BODY_3D_H
|
||||
|
||||
#include "scene/3d/physics/static_body_3d.h"
|
||||
#include "core/templates/vset.h"
|
||||
#include "scene/3d/physics/physics_body_3d.h"
|
||||
|
||||
class RigidBody3D : public PhysicsBody3D {
|
||||
GDCLASS(RigidBody3D, PhysicsBody3D);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
#include "shape_cast_3d.h"
|
||||
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/3d/physics/collision_object_3d.h"
|
||||
#include "scene/resources/3d/concave_polygon_shape_3d.h"
|
||||
|
||||
|
|
@ -157,7 +156,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 +168,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 +474,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) {
|
||||
|
|
@ -499,7 +498,7 @@ void ShapeCast3D::_update_debug_shape_vertices() {
|
|||
debug_shape_vertices.clear();
|
||||
debug_line_vertices.clear();
|
||||
|
||||
if (!shape.is_null()) {
|
||||
if (shape.is_valid()) {
|
||||
debug_shape_vertices.append_array(shape->get_debug_mesh_lines());
|
||||
for (int i = 0; i < debug_shape_vertices.size(); i++) {
|
||||
debug_shape_vertices.set(i, debug_shape_vertices[i] + Vector3(target_position * get_closest_collision_safe_fraction()));
|
||||
|
|
@ -546,12 +545,12 @@ void ShapeCast3D::_create_debug_shape() {
|
|||
}
|
||||
|
||||
if (debug_mesh.is_null()) {
|
||||
debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
||||
debug_mesh.instantiate();
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeCast3D::_update_debug_shape_material(bool p_check_collision) {
|
||||
if (!debug_material.is_valid()) {
|
||||
if (debug_material.is_null()) {
|
||||
Ref<StandardMaterial3D> material = memnew(StandardMaterial3D);
|
||||
debug_material = material;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,24 @@
|
|||
|
||||
#include "static_body_3d.h"
|
||||
|
||||
#include "core/math/convex_hull.h"
|
||||
#include "scene/resources/3d/box_shape_3d.h"
|
||||
#include "scene/resources/3d/capsule_shape_3d.h"
|
||||
#include "scene/resources/3d/concave_polygon_shape_3d.h"
|
||||
#include "scene/resources/3d/convex_polygon_shape_3d.h"
|
||||
#include "scene/resources/3d/cylinder_shape_3d.h"
|
||||
#include "scene/resources/3d/height_map_shape_3d.h"
|
||||
#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h"
|
||||
#include "scene/resources/3d/primitive_meshes.h"
|
||||
#include "scene/resources/3d/shape_3d.h"
|
||||
#include "scene/resources/3d/sphere_shape_3d.h"
|
||||
#include "scene/resources/3d/world_boundary_shape_3d.h"
|
||||
#include "scene/resources/navigation_mesh.h"
|
||||
#include "servers/navigation_server_3d.h"
|
||||
|
||||
Callable StaticBody3D::_navmesh_source_geometry_parsing_callback;
|
||||
RID StaticBody3D::_navmesh_source_geometry_parser;
|
||||
|
||||
void StaticBody3D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) {
|
||||
if (physics_material_override.is_valid()) {
|
||||
physics_material_override->disconnect_changed(callable_mp(this, &StaticBody3D::_reload_physics_characteristics));
|
||||
|
|
@ -77,6 +95,138 @@ void StaticBody3D::_reload_physics_characteristics() {
|
|||
}
|
||||
}
|
||||
|
||||
void StaticBody3D::navmesh_parse_init() {
|
||||
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
|
||||
if (!_navmesh_source_geometry_parser.is_valid()) {
|
||||
_navmesh_source_geometry_parsing_callback = callable_mp_static(&StaticBody3D::navmesh_parse_source_geometry);
|
||||
_navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create();
|
||||
NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void StaticBody3D::navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
|
||||
StaticBody3D *static_body = Object::cast_to<StaticBody3D>(p_node);
|
||||
|
||||
if (static_body == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
|
||||
uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask();
|
||||
|
||||
if ((parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) && (static_body->get_collision_layer() & parsed_collision_mask)) {
|
||||
List<uint32_t> shape_owners;
|
||||
static_body->get_shape_owners(&shape_owners);
|
||||
for (uint32_t shape_owner : shape_owners) {
|
||||
if (static_body->is_shape_owner_disabled(shape_owner)) {
|
||||
continue;
|
||||
}
|
||||
const int shape_count = static_body->shape_owner_get_shape_count(shape_owner);
|
||||
for (int shape_index = 0; shape_index < shape_count; shape_index++) {
|
||||
Ref<Shape3D> s = static_body->shape_owner_get_shape(shape_owner, shape_index);
|
||||
if (s.is_null()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Transform3D transform = static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner);
|
||||
|
||||
BoxShape3D *box = Object::cast_to<BoxShape3D>(*s);
|
||||
if (box) {
|
||||
Array arr;
|
||||
arr.resize(RS::ARRAY_MAX);
|
||||
BoxMesh::create_mesh_array(arr, box->get_size());
|
||||
p_source_geometry_data->add_mesh_array(arr, transform);
|
||||
}
|
||||
|
||||
CapsuleShape3D *capsule = Object::cast_to<CapsuleShape3D>(*s);
|
||||
if (capsule) {
|
||||
Array arr;
|
||||
arr.resize(RS::ARRAY_MAX);
|
||||
CapsuleMesh::create_mesh_array(arr, capsule->get_radius(), capsule->get_height());
|
||||
p_source_geometry_data->add_mesh_array(arr, transform);
|
||||
}
|
||||
|
||||
CylinderShape3D *cylinder = Object::cast_to<CylinderShape3D>(*s);
|
||||
if (cylinder) {
|
||||
Array arr;
|
||||
arr.resize(RS::ARRAY_MAX);
|
||||
CylinderMesh::create_mesh_array(arr, cylinder->get_radius(), cylinder->get_radius(), cylinder->get_height());
|
||||
p_source_geometry_data->add_mesh_array(arr, transform);
|
||||
}
|
||||
|
||||
SphereShape3D *sphere = Object::cast_to<SphereShape3D>(*s);
|
||||
if (sphere) {
|
||||
Array arr;
|
||||
arr.resize(RS::ARRAY_MAX);
|
||||
SphereMesh::create_mesh_array(arr, sphere->get_radius(), sphere->get_radius() * 2.0);
|
||||
p_source_geometry_data->add_mesh_array(arr, transform);
|
||||
}
|
||||
|
||||
ConcavePolygonShape3D *concave_polygon = Object::cast_to<ConcavePolygonShape3D>(*s);
|
||||
if (concave_polygon) {
|
||||
p_source_geometry_data->add_faces(concave_polygon->get_faces(), transform);
|
||||
}
|
||||
|
||||
ConvexPolygonShape3D *convex_polygon = Object::cast_to<ConvexPolygonShape3D>(*s);
|
||||
if (convex_polygon) {
|
||||
Vector<Vector3> varr = Variant(convex_polygon->get_points());
|
||||
Geometry3D::MeshData md;
|
||||
|
||||
Error err = ConvexHullComputer::convex_hull(varr, md);
|
||||
|
||||
if (err == OK) {
|
||||
PackedVector3Array faces;
|
||||
|
||||
for (const Geometry3D::MeshData::Face &face : md.faces) {
|
||||
for (uint32_t k = 2; k < face.indices.size(); ++k) {
|
||||
faces.push_back(md.vertices[face.indices[0]]);
|
||||
faces.push_back(md.vertices[face.indices[k - 1]]);
|
||||
faces.push_back(md.vertices[face.indices[k]]);
|
||||
}
|
||||
}
|
||||
|
||||
p_source_geometry_data->add_faces(faces, transform);
|
||||
}
|
||||
}
|
||||
|
||||
HeightMapShape3D *heightmap_shape = Object::cast_to<HeightMapShape3D>(*s);
|
||||
if (heightmap_shape) {
|
||||
int heightmap_depth = heightmap_shape->get_map_depth();
|
||||
int heightmap_width = heightmap_shape->get_map_width();
|
||||
|
||||
if (heightmap_depth >= 2 && heightmap_width >= 2) {
|
||||
const Vector<real_t> &map_data = heightmap_shape->get_map_data();
|
||||
|
||||
Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1);
|
||||
Vector3 start = Vector3(heightmap_gridsize.x, 0, heightmap_gridsize.y) * -0.5;
|
||||
|
||||
Vector<Vector3> vertex_array;
|
||||
vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6);
|
||||
Vector3 *vertex_array_ptrw = vertex_array.ptrw();
|
||||
const real_t *map_data_ptr = map_data.ptr();
|
||||
int vertex_index = 0;
|
||||
|
||||
for (int d = 0; d < heightmap_depth - 1; d++) {
|
||||
for (int w = 0; w < heightmap_width - 1; w++) {
|
||||
vertex_array_ptrw[vertex_index] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + w], d);
|
||||
vertex_array_ptrw[vertex_index + 1] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d);
|
||||
vertex_array_ptrw[vertex_index + 2] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1);
|
||||
vertex_array_ptrw[vertex_index + 3] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d);
|
||||
vertex_array_ptrw[vertex_index + 4] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + heightmap_width + w + 1], d + 1);
|
||||
vertex_array_ptrw[vertex_index + 5] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1);
|
||||
vertex_index += 6;
|
||||
}
|
||||
}
|
||||
if (vertex_array.size() > 0) {
|
||||
p_source_geometry_data->add_faces(vertex_array, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StaticBody3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody3D::set_constant_linear_velocity);
|
||||
ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody3D::set_constant_angular_velocity);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@
|
|||
|
||||
#include "scene/3d/physics/physics_body_3d.h"
|
||||
|
||||
class NavigationMesh;
|
||||
class NavigationMeshSourceGeometryData3D;
|
||||
|
||||
class StaticBody3D : public PhysicsBody3D {
|
||||
GDCLASS(StaticBody3D, PhysicsBody3D);
|
||||
|
||||
|
|
@ -59,6 +62,13 @@ public:
|
|||
|
||||
private:
|
||||
void _reload_physics_characteristics();
|
||||
|
||||
static Callable _navmesh_source_geometry_parsing_callback;
|
||||
static RID _navmesh_source_geometry_parser;
|
||||
|
||||
public:
|
||||
static void navmesh_parse_init();
|
||||
static void navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
|
||||
};
|
||||
|
||||
#endif // STATIC_BODY_3D_H
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ void VehicleWheel3D::_notification(int p_what) {
|
|||
}
|
||||
|
||||
PackedStringArray VehicleWheel3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = Node3D::get_configuration_warnings();
|
||||
|
||||
if (!Object::cast_to<VehicleBody3D>(get_parent())) {
|
||||
warnings.push_back(RTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D."));
|
||||
|
|
@ -219,6 +219,14 @@ bool VehicleWheel3D::is_in_contact() const {
|
|||
return m_raycastInfo.m_isInContact;
|
||||
}
|
||||
|
||||
Vector3 VehicleWheel3D::get_contact_point() const {
|
||||
return m_raycastInfo.m_contactPointWS;
|
||||
}
|
||||
|
||||
Vector3 VehicleWheel3D::get_contact_normal() const {
|
||||
return m_raycastInfo.m_contactNormalWS;
|
||||
}
|
||||
|
||||
Node3D *VehicleWheel3D::get_contact_body() const {
|
||||
return m_raycastInfo.m_groundObject;
|
||||
}
|
||||
|
|
@ -256,6 +264,8 @@ void VehicleWheel3D::_bind_methods() {
|
|||
|
||||
ClassDB::bind_method(D_METHOD("is_in_contact"), &VehicleWheel3D::is_in_contact);
|
||||
ClassDB::bind_method(D_METHOD("get_contact_body"), &VehicleWheel3D::get_contact_body);
|
||||
ClassDB::bind_method(D_METHOD("get_contact_point"), &VehicleWheel3D::get_contact_point);
|
||||
ClassDB::bind_method(D_METHOD("get_contact_normal"), &VehicleWheel3D::get_contact_normal);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_roll_influence", "roll_influence"), &VehicleWheel3D::set_roll_influence);
|
||||
ClassDB::bind_method(D_METHOD("get_roll_influence"), &VehicleWheel3D::get_roll_influence);
|
||||
|
|
@ -287,11 +297,11 @@ void VehicleWheel3D::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wheel_friction_slip"), "set_friction_slip", "get_friction_slip");
|
||||
ADD_GROUP("Suspension", "suspension_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_travel", PROPERTY_HINT_NONE, "suffix:m"), "set_suspension_travel", "get_suspension_travel");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_stiffness"), "set_suspension_stiffness", "get_suspension_stiffness");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_stiffness", PROPERTY_HINT_NONE, U"suffix:N/mm"), "set_suspension_stiffness", "get_suspension_stiffness");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_max_force", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m/s\u00B2 (N)"), "set_suspension_max_force", "get_suspension_max_force");
|
||||
ADD_GROUP("Damping", "damping_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_compression"), "set_damping_compression", "get_damping_compression");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_relaxation"), "set_damping_relaxation", "get_damping_relaxation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_compression", PROPERTY_HINT_NONE, U"suffix:N\u22C5s/mm"), "set_damping_compression", "get_damping_compression");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_relaxation", PROPERTY_HINT_NONE, U"suffix:N\u22C5s/mm"), "set_damping_relaxation", "get_damping_relaxation");
|
||||
}
|
||||
|
||||
void VehicleWheel3D::set_engine_force(real_t p_engine_force) {
|
||||
|
|
@ -532,7 +542,7 @@ void VehicleBody3D::_resolve_single_bilateral(PhysicsDirectBodyState3D *s, const
|
|||
if (body2) {
|
||||
rel_pos2 = pos2 - body2->get_global_transform().origin;
|
||||
}
|
||||
//this jacobian entry could be re-used for all iterations
|
||||
// This Jacobian entry could be reused for all iterations.
|
||||
|
||||
Vector3 vel1 = s->get_linear_velocity() + (s->get_angular_velocity()).cross(rel_pos1); // * mPos);
|
||||
Vector3 vel2;
|
||||
|
|
|
|||
|
|
@ -130,6 +130,10 @@ public:
|
|||
|
||||
bool is_in_contact() const;
|
||||
|
||||
Vector3 get_contact_point() const;
|
||||
|
||||
Vector3 get_contact_normal() const;
|
||||
|
||||
Node3D *get_contact_body() const;
|
||||
|
||||
void set_roll_influence(real_t p_value);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,16 @@ float ReflectionProbe::get_intensity() const {
|
|||
return intensity;
|
||||
}
|
||||
|
||||
void ReflectionProbe::set_blend_distance(float p_blend_distance) {
|
||||
blend_distance = p_blend_distance;
|
||||
RS::get_singleton()->reflection_probe_set_blend_distance(probe, p_blend_distance);
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
float ReflectionProbe::get_blend_distance() const {
|
||||
return blend_distance;
|
||||
}
|
||||
|
||||
void ReflectionProbe::set_ambient_mode(AmbientMode p_mode) {
|
||||
ambient_mode = p_mode;
|
||||
RS::get_singleton()->reflection_probe_set_ambient_mode(probe, RS::ReflectionProbeAmbientMode(p_mode));
|
||||
|
|
@ -185,8 +195,8 @@ ReflectionProbe::UpdateMode ReflectionProbe::get_update_mode() const {
|
|||
|
||||
AABB ReflectionProbe::get_aabb() const {
|
||||
AABB aabb;
|
||||
aabb.position = -origin_offset;
|
||||
aabb.size = origin_offset + size / 2;
|
||||
aabb.position = -size / 2;
|
||||
aabb.size = size;
|
||||
return aabb;
|
||||
}
|
||||
|
||||
|
|
@ -202,6 +212,9 @@ void ReflectionProbe::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_intensity", "intensity"), &ReflectionProbe::set_intensity);
|
||||
ClassDB::bind_method(D_METHOD("get_intensity"), &ReflectionProbe::get_intensity);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_blend_distance", "blend_distance"), &ReflectionProbe::set_blend_distance);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_distance"), &ReflectionProbe::get_blend_distance);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_ambient_mode", "ambient"), &ReflectionProbe::set_ambient_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_ambient_mode"), &ReflectionProbe::get_ambient_mode);
|
||||
|
||||
|
|
@ -243,6 +256,7 @@ void ReflectionProbe::_bind_methods() {
|
|||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Once (Fast),Always (Slow)"), "set_update_mode", "get_update_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "intensity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_intensity", "get_intensity");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "blend_distance", PROPERTY_HINT_RANGE, "0,8,0.01,or_greater,suffix:m"), "set_blend_distance", "get_blend_distance");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,16384,0.1,or_greater,exp,suffix:m"), "set_max_distance", "get_max_distance");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "origin_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_origin_offset", "get_origin_offset");
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ public:
|
|||
private:
|
||||
RID probe;
|
||||
float intensity = 1.0;
|
||||
float blend_distance = 1.0;
|
||||
float max_distance = 0.0;
|
||||
Vector3 size = Vector3(20, 20, 20);
|
||||
Vector3 origin_offset = Vector3(0, 0, 0);
|
||||
|
|
@ -78,6 +79,9 @@ public:
|
|||
void set_intensity(float p_intensity);
|
||||
float get_intensity() const;
|
||||
|
||||
void set_blend_distance(float p_blend_distance);
|
||||
float get_blend_distance() const;
|
||||
|
||||
void set_ambient_mode(AmbientMode p_mode);
|
||||
AmbientMode get_ambient_mode() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,16 @@ void RemoteTransform3D::_notification(int p_what) {
|
|||
_update_cache();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
|
||||
if (cache.is_valid()) {
|
||||
_update_remote();
|
||||
Node3D *n = Object::cast_to<Node3D>(ObjectDB::get_instance(cache));
|
||||
if (n) {
|
||||
n->reset_physics_interpolation();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
|
||||
case NOTIFICATION_TRANSFORM_CHANGED: {
|
||||
if (!is_inside_tree()) {
|
||||
|
|
@ -201,7 +211,7 @@ void RemoteTransform3D::force_update_cache() {
|
|||
}
|
||||
|
||||
PackedStringArray RemoteTransform3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = Node3D::get_configuration_warnings();
|
||||
|
||||
if (!has_node(remote_node) || !Object::cast_to<Node3D>(get_node(remote_node))) {
|
||||
warnings.push_back(RTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work."));
|
||||
|
|
|
|||
478
engine/scene/3d/retarget_modifier_3d.cpp
Normal file
478
engine/scene/3d/retarget_modifier_3d.cpp
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
/**************************************************************************/
|
||||
/* retarget_modifier_3d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "retarget_modifier_3d.h"
|
||||
|
||||
PackedStringArray RetargetModifier3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings();
|
||||
if (child_skeletons.is_empty()) {
|
||||
warnings.push_back(RTR("There is no child Skeleton3D!"));
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
/// Caching
|
||||
|
||||
void RetargetModifier3D::_profile_changed(Ref<SkeletonProfile> p_old, Ref<SkeletonProfile> p_new) {
|
||||
if (p_old.is_valid() && p_old->is_connected(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset))) {
|
||||
p_old->disconnect(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset));
|
||||
}
|
||||
profile = p_new;
|
||||
if (p_new.is_valid() && !p_new->is_connected(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset))) {
|
||||
p_new->connect(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset));
|
||||
}
|
||||
cache_rests_with_reset();
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) {
|
||||
if (p_old && p_old->is_connected(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests))) {
|
||||
p_old->disconnect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests));
|
||||
}
|
||||
if (p_new && !p_new->is_connected(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests))) {
|
||||
p_new->connect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests));
|
||||
}
|
||||
cache_rests();
|
||||
}
|
||||
|
||||
void RetargetModifier3D::cache_rests_with_reset() {
|
||||
_reset_child_skeleton_poses();
|
||||
cache_rests();
|
||||
}
|
||||
|
||||
void RetargetModifier3D::cache_rests() {
|
||||
source_bone_ids.clear();
|
||||
|
||||
Skeleton3D *source_skeleton = get_skeleton();
|
||||
if (profile.is_null() || !source_skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
PackedStringArray bone_names = profile->get_bone_names();
|
||||
for (const String &E : bone_names) {
|
||||
source_bone_ids.push_back(source_skeleton->find_bone(E));
|
||||
}
|
||||
|
||||
for (int i = 0; i < child_skeletons.size(); i++) {
|
||||
_update_child_skeleton_rests(i);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<RetargetModifier3D::RetargetBoneInfo> RetargetModifier3D::cache_bone_global_rests(Skeleton3D *p_skeleton) {
|
||||
// Retarget global pose in model space:
|
||||
// tgt_global_pose.basis = src_global_pose.basis * src_rest.basis.inv * src_parent_global_rest.basis.inv * tgt_parent_global_rest.basis * tgt_rest.basis
|
||||
// tgt_global_pose.origin = src_global_pose.origin
|
||||
Skeleton3D *source_skeleton = get_skeleton();
|
||||
Vector<RetargetBoneInfo> bone_rests;
|
||||
if (profile.is_null() || !source_skeleton) {
|
||||
return bone_rests;
|
||||
}
|
||||
PackedStringArray bone_names = profile->get_bone_names();
|
||||
for (const String &E : bone_names) {
|
||||
RetargetBoneInfo rbi;
|
||||
int source_bone_id = source_skeleton->find_bone(E);
|
||||
if (source_bone_id >= 0) {
|
||||
Transform3D parent_global_rest;
|
||||
int bone_parent = source_skeleton->get_bone_parent(source_bone_id);
|
||||
if (bone_parent >= 0) {
|
||||
parent_global_rest = source_skeleton->get_bone_global_rest(bone_parent);
|
||||
}
|
||||
rbi.post_basis = source_skeleton->get_bone_rest(source_bone_id).basis.inverse() * parent_global_rest.basis.inverse();
|
||||
}
|
||||
int target_bone_id = p_skeleton->find_bone(E);
|
||||
rbi.bone_id = target_bone_id;
|
||||
if (target_bone_id >= 0) {
|
||||
Transform3D parent_global_rest;
|
||||
int bone_parent = p_skeleton->get_bone_parent(target_bone_id);
|
||||
if (bone_parent >= 0) {
|
||||
parent_global_rest = p_skeleton->get_bone_global_rest(bone_parent);
|
||||
}
|
||||
rbi.post_basis = rbi.post_basis * parent_global_rest.basis * p_skeleton->get_bone_rest(target_bone_id).basis;
|
||||
}
|
||||
bone_rests.push_back(rbi);
|
||||
}
|
||||
return bone_rests;
|
||||
}
|
||||
|
||||
Vector<RetargetModifier3D::RetargetBoneInfo> RetargetModifier3D::cache_bone_rests(Skeleton3D *p_skeleton) {
|
||||
// Retarget pose in model space:
|
||||
// tgt_pose.basis = tgt_parent_global_rest.basis.inv * src_parent_global_rest.basis * src_pose.basis * src_rest.basis.inv * src_parent_global_rest.basis.inv * tgt_parent_global_rest.basis * tgt_rest.basis
|
||||
// tgt_pose.origin = tgt_parent_global_rest.basis.inv.xform(src_parent_global_rest.basis.xform(src_pose.origin - src_rest.origin)) + tgt_rest.origin
|
||||
Skeleton3D *source_skeleton = get_skeleton();
|
||||
Vector<RetargetBoneInfo> bone_rests;
|
||||
if (profile.is_null() || !source_skeleton) {
|
||||
return bone_rests;
|
||||
}
|
||||
PackedStringArray bone_names = profile->get_bone_names();
|
||||
for (const String &E : bone_names) {
|
||||
RetargetBoneInfo rbi;
|
||||
int source_bone_id = source_skeleton->find_bone(E);
|
||||
if (source_bone_id >= 0) {
|
||||
Transform3D parent_global_rest;
|
||||
int bone_parent = source_skeleton->get_bone_parent(source_bone_id);
|
||||
if (bone_parent >= 0) {
|
||||
parent_global_rest = source_skeleton->get_bone_global_rest(bone_parent);
|
||||
}
|
||||
rbi.pre_basis = parent_global_rest.basis;
|
||||
rbi.post_basis = source_skeleton->get_bone_rest(source_bone_id).basis.inverse() * parent_global_rest.basis.inverse();
|
||||
}
|
||||
|
||||
int target_bone_id = p_skeleton->find_bone(E);
|
||||
rbi.bone_id = target_bone_id;
|
||||
if (target_bone_id >= 0) {
|
||||
Transform3D parent_global_rest;
|
||||
int bone_parent = p_skeleton->get_bone_parent(target_bone_id);
|
||||
if (bone_parent >= 0) {
|
||||
parent_global_rest = p_skeleton->get_bone_global_rest(bone_parent);
|
||||
}
|
||||
rbi.pre_basis = parent_global_rest.basis.inverse() * rbi.pre_basis;
|
||||
rbi.post_basis = rbi.post_basis * parent_global_rest.basis * p_skeleton->get_bone_rest(target_bone_id).basis;
|
||||
}
|
||||
bone_rests.push_back(rbi);
|
||||
}
|
||||
return bone_rests;
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_update_child_skeleton_rests(int p_child_skeleton_idx) {
|
||||
ERR_FAIL_INDEX(p_child_skeleton_idx, child_skeletons.size());
|
||||
Skeleton3D *c = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(child_skeletons[p_child_skeleton_idx].skeleton_id));
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
if (use_global_pose) {
|
||||
child_skeletons.write[p_child_skeleton_idx].humanoid_bone_rests = cache_bone_global_rests(c);
|
||||
} else {
|
||||
child_skeletons.write[p_child_skeleton_idx].humanoid_bone_rests = cache_bone_rests(c);
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_update_child_skeletons() {
|
||||
_reset_child_skeletons();
|
||||
|
||||
for (int i = 0; i < get_child_count(); i++) {
|
||||
RetargetInfo ri;
|
||||
Skeleton3D *c = Object::cast_to<Skeleton3D>(get_child(i));
|
||||
if (c) {
|
||||
int id = child_skeletons.size();
|
||||
ri.skeleton_id = c->get_instance_id();
|
||||
child_skeletons.push_back(ri);
|
||||
c->connect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::_update_child_skeleton_rests).bind(id));
|
||||
}
|
||||
}
|
||||
|
||||
cache_rests();
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_reset_child_skeleton_poses() {
|
||||
for (const RetargetInfo &E : child_skeletons) {
|
||||
Skeleton3D *c = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(E.skeleton_id));
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
if (c->is_connected(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::_update_child_skeleton_rests))) {
|
||||
c->disconnect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::_update_child_skeleton_rests));
|
||||
}
|
||||
for (const RetargetBoneInfo &F : E.humanoid_bone_rests) {
|
||||
if (F.bone_id < 0) {
|
||||
continue;
|
||||
}
|
||||
c->reset_bone_pose(F.bone_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_reset_child_skeletons() {
|
||||
_reset_child_skeleton_poses();
|
||||
child_skeletons.clear();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void RetargetModifier3D::_force_update_child_skeletons() {
|
||||
for (const RetargetInfo &E : child_skeletons) {
|
||||
Skeleton3D *c = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(E.skeleton_id));
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
c->force_update_all_dirty_bones();
|
||||
c->emit_signal(SceneStringName(skeleton_updated));
|
||||
}
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
/// General functions
|
||||
|
||||
void RetargetModifier3D::add_child_notify(Node *p_child) {
|
||||
if (Object::cast_to<Skeleton3D>(p_child)) {
|
||||
_update_child_skeletons();
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetModifier3D::move_child_notify(Node *p_child) {
|
||||
if (Object::cast_to<Skeleton3D>(p_child)) {
|
||||
_update_child_skeletons();
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetModifier3D::remove_child_notify(Node *p_child) {
|
||||
if (Object::cast_to<Skeleton3D>(p_child)) {
|
||||
// Reset after process.
|
||||
callable_mp(this, &RetargetModifier3D::_update_child_skeletons).call_deferred();
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (use_global_pose) {
|
||||
if (p_property.name == "enable_flags") {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_profile", "profile"), &RetargetModifier3D::set_profile);
|
||||
ClassDB::bind_method(D_METHOD("get_profile"), &RetargetModifier3D::get_profile);
|
||||
ClassDB::bind_method(D_METHOD("set_use_global_pose", "use_global_pose"), &RetargetModifier3D::set_use_global_pose);
|
||||
ClassDB::bind_method(D_METHOD("is_using_global_pose"), &RetargetModifier3D::is_using_global_pose);
|
||||
ClassDB::bind_method(D_METHOD("set_enable_flags", "enable_flags"), &RetargetModifier3D::set_enable_flags);
|
||||
ClassDB::bind_method(D_METHOD("get_enable_flags"), &RetargetModifier3D::get_enable_flags);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_position_enabled", "enabled"), &RetargetModifier3D::set_position_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_position_enabled"), &RetargetModifier3D::is_position_enabled);
|
||||
ClassDB::bind_method(D_METHOD("set_rotation_enabled", "enabled"), &RetargetModifier3D::set_rotation_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_rotation_enabled"), &RetargetModifier3D::is_rotation_enabled);
|
||||
ClassDB::bind_method(D_METHOD("set_scale_enabled", "enabled"), &RetargetModifier3D::set_scale_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_scale_enabled"), &RetargetModifier3D::is_scale_enabled);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "profile", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonProfile"), "set_profile", "get_profile");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_global_pose"), "set_use_global_pose", "is_using_global_pose");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "enable", PROPERTY_HINT_FLAGS, "Position,Rotation,Scale"), "set_enable_flags", "get_enable_flags");
|
||||
|
||||
BIND_BITFIELD_FLAG(TRANSFORM_FLAG_POSITION);
|
||||
BIND_BITFIELD_FLAG(TRANSFORM_FLAG_ROTATION);
|
||||
BIND_BITFIELD_FLAG(TRANSFORM_FLAG_SCALE);
|
||||
BIND_BITFIELD_FLAG(TRANSFORM_FLAG_ALL);
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_set_active(bool p_active) {
|
||||
if (!p_active) {
|
||||
_reset_child_skeleton_poses();
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_retarget_global_pose() {
|
||||
Skeleton3D *source_skeleton = get_skeleton();
|
||||
if (profile.is_null() || !source_skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalVector<Transform3D> source_poses;
|
||||
if (influence < 1.0) {
|
||||
for (int source_bone_id : source_bone_ids) {
|
||||
source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_global_rest(source_bone_id).interpolate_with(source_skeleton->get_bone_global_pose(source_bone_id), influence));
|
||||
}
|
||||
} else {
|
||||
for (int source_bone_id : source_bone_ids) {
|
||||
source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_global_pose(source_bone_id));
|
||||
}
|
||||
}
|
||||
|
||||
for (const RetargetInfo &E : child_skeletons) {
|
||||
Skeleton3D *target_skeleton = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(E.skeleton_id));
|
||||
if (!target_skeleton) {
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < source_bone_ids.size(); i++) {
|
||||
int target_bone_id = E.humanoid_bone_rests[i].bone_id;
|
||||
if (target_bone_id < 0) {
|
||||
continue;
|
||||
}
|
||||
Transform3D retarget_pose = source_poses[i];
|
||||
retarget_pose.basis = retarget_pose.basis * E.humanoid_bone_rests[i].post_basis;
|
||||
target_skeleton->set_bone_global_pose(target_bone_id, retarget_pose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_retarget_pose() {
|
||||
Skeleton3D *source_skeleton = get_skeleton();
|
||||
if (profile.is_null() || !source_skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalVector<Transform3D> source_poses;
|
||||
if (influence < 1.0) {
|
||||
for (int source_bone_id : source_bone_ids) {
|
||||
source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_rest(source_bone_id).interpolate_with(source_skeleton->get_bone_pose(source_bone_id), influence));
|
||||
}
|
||||
} else {
|
||||
for (int source_bone_id : source_bone_ids) {
|
||||
source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_pose(source_bone_id));
|
||||
}
|
||||
}
|
||||
|
||||
for (const RetargetInfo &E : child_skeletons) {
|
||||
Skeleton3D *target_skeleton = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(E.skeleton_id));
|
||||
if (!target_skeleton) {
|
||||
continue;
|
||||
}
|
||||
float motion_scale_ratio = target_skeleton->get_motion_scale() / source_skeleton->get_motion_scale();
|
||||
for (int i = 0; i < source_bone_ids.size(); i++) {
|
||||
int target_bone_id = E.humanoid_bone_rests[i].bone_id;
|
||||
if (target_bone_id < 0) {
|
||||
continue;
|
||||
}
|
||||
int source_bone_id = source_bone_ids[i];
|
||||
if (source_bone_id < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Transform3D extracted_transform = source_poses[i];
|
||||
extracted_transform.basis = E.humanoid_bone_rests[i].pre_basis * extracted_transform.basis * E.humanoid_bone_rests[i].post_basis;
|
||||
extracted_transform.origin = E.humanoid_bone_rests[i].pre_basis.xform((extracted_transform.origin - source_skeleton->get_bone_rest(source_bone_id).origin) * motion_scale_ratio) + target_skeleton->get_bone_rest(target_bone_id).origin;
|
||||
|
||||
if (enable_flags.has_flag(TRANSFORM_FLAG_POSITION)) {
|
||||
target_skeleton->set_bone_pose_position(target_bone_id, extracted_transform.origin);
|
||||
}
|
||||
if (enable_flags.has_flag(TRANSFORM_FLAG_ROTATION)) {
|
||||
target_skeleton->set_bone_pose_rotation(target_bone_id, extracted_transform.basis.get_rotation_quaternion());
|
||||
}
|
||||
if (enable_flags.has_flag(TRANSFORM_FLAG_SCALE)) {
|
||||
target_skeleton->set_bone_pose_scale(target_bone_id, extracted_transform.basis.get_scale());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_process_modification() {
|
||||
if (use_global_pose) {
|
||||
_retarget_global_pose();
|
||||
} else {
|
||||
_retarget_pose();
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetModifier3D::set_profile(Ref<SkeletonProfile> p_profile) {
|
||||
if (profile == p_profile) {
|
||||
return;
|
||||
}
|
||||
_profile_changed(profile, p_profile);
|
||||
}
|
||||
|
||||
Ref<SkeletonProfile> RetargetModifier3D::get_profile() const {
|
||||
return profile;
|
||||
}
|
||||
|
||||
void RetargetModifier3D::set_use_global_pose(bool p_use_global_pose) {
|
||||
if (use_global_pose == p_use_global_pose) {
|
||||
return;
|
||||
}
|
||||
|
||||
use_global_pose = p_use_global_pose;
|
||||
cache_rests_with_reset();
|
||||
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
bool RetargetModifier3D::is_using_global_pose() const {
|
||||
return use_global_pose;
|
||||
}
|
||||
|
||||
void RetargetModifier3D::set_enable_flags(BitField<TransformFlag> p_enable_flag) {
|
||||
if (enable_flags != p_enable_flag) {
|
||||
_reset_child_skeleton_poses();
|
||||
}
|
||||
enable_flags = p_enable_flag;
|
||||
}
|
||||
|
||||
BitField<RetargetModifier3D::TransformFlag> RetargetModifier3D::get_enable_flags() const {
|
||||
return enable_flags;
|
||||
}
|
||||
|
||||
void RetargetModifier3D::set_position_enabled(bool p_enabled) {
|
||||
if (enable_flags.has_flag(TRANSFORM_FLAG_POSITION) != p_enabled) {
|
||||
_reset_child_skeleton_poses();
|
||||
}
|
||||
if (p_enabled) {
|
||||
enable_flags.set_flag(TRANSFORM_FLAG_POSITION);
|
||||
} else {
|
||||
enable_flags.clear_flag(TRANSFORM_FLAG_POSITION);
|
||||
}
|
||||
}
|
||||
|
||||
bool RetargetModifier3D::is_position_enabled() const {
|
||||
return enable_flags.has_flag(TRANSFORM_FLAG_POSITION);
|
||||
}
|
||||
|
||||
void RetargetModifier3D::set_rotation_enabled(bool p_enabled) {
|
||||
if (enable_flags.has_flag(TRANSFORM_FLAG_ROTATION) != p_enabled) {
|
||||
_reset_child_skeleton_poses();
|
||||
}
|
||||
if (p_enabled) {
|
||||
enable_flags.set_flag(TRANSFORM_FLAG_ROTATION);
|
||||
} else {
|
||||
enable_flags.clear_flag(TRANSFORM_FLAG_ROTATION);
|
||||
}
|
||||
}
|
||||
|
||||
bool RetargetModifier3D::is_rotation_enabled() const {
|
||||
return enable_flags.has_flag(TRANSFORM_FLAG_ROTATION);
|
||||
}
|
||||
|
||||
void RetargetModifier3D::set_scale_enabled(bool p_enabled) {
|
||||
if (enable_flags.has_flag(TRANSFORM_FLAG_SCALE) != p_enabled) {
|
||||
_reset_child_skeleton_poses();
|
||||
}
|
||||
if (p_enabled) {
|
||||
enable_flags.set_flag(TRANSFORM_FLAG_SCALE);
|
||||
} else {
|
||||
enable_flags.clear_flag(TRANSFORM_FLAG_SCALE);
|
||||
}
|
||||
}
|
||||
|
||||
bool RetargetModifier3D::is_scale_enabled() const {
|
||||
return enable_flags.has_flag(TRANSFORM_FLAG_SCALE);
|
||||
}
|
||||
|
||||
void RetargetModifier3D::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
_update_child_skeletons();
|
||||
} break;
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
_reset_child_skeletons();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
RetargetModifier3D::RetargetModifier3D() {
|
||||
}
|
||||
|
||||
RetargetModifier3D::~RetargetModifier3D() {
|
||||
}
|
||||
130
engine/scene/3d/retarget_modifier_3d.h
Normal file
130
engine/scene/3d/retarget_modifier_3d.h
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/**************************************************************************/
|
||||
/* retarget_modifier_3d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef RETARGET_MODIFIER_3D_H
|
||||
#define RETARGET_MODIFIER_3D_H
|
||||
|
||||
#include "scene/3d/skeleton_modifier_3d.h"
|
||||
#include "scene/resources/skeleton_profile.h"
|
||||
|
||||
class RetargetModifier3D : public SkeletonModifier3D {
|
||||
GDCLASS(RetargetModifier3D, SkeletonModifier3D);
|
||||
|
||||
public:
|
||||
enum TransformFlag {
|
||||
TRANSFORM_FLAG_POSITION = 1,
|
||||
TRANSFORM_FLAG_ROTATION = 2,
|
||||
TRANSFORM_FLAG_SCALE = 4,
|
||||
TRANSFORM_FLAG_ALL = TRANSFORM_FLAG_POSITION | TRANSFORM_FLAG_ROTATION | TRANSFORM_FLAG_SCALE,
|
||||
};
|
||||
|
||||
private:
|
||||
Ref<SkeletonProfile> profile;
|
||||
|
||||
bool use_global_pose = false;
|
||||
BitField<TransformFlag> enable_flags = TRANSFORM_FLAG_ALL;
|
||||
|
||||
struct RetargetBoneInfo {
|
||||
int bone_id = -1;
|
||||
Basis pre_basis;
|
||||
Basis post_basis;
|
||||
};
|
||||
|
||||
struct RetargetInfo {
|
||||
ObjectID skeleton_id;
|
||||
Vector<RetargetBoneInfo> humanoid_bone_rests;
|
||||
};
|
||||
|
||||
Vector<RetargetInfo> child_skeletons;
|
||||
Vector<int> source_bone_ids;
|
||||
|
||||
void _update_child_skeleton_rests(int p_child_skeleton_idx);
|
||||
void _update_child_skeletons();
|
||||
void _reset_child_skeleton_poses();
|
||||
void _reset_child_skeletons();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void _force_update_child_skeletons();
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
void cache_rests_with_reset();
|
||||
void cache_rests();
|
||||
Vector<RetargetBoneInfo> cache_bone_global_rests(Skeleton3D *p_skeleton);
|
||||
Vector<RetargetBoneInfo> cache_bone_rests(Skeleton3D *p_skeleton);
|
||||
Vector<RetargetBoneInfo> get_humanoid_bone_rests(Skeleton3D *p_skeleton);
|
||||
|
||||
void _retarget_global_pose();
|
||||
void _retarget_pose();
|
||||
|
||||
protected:
|
||||
virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) override;
|
||||
void _profile_changed(Ref<SkeletonProfile> p_old, Ref<SkeletonProfile> p_new);
|
||||
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
static void _bind_methods();
|
||||
virtual void _notification(int p_what);
|
||||
|
||||
virtual void add_child_notify(Node *p_child) override;
|
||||
virtual void move_child_notify(Node *p_child) override;
|
||||
virtual void remove_child_notify(Node *p_child) override;
|
||||
|
||||
virtual void _set_active(bool p_active) override;
|
||||
virtual void _process_modification() override;
|
||||
|
||||
public:
|
||||
virtual PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
void set_use_global_pose(bool p_use_global_pose);
|
||||
bool is_using_global_pose() const;
|
||||
void set_enable_flags(BitField<TransformFlag> p_enable_flags);
|
||||
BitField<TransformFlag> get_enable_flags() const;
|
||||
|
||||
void set_position_enabled(bool p_enabled);
|
||||
bool is_position_enabled() const;
|
||||
void set_rotation_enabled(bool p_enabled);
|
||||
bool is_rotation_enabled() const;
|
||||
void set_scale_enabled(bool p_enabled);
|
||||
bool is_scale_enabled() const;
|
||||
|
||||
void set_profile(Ref<SkeletonProfile> p_profile);
|
||||
Ref<SkeletonProfile> get_profile() const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual bool is_processed_on_saving() const override { return true; }
|
||||
#endif
|
||||
|
||||
RetargetModifier3D();
|
||||
virtual ~RetargetModifier3D();
|
||||
};
|
||||
|
||||
VARIANT_BITFIELD_CAST(RetargetModifier3D::TransformFlag);
|
||||
|
||||
#endif // RETARGET_MODIFIER_3D_H
|
||||
|
|
@ -31,9 +31,7 @@
|
|||
#include "skeleton_3d.h"
|
||||
#include "skeleton_3d.compat.inc"
|
||||
|
||||
#include "core/variant/type_info.h"
|
||||
#include "scene/3d/skeleton_modifier_3d.h"
|
||||
#include "scene/resources/surface_tool.h"
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
#include "scene/3d/physical_bone_simulator_3d.h"
|
||||
#endif // _DISABLE_DEPRECATED
|
||||
|
|
@ -69,19 +67,19 @@ SkinReference::~SkinReference() {
|
|||
///////////////////////////////////////
|
||||
|
||||
bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
if (path.begins_with("animate_physical_bones")) {
|
||||
if (p_path == SNAME("animate_physical_bones")) {
|
||||
set_animate_physical_bones(p_value);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
String path = p_path;
|
||||
|
||||
if (!path.begins_with("bones/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int which = path.get_slicec('/', 1).to_int();
|
||||
uint32_t which = path.get_slicec('/', 1).to_int();
|
||||
String what = path.get_slicec('/', 2);
|
||||
|
||||
if (which == bones.size() && what == "name") {
|
||||
|
|
@ -89,7 +87,7 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
|
|||
return true;
|
||||
}
|
||||
|
||||
ERR_FAIL_INDEX_V(which, bones.size(), false);
|
||||
ERR_FAIL_UNSIGNED_INDEX_V(which, bones.size(), false);
|
||||
|
||||
if (what == "parent") {
|
||||
set_bone_parent(which, p_value);
|
||||
|
|
@ -103,6 +101,8 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
|
|||
set_bone_pose_rotation(which, p_value);
|
||||
} else if (what == "scale") {
|
||||
set_bone_pose_scale(which, p_value);
|
||||
} else if (what == "bone_meta") {
|
||||
set_bone_meta(which, path.get_slicec('/', 3), p_value);
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
} else if (what == "pose" || what == "bound_children") {
|
||||
// Kept for compatibility from 3.x to 4.x.
|
||||
|
|
@ -139,22 +139,22 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
|
|||
}
|
||||
|
||||
bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
if (path.begins_with("animate_physical_bones")) {
|
||||
if (p_path == SNAME("animate_physical_bones")) {
|
||||
r_ret = get_animate_physical_bones();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
String path = p_path;
|
||||
|
||||
if (!path.begins_with("bones/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int which = path.get_slicec('/', 1).to_int();
|
||||
uint32_t which = path.get_slicec('/', 1).to_int();
|
||||
String what = path.get_slicec('/', 2);
|
||||
|
||||
ERR_FAIL_INDEX_V(which, bones.size(), false);
|
||||
ERR_FAIL_UNSIGNED_INDEX_V(which, bones.size(), false);
|
||||
|
||||
if (what == "name") {
|
||||
r_ret = get_bone_name(which);
|
||||
|
|
@ -170,6 +170,8 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
|
|||
r_ret = get_bone_pose_rotation(which);
|
||||
} else if (what == "scale") {
|
||||
r_ret = get_bone_pose_scale(which);
|
||||
} else if (what == "bone_meta") {
|
||||
r_ret = get_bone_meta(which, path.get_slicec('/', 3));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -178,15 +180,20 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
|
|||
}
|
||||
|
||||
void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
const String prep = vformat("%s/%d/", PNAME("bones"), i);
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, prep + PNAME("name"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::INT, prep + PNAME("parent"), PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + PNAME("rest"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, prep + PNAME("enabled"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("position"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + PNAME("rotation"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("scale"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
for (uint32_t i = 0; i < bones.size(); i++) {
|
||||
const String prep = vformat("%s/%d/", "bones", i);
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, prep + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + "rest", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + "rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
|
||||
for (const KeyValue<StringName, Variant> &K : bones[i].metadata) {
|
||||
PropertyInfo pi = PropertyInfo(bones[i].metadata[K.key].get_type(), prep + "bone_meta/" + K.key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
|
||||
p_list->push_back(pi);
|
||||
}
|
||||
}
|
||||
|
||||
for (PropertyInfo &E : *p_list) {
|
||||
|
|
@ -227,12 +234,12 @@ void Skeleton3D::_validate_property(PropertyInfo &p_property) const {
|
|||
}
|
||||
}
|
||||
|
||||
void Skeleton3D::_update_process_order() {
|
||||
void Skeleton3D::_update_process_order() const {
|
||||
if (!process_order_dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bone *bonesptr = bones.ptrw();
|
||||
Bone *bonesptr = bones.ptr();
|
||||
int len = bones.size();
|
||||
|
||||
parentless_bones.clear();
|
||||
|
|
@ -263,18 +270,18 @@ void Skeleton3D::_update_process_order() {
|
|||
}
|
||||
}
|
||||
|
||||
bones_backup.resize(bones.size());
|
||||
|
||||
concatenated_bone_names = StringName();
|
||||
|
||||
_update_bones_nested_set();
|
||||
|
||||
process_order_dirty = false;
|
||||
|
||||
emit_signal("bone_list_changed");
|
||||
const_cast<Skeleton3D *>(this)->emit_signal("bone_list_changed");
|
||||
}
|
||||
|
||||
void Skeleton3D::_update_bone_names() const {
|
||||
String names;
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
for (uint32_t i = 0; i < bones.size(); i++) {
|
||||
if (i > 0) {
|
||||
names += ",";
|
||||
}
|
||||
|
|
@ -315,23 +322,41 @@ void Skeleton3D::_notification(int p_what) {
|
|||
#ifndef DISABLE_DEPRECATED
|
||||
setup_simulator();
|
||||
#endif // _DISABLE_DEPRECATED
|
||||
update_flags = UPDATE_FLAG_POSE;
|
||||
_notification(NOTIFICATION_UPDATE_SKELETON);
|
||||
} break;
|
||||
#ifdef TOOLS_ENABLED
|
||||
case NOTIFICATION_EDITOR_PRE_SAVE: {
|
||||
saving = true;
|
||||
} break;
|
||||
case NOTIFICATION_EDITOR_POST_SAVE: {
|
||||
saving = false;
|
||||
} break;
|
||||
#endif // TOOLS_ENABLED
|
||||
case NOTIFICATION_UPDATE_SKELETON: {
|
||||
// Update bone transforms to apply unprocessed poses.
|
||||
force_update_all_dirty_bones();
|
||||
|
||||
updating = true;
|
||||
|
||||
Bone *bonesptr = bones.ptrw();
|
||||
Bone *bonesptr = bones.ptr();
|
||||
int len = bones.size();
|
||||
|
||||
thread_local LocalVector<bool> bone_global_pose_dirty_backup;
|
||||
|
||||
// Process modifiers.
|
||||
|
||||
thread_local LocalVector<BonePoseBackup> bones_backup;
|
||||
_find_modifiers();
|
||||
if (!modifiers.is_empty()) {
|
||||
bones_backup.resize(bones.size());
|
||||
// Store unmodified bone poses.
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
bones_backup[i].save(bones[i]);
|
||||
for (uint32_t i = 0; i < bones.size(); i++) {
|
||||
bones_backup[i].save(bonesptr[i]);
|
||||
}
|
||||
// Store dirty flags for global bone poses.
|
||||
bone_global_pose_dirty_backup = bone_global_pose_dirty;
|
||||
|
||||
_process_modifiers();
|
||||
}
|
||||
|
||||
|
|
@ -403,9 +428,11 @@ void Skeleton3D::_notification(int p_what) {
|
|||
|
||||
if (!modifiers.is_empty()) {
|
||||
// Restore unmodified bone poses.
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
bones_backup[i].restore(bones.write[i]);
|
||||
for (uint32_t i = 0; i < bones.size(); i++) {
|
||||
bones_backup[i].restore(bones[i]);
|
||||
}
|
||||
// Restore dirty flags for global bone poses.
|
||||
bone_global_pose_dirty = bone_global_pose_dirty_backup;
|
||||
}
|
||||
|
||||
updating = false;
|
||||
|
|
@ -448,10 +475,111 @@ void Skeleton3D::_make_modifiers_dirty() {
|
|||
_update_deferred(UPDATE_FLAG_MODIFIER);
|
||||
}
|
||||
|
||||
void Skeleton3D::_update_bones_nested_set() const {
|
||||
nested_set_offset_to_bone_index.resize(bones.size());
|
||||
bone_global_pose_dirty.resize(bones.size());
|
||||
_make_bone_global_poses_dirty();
|
||||
|
||||
int offset = 0;
|
||||
for (int bone : parentless_bones) {
|
||||
offset += _update_bone_nested_set(bone, offset);
|
||||
}
|
||||
}
|
||||
|
||||
int Skeleton3D::_update_bone_nested_set(int p_bone, int p_offset) const {
|
||||
Bone &bone = bones[p_bone];
|
||||
int offset = p_offset + 1;
|
||||
int span = 1;
|
||||
|
||||
for (int child_bone : bone.child_bones) {
|
||||
int subspan = _update_bone_nested_set(child_bone, offset);
|
||||
offset += subspan;
|
||||
span += subspan;
|
||||
}
|
||||
|
||||
nested_set_offset_to_bone_index[p_offset] = p_bone;
|
||||
bone.nested_set_offset = p_offset;
|
||||
bone.nested_set_span = span;
|
||||
|
||||
return span;
|
||||
}
|
||||
|
||||
void Skeleton3D::_make_bone_global_poses_dirty() const {
|
||||
for (uint32_t i = 0; i < bone_global_pose_dirty.size(); i++) {
|
||||
bone_global_pose_dirty[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Skeleton3D::_make_bone_global_pose_subtree_dirty(int p_bone) const {
|
||||
if (process_order_dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Bone &bone = bones[p_bone];
|
||||
int span_offset = bone.nested_set_offset;
|
||||
// No need to make subtree dirty when bone is already dirty.
|
||||
if (bone_global_pose_dirty[span_offset]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make global poses of subtree dirty.
|
||||
int span_end = span_offset + bone.nested_set_span;
|
||||
for (int i = span_offset; i < span_end; i++) {
|
||||
bone_global_pose_dirty[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Skeleton3D::_update_bone_global_pose(int p_bone) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
_update_process_order();
|
||||
|
||||
// Global pose is already calculated.
|
||||
int nested_set_offset = bones[p_bone].nested_set_offset;
|
||||
if (!bone_global_pose_dirty[nested_set_offset]) {
|
||||
return;
|
||||
}
|
||||
|
||||
thread_local LocalVector<int> bone_list;
|
||||
bone_list.clear();
|
||||
Transform3D global_pose;
|
||||
|
||||
// Create list of parent bones for which the global pose needs to be recalculated.
|
||||
for (int bone = p_bone; bone >= 0; bone = bones[bone].parent) {
|
||||
int offset = bones[bone].nested_set_offset;
|
||||
// Stop searching when global pose is not dirty.
|
||||
if (!bone_global_pose_dirty[offset]) {
|
||||
global_pose = bones[bone].global_pose;
|
||||
break;
|
||||
}
|
||||
|
||||
bone_list.push_back(bone);
|
||||
}
|
||||
|
||||
// Calculate global poses for all parent bones and the current bone.
|
||||
for (int i = bone_list.size() - 1; i >= 0; i--) {
|
||||
int bone_idx = bone_list[i];
|
||||
Bone &bone = bones[bone_idx];
|
||||
bool bone_enabled = bone.enabled && !show_rest_only;
|
||||
Transform3D bone_pose = bone_enabled ? get_bone_pose(bone_idx) : get_bone_rest(bone_idx);
|
||||
|
||||
global_pose *= bone_pose;
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
if (bone.global_pose_override_amount >= CMP_EPSILON) {
|
||||
global_pose = global_pose.interpolate_with(bone.global_pose_override, bone.global_pose_override_amount);
|
||||
}
|
||||
#endif // _DISABLE_DEPRECATED
|
||||
|
||||
bone.global_pose = global_pose;
|
||||
bone_global_pose_dirty[bone.nested_set_offset] = false;
|
||||
}
|
||||
}
|
||||
|
||||
Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
|
||||
const_cast<Skeleton3D *>(this)->force_update_all_dirty_bones();
|
||||
_update_bone_global_pose(p_bone);
|
||||
return bones[p_bone].global_pose;
|
||||
}
|
||||
|
||||
|
|
@ -487,7 +615,7 @@ uint64_t Skeleton3D::get_version() const {
|
|||
}
|
||||
|
||||
int Skeleton3D::add_bone(const String &p_name) {
|
||||
ERR_FAIL_COND_V_MSG(p_name.is_empty() || p_name.contains(":") || p_name.contains("/"), -1, vformat("Bone name cannot be empty or contain ':' or '/'.", p_name));
|
||||
ERR_FAIL_COND_V_MSG(p_name.is_empty() || p_name.contains_char(':') || p_name.contains_char('/'), -1, vformat("Bone name cannot be empty or contain ':' or '/'.", p_name));
|
||||
ERR_FAIL_COND_V_MSG(name_to_bone_index.has(p_name), -1, vformat("Skeleton3D \"%s\" already has a bone with name \"%s\".", to_string(), p_name));
|
||||
|
||||
Bone b;
|
||||
|
|
@ -525,12 +653,63 @@ void Skeleton3D::set_bone_name(int p_bone, const String &p_name) {
|
|||
}
|
||||
|
||||
name_to_bone_index.erase(bones[p_bone].name);
|
||||
bones.write[p_bone].name = p_name;
|
||||
bones[p_bone].name = p_name;
|
||||
name_to_bone_index.insert(p_name, p_bone);
|
||||
|
||||
version++;
|
||||
}
|
||||
|
||||
Variant Skeleton3D::get_bone_meta(int p_bone, const StringName &p_key) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Variant());
|
||||
|
||||
if (!bones[p_bone].metadata.has(p_key)) {
|
||||
return Variant();
|
||||
}
|
||||
return bones[p_bone].metadata[p_key];
|
||||
}
|
||||
|
||||
TypedArray<StringName> Skeleton3D::_get_bone_meta_list_bind(int p_bone) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, TypedArray<StringName>());
|
||||
|
||||
TypedArray<StringName> _metaret;
|
||||
for (const KeyValue<StringName, Variant> &K : bones[p_bone].metadata) {
|
||||
_metaret.push_back(K.key);
|
||||
}
|
||||
return _metaret;
|
||||
}
|
||||
|
||||
void Skeleton3D::get_bone_meta_list(int p_bone, List<StringName> *p_list) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
for (const KeyValue<StringName, Variant> &K : bones[p_bone].metadata) {
|
||||
p_list->push_back(K.key);
|
||||
}
|
||||
}
|
||||
|
||||
bool Skeleton3D::has_bone_meta(int p_bone, const StringName &p_key) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, false);
|
||||
|
||||
return bones[p_bone].metadata.has(p_key);
|
||||
}
|
||||
|
||||
void Skeleton3D::set_bone_meta(int p_bone, const StringName &p_key, const Variant &p_value) {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
if (p_value.get_type() == Variant::NIL) {
|
||||
if (bones[p_bone].metadata.has(p_key)) {
|
||||
bones[p_bone].metadata.erase(p_key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bones[p_bone].metadata.insert(p_key, p_value, false);
|
||||
}
|
||||
|
||||
bool Skeleton3D::is_bone_parent_of(int p_bone, int p_parent_bone_id) const {
|
||||
int parent_of_bone = get_bone_parent(p_bone);
|
||||
|
||||
|
|
@ -555,7 +734,7 @@ void Skeleton3D::set_bone_parent(int p_bone, int p_parent) {
|
|||
ERR_FAIL_COND(p_parent != -1 && (p_parent < 0));
|
||||
ERR_FAIL_COND(p_bone == p_parent);
|
||||
|
||||
bones.write[p_bone].parent = p_parent;
|
||||
bones[p_bone].parent = p_parent;
|
||||
process_order_dirty = true;
|
||||
rest_dirty = true;
|
||||
_make_dirty();
|
||||
|
|
@ -569,11 +748,11 @@ void Skeleton3D::unparent_bone_and_rest(int p_bone) {
|
|||
|
||||
int parent = bones[p_bone].parent;
|
||||
while (parent >= 0) {
|
||||
bones.write[p_bone].rest = bones[parent].rest * bones[p_bone].rest;
|
||||
bones[p_bone].rest = bones[parent].rest * bones[p_bone].rest;
|
||||
parent = bones[parent].parent;
|
||||
}
|
||||
|
||||
bones.write[p_bone].parent = -1;
|
||||
bones[p_bone].parent = -1;
|
||||
process_order_dirty = true;
|
||||
|
||||
rest_dirty = true;
|
||||
|
|
@ -584,7 +763,7 @@ int Skeleton3D::get_bone_parent(int p_bone) const {
|
|||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, -1);
|
||||
if (process_order_dirty) {
|
||||
const_cast<Skeleton3D *>(this)->_update_process_order();
|
||||
_update_process_order();
|
||||
}
|
||||
return bones[p_bone].parent;
|
||||
}
|
||||
|
|
@ -593,14 +772,14 @@ Vector<int> Skeleton3D::get_bone_children(int p_bone) const {
|
|||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Vector<int>());
|
||||
if (process_order_dirty) {
|
||||
const_cast<Skeleton3D *>(this)->_update_process_order();
|
||||
_update_process_order();
|
||||
}
|
||||
return bones[p_bone].child_bones;
|
||||
}
|
||||
|
||||
Vector<int> Skeleton3D::get_parentless_bones() const {
|
||||
if (process_order_dirty) {
|
||||
const_cast<Skeleton3D *>(this)->_update_process_order();
|
||||
_update_process_order();
|
||||
}
|
||||
return parentless_bones;
|
||||
}
|
||||
|
|
@ -609,9 +788,10 @@ void Skeleton3D::set_bone_rest(int p_bone, const Transform3D &p_rest) {
|
|||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
bones.write[p_bone].rest = p_rest;
|
||||
bones[p_bone].rest = p_rest;
|
||||
rest_dirty = true;
|
||||
_make_dirty();
|
||||
_make_bone_global_pose_subtree_dirty(p_bone);
|
||||
}
|
||||
Transform3D Skeleton3D::get_bone_rest(int p_bone) const {
|
||||
const int bone_size = bones.size();
|
||||
|
|
@ -623,7 +803,7 @@ Transform3D Skeleton3D::get_bone_global_rest(int p_bone) const {
|
|||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
|
||||
if (rest_dirty) {
|
||||
const_cast<Skeleton3D *>(this)->force_update_all_bone_transforms();
|
||||
_force_update_all_bone_transforms();
|
||||
}
|
||||
return bones[p_bone].global_rest;
|
||||
}
|
||||
|
|
@ -632,9 +812,10 @@ void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) {
|
|||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
bones.write[p_bone].enabled = p_enabled;
|
||||
bones[p_bone].enabled = p_enabled;
|
||||
emit_signal(SceneStringName(bone_enabled_changed), p_bone);
|
||||
_make_dirty();
|
||||
_make_bone_global_pose_subtree_dirty(p_bone);
|
||||
}
|
||||
|
||||
bool Skeleton3D::is_bone_enabled(int p_bone) const {
|
||||
|
|
@ -647,6 +828,7 @@ void Skeleton3D::set_show_rest_only(bool p_enabled) {
|
|||
show_rest_only = p_enabled;
|
||||
emit_signal(SceneStringName(show_rest_only_changed));
|
||||
_make_dirty();
|
||||
_make_bone_global_poses_dirty();
|
||||
}
|
||||
|
||||
bool Skeleton3D::is_show_rest_only() const {
|
||||
|
|
@ -667,12 +849,13 @@ void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) {
|
|||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
bones.write[p_bone].pose_position = p_pose.origin;
|
||||
bones.write[p_bone].pose_rotation = p_pose.basis.get_rotation_quaternion();
|
||||
bones.write[p_bone].pose_scale = p_pose.basis.get_scale();
|
||||
bones.write[p_bone].pose_cache_dirty = true;
|
||||
bones[p_bone].pose_position = p_pose.origin;
|
||||
bones[p_bone].pose_rotation = p_pose.basis.get_rotation_quaternion();
|
||||
bones[p_bone].pose_scale = p_pose.basis.get_scale();
|
||||
bones[p_bone].pose_cache_dirty = true;
|
||||
if (is_inside_tree()) {
|
||||
_make_dirty();
|
||||
_make_bone_global_pose_subtree_dirty(p_bone);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -680,30 +863,33 @@ void Skeleton3D::set_bone_pose_position(int p_bone, const Vector3 &p_position) {
|
|||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
bones.write[p_bone].pose_position = p_position;
|
||||
bones.write[p_bone].pose_cache_dirty = true;
|
||||
bones[p_bone].pose_position = p_position;
|
||||
bones[p_bone].pose_cache_dirty = true;
|
||||
if (is_inside_tree()) {
|
||||
_make_dirty();
|
||||
_make_bone_global_pose_subtree_dirty(p_bone);
|
||||
}
|
||||
}
|
||||
void Skeleton3D::set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation) {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
bones.write[p_bone].pose_rotation = p_rotation;
|
||||
bones.write[p_bone].pose_cache_dirty = true;
|
||||
bones[p_bone].pose_rotation = p_rotation;
|
||||
bones[p_bone].pose_cache_dirty = true;
|
||||
if (is_inside_tree()) {
|
||||
_make_dirty();
|
||||
_make_bone_global_pose_subtree_dirty(p_bone);
|
||||
}
|
||||
}
|
||||
void Skeleton3D::set_bone_pose_scale(int p_bone, const Vector3 &p_scale) {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
bones.write[p_bone].pose_scale = p_scale;
|
||||
bones.write[p_bone].pose_cache_dirty = true;
|
||||
bones[p_bone].pose_scale = p_scale;
|
||||
bones[p_bone].pose_cache_dirty = true;
|
||||
if (is_inside_tree()) {
|
||||
_make_dirty();
|
||||
_make_bone_global_pose_subtree_dirty(p_bone);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -734,7 +920,7 @@ void Skeleton3D::reset_bone_pose(int p_bone) {
|
|||
}
|
||||
|
||||
void Skeleton3D::reset_bone_poses() {
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
for (uint32_t i = 0; i < bones.size(); i++) {
|
||||
reset_bone_pose(i);
|
||||
}
|
||||
}
|
||||
|
|
@ -742,7 +928,7 @@ void Skeleton3D::reset_bone_poses() {
|
|||
Transform3D Skeleton3D::get_bone_pose(int p_bone) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
|
||||
const_cast<Skeleton3D *>(this)->bones.write[p_bone].update_pose_cache();
|
||||
bones[p_bone].update_pose_cache();
|
||||
return bones[p_bone].pose_cache;
|
||||
}
|
||||
|
||||
|
|
@ -756,6 +942,13 @@ void Skeleton3D::_make_dirty() {
|
|||
|
||||
void Skeleton3D::_update_deferred(UpdateFlag p_update_flag) {
|
||||
if (is_inside_tree()) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (saving) {
|
||||
update_flags |= p_update_flag;
|
||||
_notification(NOTIFICATION_UPDATE_SKELETON);
|
||||
return;
|
||||
}
|
||||
#endif //TOOLS_ENABLED
|
||||
if (update_flags == UPDATE_FLAG_NONE && !updating) {
|
||||
notify_deferred_thread_group(NOTIFICATION_UPDATE_SKELETON); // It must never be called more than once in a single frame.
|
||||
}
|
||||
|
|
@ -793,7 +986,7 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() {
|
|||
|
||||
// Pose changed, rebuild cache of inverses.
|
||||
const Bone *bonesptr = bones.ptr();
|
||||
int len = bones.size();
|
||||
uint32_t len = bones.size();
|
||||
|
||||
// Calculate global rests and invert them.
|
||||
LocalVector<int> bones_to_process;
|
||||
|
|
@ -817,7 +1010,7 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() {
|
|||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
// The inverse is what is actually required.
|
||||
skin->set_bind_bone(i, i);
|
||||
skin->set_bind_pose(i, skin->get_bind_pose(i).affine_inverse());
|
||||
|
|
@ -853,39 +1046,60 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
|
|||
return skin_ref;
|
||||
}
|
||||
|
||||
void Skeleton3D::force_update_deferred() {
|
||||
_make_dirty();
|
||||
}
|
||||
|
||||
void Skeleton3D::force_update_all_dirty_bones() {
|
||||
_force_update_all_dirty_bones();
|
||||
}
|
||||
|
||||
void Skeleton3D::_force_update_all_dirty_bones() const {
|
||||
if (!dirty) {
|
||||
return;
|
||||
}
|
||||
force_update_all_bone_transforms();
|
||||
_force_update_all_bone_transforms();
|
||||
}
|
||||
|
||||
void Skeleton3D::force_update_all_bone_transforms() {
|
||||
_force_update_all_bone_transforms();
|
||||
}
|
||||
|
||||
void Skeleton3D::_force_update_all_bone_transforms() const {
|
||||
_update_process_order();
|
||||
for (int i = 0; i < parentless_bones.size(); i++) {
|
||||
force_update_bone_children_transforms(parentless_bones[i]);
|
||||
_force_update_bone_children_transforms(parentless_bones[i]);
|
||||
}
|
||||
if (rest_dirty) {
|
||||
rest_dirty = false;
|
||||
const_cast<Skeleton3D *>(this)->emit_signal(SNAME("rest_updated"));
|
||||
} else {
|
||||
rest_dirty = false;
|
||||
}
|
||||
rest_dirty = false;
|
||||
dirty = false;
|
||||
if (updating) {
|
||||
return;
|
||||
}
|
||||
emit_signal(SceneStringName(pose_updated));
|
||||
const_cast<Skeleton3D *>(this)->emit_signal(SceneStringName(pose_updated));
|
||||
}
|
||||
|
||||
void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
|
||||
_force_update_bone_children_transforms(p_bone_idx);
|
||||
}
|
||||
|
||||
void Skeleton3D::_force_update_bone_children_transforms(int p_bone_idx) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone_idx, bone_size);
|
||||
|
||||
Bone *bonesptr = bones.ptrw();
|
||||
thread_local LocalVector<int> bones_to_process;
|
||||
bones_to_process.clear();
|
||||
bones_to_process.push_back(p_bone_idx);
|
||||
Bone *bonesptr = bones.ptr();
|
||||
|
||||
uint32_t index = 0;
|
||||
while (index < bones_to_process.size()) {
|
||||
int current_bone_idx = bones_to_process[index];
|
||||
// Loop through nested set.
|
||||
for (int offset = 0; offset < bone_size; offset++) {
|
||||
if (!bone_global_pose_dirty[offset]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int current_bone_idx = nested_set_offset_to_bone_index[offset];
|
||||
Bone &b = bonesptr[current_bone_idx];
|
||||
bool bone_enabled = b.enabled && !show_rest_only;
|
||||
|
||||
|
|
@ -932,13 +1146,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
|
|||
}
|
||||
#endif // _DISABLE_DEPRECATED
|
||||
|
||||
// Add the bone's children to the list of bones to be processed.
|
||||
int child_bone_size = b.child_bones.size();
|
||||
for (int i = 0; i < child_bone_size; i++) {
|
||||
bones_to_process.push_back(b.child_bones[i]);
|
||||
}
|
||||
|
||||
index++;
|
||||
bone_global_pose_dirty[offset] = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -966,6 +1174,11 @@ void Skeleton3D::_process_modifiers() {
|
|||
if (!mod) {
|
||||
continue;
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (saving && !mod->is_processed_on_saving()) {
|
||||
continue;
|
||||
}
|
||||
#endif //TOOLS_ENABLED
|
||||
real_t influence = mod->get_influence();
|
||||
if (influence < 1.0) {
|
||||
LocalVector<Transform3D> old_poses;
|
||||
|
|
@ -1014,6 +1227,11 @@ void Skeleton3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &Skeleton3D::get_bone_name);
|
||||
ClassDB::bind_method(D_METHOD("set_bone_name", "bone_idx", "name"), &Skeleton3D::set_bone_name);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_bone_meta", "bone_idx", "key"), &Skeleton3D::get_bone_meta);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_meta_list", "bone_idx"), &Skeleton3D::_get_bone_meta_list_bind);
|
||||
ClassDB::bind_method(D_METHOD("has_bone_meta", "bone_idx", "key"), &Skeleton3D::has_bone_meta);
|
||||
ClassDB::bind_method(D_METHOD("set_bone_meta", "bone_idx", "key", "value"), &Skeleton3D::set_bone_meta);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_concatenated_bone_names"), &Skeleton3D::get_concatenated_bone_names);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_bone_parent", "bone_idx"), &Skeleton3D::get_bone_parent);
|
||||
|
|
@ -1076,6 +1294,7 @@ void Skeleton3D::_bind_methods() {
|
|||
ADD_GROUP("Modifier", "modifier_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "modifier_callback_mode_process", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_modifier_callback_mode_process", "get_modifier_callback_mode_process");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("rest_updated"));
|
||||
ADD_SIGNAL(MethodInfo("pose_updated"));
|
||||
ADD_SIGNAL(MethodInfo("skeleton_updated"));
|
||||
ADD_SIGNAL(MethodInfo("bone_enabled_changed", PropertyInfo(Variant::INT, "bone_idx")));
|
||||
|
|
@ -1106,20 +1325,22 @@ void Skeleton3D::_bind_methods() {
|
|||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void Skeleton3D::clear_bones_global_pose_override() {
|
||||
for (int i = 0; i < bones.size(); i += 1) {
|
||||
bones.write[i].global_pose_override_amount = 0;
|
||||
bones.write[i].global_pose_override_reset = true;
|
||||
for (uint32_t i = 0; i < bones.size(); i += 1) {
|
||||
bones[i].global_pose_override_amount = 0;
|
||||
bones[i].global_pose_override_reset = true;
|
||||
}
|
||||
_make_dirty();
|
||||
_make_bone_global_poses_dirty();
|
||||
}
|
||||
|
||||
void Skeleton3D::set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
bones.write[p_bone].global_pose_override_amount = p_amount;
|
||||
bones.write[p_bone].global_pose_override = p_pose;
|
||||
bones.write[p_bone].global_pose_override_reset = !p_persistent;
|
||||
bones[p_bone].global_pose_override_amount = p_amount;
|
||||
bones[p_bone].global_pose_override = p_pose;
|
||||
bones[p_bone].global_pose_override_reset = !p_persistent;
|
||||
_make_dirty();
|
||||
_make_bone_global_pose_subtree_dirty(p_bone);
|
||||
}
|
||||
|
||||
Transform3D Skeleton3D::get_bone_global_pose_override(int p_bone) const {
|
||||
|
|
@ -1131,7 +1352,7 @@ Transform3D Skeleton3D::get_bone_global_pose_override(int p_bone) const {
|
|||
Transform3D Skeleton3D::get_bone_global_pose_no_override(int p_bone) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
|
||||
const_cast<Skeleton3D *>(this)->force_update_all_dirty_bones();
|
||||
_force_update_all_dirty_bones();
|
||||
return bones[p_bone].pose_global_no_override;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,13 +31,13 @@
|
|||
#ifndef SKELETON_3D_H
|
||||
#define SKELETON_3D_H
|
||||
|
||||
#include "core/templates/a_hash_map.h"
|
||||
#include "scene/3d/node_3d.h"
|
||||
#include "scene/resources/3d/skin.h"
|
||||
|
||||
typedef int BoneId;
|
||||
|
||||
class Skeleton3D;
|
||||
class SkeletonModifier3D;
|
||||
|
||||
class SkinReference : public RefCounted {
|
||||
GDCLASS(SkinReference, RefCounted)
|
||||
|
|
@ -66,6 +66,10 @@ public:
|
|||
class Skeleton3D : public Node3D {
|
||||
GDCLASS(Skeleton3D, Node3D);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool saving = false;
|
||||
#endif //TOOLS_ENABLED
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
bool animate_physical_bones = true;
|
||||
Node *simulator = nullptr;
|
||||
|
|
@ -107,6 +111,8 @@ private:
|
|||
Quaternion pose_rotation;
|
||||
Vector3 pose_scale = Vector3(1, 1, 1);
|
||||
Transform3D global_pose;
|
||||
int nested_set_offset = 0; // Offset in nested set of bone hierarchy.
|
||||
int nested_set_span = 0; // Subtree span in nested set of bone hierarchy.
|
||||
|
||||
void update_pose_cache() {
|
||||
if (pose_cache_dirty) {
|
||||
|
|
@ -116,6 +122,8 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
HashMap<StringName, Variant> metadata;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
Transform3D pose_global_no_override;
|
||||
real_t global_pose_override_amount = 0.0;
|
||||
|
|
@ -151,25 +159,25 @@ private:
|
|||
HashSet<SkinReference *> skin_bindings;
|
||||
void _skin_changed();
|
||||
|
||||
Vector<Bone> bones;
|
||||
bool process_order_dirty = false;
|
||||
mutable LocalVector<Bone> bones;
|
||||
mutable bool process_order_dirty = false;
|
||||
|
||||
Vector<int> parentless_bones;
|
||||
HashMap<String, int> name_to_bone_index;
|
||||
mutable Vector<int> parentless_bones;
|
||||
AHashMap<String, int> name_to_bone_index;
|
||||
|
||||
mutable StringName concatenated_bone_names = StringName();
|
||||
mutable StringName concatenated_bone_names;
|
||||
void _update_bone_names() const;
|
||||
|
||||
void _make_dirty();
|
||||
bool dirty = false;
|
||||
bool rest_dirty = false;
|
||||
mutable bool dirty = false;
|
||||
mutable bool rest_dirty = false;
|
||||
|
||||
bool show_rest_only = false;
|
||||
float motion_scale = 1.0;
|
||||
|
||||
uint64_t version = 1;
|
||||
|
||||
void _update_process_order();
|
||||
void _update_process_order() const;
|
||||
|
||||
// To process modifiers.
|
||||
ModifierCallbackModeProcess modifier_callback_mode_process = MODIFIER_CALLBACK_MODE_PROCESS_IDLE;
|
||||
|
|
@ -179,7 +187,15 @@ private:
|
|||
void _process_modifiers();
|
||||
void _process_changed();
|
||||
void _make_modifiers_dirty();
|
||||
LocalVector<BonePoseBackup> bones_backup;
|
||||
|
||||
// Global bone pose calculation.
|
||||
mutable LocalVector<int> nested_set_offset_to_bone_index; // Map from Bone::nested_set_offset to bone index.
|
||||
mutable LocalVector<bool> bone_global_pose_dirty; // Indexable with Bone::nested_set_offset.
|
||||
void _update_bones_nested_set() const;
|
||||
int _update_bone_nested_set(int p_bone, int p_offset) const;
|
||||
void _make_bone_global_poses_dirty() const;
|
||||
void _make_bone_global_pose_subtree_dirty(int p_bone) const;
|
||||
void _update_bone_global_pose(int p_bone) const;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void _add_bone_bind_compat_88791(const String &p_name);
|
||||
|
|
@ -193,6 +209,7 @@ protected:
|
|||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
void _notification(int p_what);
|
||||
TypedArray<StringName> _get_bone_meta_list_bind(int p_bone) const;
|
||||
static void _bind_methods();
|
||||
|
||||
virtual void add_child_notify(Node *p_child) override;
|
||||
|
|
@ -207,6 +224,7 @@ public:
|
|||
// Skeleton creation API
|
||||
uint64_t get_version() const;
|
||||
int add_bone(const String &p_name);
|
||||
void remove_bone(int p_bone);
|
||||
int find_bone(const String &p_name) const;
|
||||
String get_bone_name(int p_bone) const;
|
||||
void set_bone_name(int p_bone, const String &p_name);
|
||||
|
|
@ -238,6 +256,12 @@ public:
|
|||
void set_motion_scale(float p_motion_scale);
|
||||
float get_motion_scale() const;
|
||||
|
||||
// bone metadata
|
||||
Variant get_bone_meta(int p_bone, const StringName &p_key) const;
|
||||
void get_bone_meta_list(int p_bone, List<StringName> *p_list) const;
|
||||
bool has_bone_meta(int p_bone, const StringName &p_key) const;
|
||||
void set_bone_meta(int p_bone, const StringName &p_key, const Variant &p_value);
|
||||
|
||||
// Posing API
|
||||
Transform3D get_bone_pose(int p_bone) const;
|
||||
Vector3 get_bone_pose_position(int p_bone) const;
|
||||
|
|
@ -261,8 +285,12 @@ public:
|
|||
Ref<SkinReference> register_skin(const Ref<Skin> &p_skin);
|
||||
|
||||
void force_update_all_dirty_bones();
|
||||
void _force_update_all_dirty_bones() const;
|
||||
void force_update_all_bone_transforms();
|
||||
void _force_update_all_bone_transforms() const;
|
||||
void force_update_bone_children_transforms(int bone_idx);
|
||||
void _force_update_bone_children_transforms(int bone_idx) const;
|
||||
void force_update_deferred();
|
||||
|
||||
void set_modifier_callback_mode_process(ModifierCallbackModeProcess p_mode);
|
||||
ModifierCallbackModeProcess get_modifier_callback_mode_process() const;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ class SkeletonIK3D : public SkeletonModifier3D {
|
|||
real_t min_distance = 0.01;
|
||||
int max_iterations = 10;
|
||||
|
||||
Variant target_node_override_ref = Variant();
|
||||
Variant target_node_override_ref;
|
||||
FabrikInverseKinematic::Task *task = nullptr;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -75,6 +75,17 @@ void SkeletonModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new)
|
|||
//
|
||||
}
|
||||
|
||||
void SkeletonModifier3D::_force_update_skeleton_skin() {
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
Skeleton3D *skeleton = get_skeleton();
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
skeleton->force_update_deferred();
|
||||
}
|
||||
|
||||
/* Process */
|
||||
|
||||
void SkeletonModifier3D::set_active(bool p_active) {
|
||||
|
|
@ -83,6 +94,7 @@ void SkeletonModifier3D::set_active(bool p_active) {
|
|||
}
|
||||
active = p_active;
|
||||
_set_active(active);
|
||||
_force_update_skeleton_skin();
|
||||
}
|
||||
|
||||
bool SkeletonModifier3D::is_active() const {
|
||||
|
|
@ -119,6 +131,10 @@ void SkeletonModifier3D::_notification(int p_what) {
|
|||
case NOTIFICATION_PARENTED: {
|
||||
_update_skeleton();
|
||||
} break;
|
||||
case NOTIFICATION_EXIT_TREE:
|
||||
case NOTIFICATION_UNPARENTED: {
|
||||
_force_update_skeleton_skin();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,6 +152,73 @@ void SkeletonModifier3D::_bind_methods() {
|
|||
|
||||
ADD_SIGNAL(MethodInfo("modification_processed"));
|
||||
GDVIRTUAL_BIND(_process_modification);
|
||||
|
||||
BIND_ENUM_CONSTANT(BONE_AXIS_PLUS_X);
|
||||
BIND_ENUM_CONSTANT(BONE_AXIS_MINUS_X);
|
||||
BIND_ENUM_CONSTANT(BONE_AXIS_PLUS_Y);
|
||||
BIND_ENUM_CONSTANT(BONE_AXIS_MINUS_Y);
|
||||
BIND_ENUM_CONSTANT(BONE_AXIS_PLUS_Z);
|
||||
BIND_ENUM_CONSTANT(BONE_AXIS_MINUS_Z);
|
||||
}
|
||||
|
||||
Vector3 SkeletonModifier3D::get_vector_from_bone_axis(BoneAxis p_axis) {
|
||||
Vector3 ret;
|
||||
switch (p_axis) {
|
||||
case BONE_AXIS_PLUS_X: {
|
||||
ret = Vector3(1, 0, 0);
|
||||
} break;
|
||||
case BONE_AXIS_MINUS_X: {
|
||||
ret = Vector3(-1, 0, 0);
|
||||
} break;
|
||||
case BONE_AXIS_PLUS_Y: {
|
||||
ret = Vector3(0, 1, 0);
|
||||
} break;
|
||||
case BONE_AXIS_MINUS_Y: {
|
||||
ret = Vector3(0, -1, 0);
|
||||
} break;
|
||||
case BONE_AXIS_PLUS_Z: {
|
||||
ret = Vector3(0, 0, 1);
|
||||
} break;
|
||||
case BONE_AXIS_MINUS_Z: {
|
||||
ret = Vector3(0, 0, -1);
|
||||
} break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vector3 SkeletonModifier3D::get_vector_from_axis(Vector3::Axis p_axis) {
|
||||
Vector3 ret;
|
||||
switch (p_axis) {
|
||||
case Vector3::AXIS_X: {
|
||||
ret = Vector3(1, 0, 0);
|
||||
} break;
|
||||
case Vector3::AXIS_Y: {
|
||||
ret = Vector3(0, 1, 0);
|
||||
} break;
|
||||
case Vector3::AXIS_Z: {
|
||||
ret = Vector3(0, 0, 1);
|
||||
} break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vector3::Axis SkeletonModifier3D::get_axis_from_bone_axis(BoneAxis p_axis) {
|
||||
Vector3::Axis ret = Vector3::AXIS_X;
|
||||
switch (p_axis) {
|
||||
case BONE_AXIS_PLUS_X:
|
||||
case BONE_AXIS_MINUS_X: {
|
||||
ret = Vector3::AXIS_X;
|
||||
} break;
|
||||
case BONE_AXIS_PLUS_Y:
|
||||
case BONE_AXIS_MINUS_Y: {
|
||||
ret = Vector3::AXIS_Y;
|
||||
} break;
|
||||
case BONE_AXIS_PLUS_Z:
|
||||
case BONE_AXIS_MINUS_Z: {
|
||||
ret = Vector3::AXIS_Z;
|
||||
} break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SkeletonModifier3D::SkeletonModifier3D() {
|
||||
|
|
|
|||
|
|
@ -34,13 +34,22 @@
|
|||
#include "scene/3d/node_3d.h"
|
||||
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "scene/animation/animation_mixer.h"
|
||||
|
||||
class SkeletonModifier3D : public Node3D {
|
||||
GDCLASS(SkeletonModifier3D, Node3D);
|
||||
|
||||
void rebind();
|
||||
|
||||
public:
|
||||
enum BoneAxis {
|
||||
BONE_AXIS_PLUS_X,
|
||||
BONE_AXIS_MINUS_X,
|
||||
BONE_AXIS_PLUS_Y,
|
||||
BONE_AXIS_MINUS_Y,
|
||||
BONE_AXIS_PLUS_Z,
|
||||
BONE_AXIS_MINUS_Z,
|
||||
};
|
||||
|
||||
protected:
|
||||
bool active = true;
|
||||
real_t influence = 1.0;
|
||||
|
|
@ -50,6 +59,7 @@ protected:
|
|||
|
||||
void _update_skeleton();
|
||||
void _update_skeleton_path();
|
||||
void _force_update_skeleton_skin();
|
||||
|
||||
virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new);
|
||||
|
||||
|
|
@ -76,7 +86,18 @@ public:
|
|||
|
||||
void process_modification();
|
||||
|
||||
// Utility APIs.
|
||||
static Vector3 get_vector_from_bone_axis(BoneAxis p_axis);
|
||||
static Vector3 get_vector_from_axis(Vector3::Axis p_axis);
|
||||
static Vector3::Axis get_axis_from_bone_axis(BoneAxis p_axis);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual bool is_processed_on_saving() const { return false; }
|
||||
#endif
|
||||
|
||||
SkeletonModifier3D();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(SkeletonModifier3D::BoneAxis);
|
||||
|
||||
#endif // SKELETON_MODIFIER_3D_H
|
||||
|
|
|
|||
41
engine/scene/3d/soft_body_3d.compat.inc
Normal file
41
engine/scene/3d/soft_body_3d.compat.inc
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**************************************************************************/
|
||||
/* soft_body_3d.compat.inc */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
void SoftBody3D::_pin_point_bind_compat_94684(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path) {
|
||||
pin_point(p_point_index, pin, p_spatial_attachment_path, -1);
|
||||
}
|
||||
|
||||
void SoftBody3D::_bind_compatibility_methods() {
|
||||
ClassDB::bind_compatibility_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path"), &SoftBody3D::_pin_point_bind_compat_94684, DEFVAL(NodePath()));
|
||||
}
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
/**************************************************************************/
|
||||
|
||||
#include "soft_body_3d.h"
|
||||
#include "soft_body_3d.compat.inc"
|
||||
|
||||
#include "scene/3d/physics/physics_body_3d.h"
|
||||
|
||||
|
|
@ -200,12 +201,18 @@ bool SoftBody3D::_set_property_pinned_points_indices(const Array &p_indices) {
|
|||
int point_index;
|
||||
for (int i = 0; i < p_indices_size; ++i) {
|
||||
point_index = p_indices.get(i);
|
||||
if (w[i].point_index != point_index) {
|
||||
if (-1 != w[i].point_index) {
|
||||
if (w[i].point_index != point_index || pinned_points.size() < p_indices_size) {
|
||||
bool insert = false;
|
||||
if (w[i].point_index != -1 && p_indices.find(w[i].point_index) == -1) {
|
||||
pin_point(w[i].point_index, false);
|
||||
insert = true;
|
||||
}
|
||||
w[i].point_index = point_index;
|
||||
pin_point(w[i].point_index, true);
|
||||
if (insert) {
|
||||
pin_point(w[i].point_index, true, NodePath(), i);
|
||||
} else {
|
||||
pin_point(w[i].point_index, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
@ -218,7 +225,13 @@ bool SoftBody3D::_set_property_pinned_points_attachment(int p_item, const String
|
|||
|
||||
if ("spatial_attachment_path" == p_what) {
|
||||
PinnedPoint *w = pinned_points.ptrw();
|
||||
callable_mp(this, &SoftBody3D::_pin_point_deferred).call_deferred(Variant(w[p_item].point_index), true, p_value);
|
||||
|
||||
if (is_inside_tree()) {
|
||||
callable_mp(this, &SoftBody3D::_pin_point_deferred).call_deferred(Variant(w[p_item].point_index), true, p_value);
|
||||
} else {
|
||||
pin_point(w[p_item].point_index, true, p_value);
|
||||
_make_cache_dirty();
|
||||
}
|
||||
} else if ("offset" == p_what) {
|
||||
PinnedPoint *w = pinned_points.ptrw();
|
||||
w[p_item].offset = p_value;
|
||||
|
|
@ -350,7 +363,7 @@ void SoftBody3D::_bind_methods() {
|
|||
|
||||
ClassDB::bind_method(D_METHOD("get_point_transform", "point_index"), &SoftBody3D::get_point_transform);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path"), &SoftBody3D::pin_point, DEFVAL(NodePath()));
|
||||
ClassDB::bind_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path", "insert_at"), &SoftBody3D::pin_point, DEFVAL(NodePath()), DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("is_point_pinned", "point_index"), &SoftBody3D::is_point_pinned);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_ray_pickable", "ray_pickable"), &SoftBody3D::set_ray_pickable);
|
||||
|
|
@ -377,7 +390,7 @@ void SoftBody3D::_bind_methods() {
|
|||
}
|
||||
|
||||
PackedStringArray SoftBody3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = MeshInstance3D::get_configuration_warnings();
|
||||
|
||||
if (mesh.is_null()) {
|
||||
warnings.push_back(RTR("This body will be ignored until you set a mesh."));
|
||||
|
|
@ -662,10 +675,11 @@ void SoftBody3D::pin_point_toggle(int p_point_index) {
|
|||
pin_point(p_point_index, !(-1 != _has_pinned_point(p_point_index)));
|
||||
}
|
||||
|
||||
void SoftBody3D::pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path) {
|
||||
void SoftBody3D::pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path, int p_insert_at) {
|
||||
ERR_FAIL_COND_MSG(p_insert_at < -1 || p_insert_at >= pinned_points.size(), "Invalid index for pin point insertion position.");
|
||||
_pin_point_on_physics_server(p_point_index, pin);
|
||||
if (pin) {
|
||||
_add_pinned_point(p_point_index, p_spatial_attachment_path);
|
||||
_add_pinned_point(p_point_index, p_spatial_attachment_path, p_insert_at);
|
||||
} else {
|
||||
_remove_pinned_point(p_point_index);
|
||||
}
|
||||
|
|
@ -724,7 +738,7 @@ void SoftBody3D::_pin_point_on_physics_server(int p_point_index, bool pin) {
|
|||
PhysicsServer3D::get_singleton()->soft_body_pin_point(physics_rid, p_point_index, pin);
|
||||
}
|
||||
|
||||
void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path) {
|
||||
void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path, int p_insert_at) {
|
||||
SoftBody3D::PinnedPoint *pinned_point;
|
||||
if (-1 == _get_pinned_point(p_point_index, pinned_point)) {
|
||||
// Create new
|
||||
|
|
@ -737,7 +751,11 @@ void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_
|
|||
pp.offset = (pp.spatial_attachment->get_global_transform().affine_inverse() * get_global_transform()).xform(PhysicsServer3D::get_singleton()->soft_body_get_point_global_position(physics_rid, pp.point_index));
|
||||
}
|
||||
|
||||
pinned_points.push_back(pp);
|
||||
if (p_insert_at != -1) {
|
||||
pinned_points.insert(p_insert_at, pp);
|
||||
} else {
|
||||
pinned_points.push_back(pp);
|
||||
}
|
||||
|
||||
} else {
|
||||
pinned_point->point_index = p_point_index;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@
|
|||
#include "servers/physics_server_3d.h"
|
||||
|
||||
class PhysicsBody3D;
|
||||
class SoftBody3D;
|
||||
|
||||
class SoftBodyRenderingServerHandler : public PhysicsServer3DRenderingServerHandler {
|
||||
friend class SoftBody3D;
|
||||
|
|
@ -126,6 +125,11 @@ protected:
|
|||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void _pin_point_bind_compat_94684(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath());
|
||||
static void _bind_compatibility_methods();
|
||||
#endif
|
||||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
public:
|
||||
|
|
@ -177,7 +181,7 @@ public:
|
|||
Vector3 get_point_transform(int p_point_index);
|
||||
|
||||
void pin_point_toggle(int p_point_index);
|
||||
void pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath());
|
||||
void pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath(), int p_insert_at = -1);
|
||||
bool is_point_pinned(int p_point_index) const;
|
||||
|
||||
void _pin_point_deferred(int p_point_index, bool pin, const NodePath p_spatial_attachment_path);
|
||||
|
|
@ -193,7 +197,7 @@ private:
|
|||
void _update_cache_pin_points_datas();
|
||||
|
||||
void _pin_point_on_physics_server(int p_point_index, bool pin);
|
||||
void _add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path);
|
||||
void _add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path, int p_insert_at = -1);
|
||||
void _reset_points_offsets();
|
||||
|
||||
void _remove_pinned_point(int p_point_index);
|
||||
|
|
|
|||
182
engine/scene/3d/spring_bone_collision_3d.cpp
Normal file
182
engine/scene/3d/spring_bone_collision_3d.cpp
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/**************************************************************************/
|
||||
/* spring_bone_collision_3d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "spring_bone_collision_3d.h"
|
||||
|
||||
#include "scene/3d/spring_bone_simulator_3d.h"
|
||||
|
||||
PackedStringArray SpringBoneCollision3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node3D::get_configuration_warnings();
|
||||
|
||||
SpringBoneSimulator3D *parent = Object::cast_to<SpringBoneSimulator3D>(get_parent());
|
||||
if (!parent) {
|
||||
warnings.push_back(RTR("Parent node should be a SpringBoneSimulator3D node."));
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void SpringBoneCollision3D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "bone_name") {
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
p_property.hint = PROPERTY_HINT_ENUM_SUGGESTION;
|
||||
p_property.hint_string = sk->get_concatenated_bone_names();
|
||||
} else {
|
||||
p_property.hint = PROPERTY_HINT_NONE;
|
||||
p_property.hint_string = "";
|
||||
}
|
||||
} else if (bone < 0 && (p_property.name == "position_offset" || p_property.name == "rotation_offset")) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
Skeleton3D *SpringBoneCollision3D::get_skeleton() const {
|
||||
SpringBoneSimulator3D *parent = Object::cast_to<SpringBoneSimulator3D>(get_parent());
|
||||
if (!parent) {
|
||||
return nullptr;
|
||||
}
|
||||
return parent->get_skeleton();
|
||||
}
|
||||
|
||||
void SpringBoneCollision3D::set_bone_name(const String &p_name) {
|
||||
bone_name = p_name;
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
set_bone(sk->find_bone(bone_name));
|
||||
}
|
||||
}
|
||||
|
||||
String SpringBoneCollision3D::get_bone_name() const {
|
||||
return bone_name;
|
||||
}
|
||||
|
||||
void SpringBoneCollision3D::set_bone(int p_bone) {
|
||||
bone = p_bone;
|
||||
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
if (bone <= -1 || bone >= sk->get_bone_count()) {
|
||||
WARN_PRINT("Bone index out of range! Cannot connect BoneAttachment to node!");
|
||||
bone = -1;
|
||||
} else {
|
||||
bone_name = sk->get_bone_name(bone);
|
||||
}
|
||||
}
|
||||
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
int SpringBoneCollision3D::get_bone() const {
|
||||
return bone;
|
||||
}
|
||||
|
||||
void SpringBoneCollision3D::set_position_offset(const Vector3 &p_offset) {
|
||||
if (position_offset == p_offset) {
|
||||
return;
|
||||
}
|
||||
position_offset = p_offset;
|
||||
sync_pose();
|
||||
#ifdef TOOLS_ENABLED
|
||||
update_gizmos();
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
Vector3 SpringBoneCollision3D::get_position_offset() const {
|
||||
return position_offset;
|
||||
}
|
||||
|
||||
void SpringBoneCollision3D::set_rotation_offset(const Quaternion &p_offset) {
|
||||
if (rotation_offset == p_offset) {
|
||||
return;
|
||||
}
|
||||
rotation_offset = p_offset;
|
||||
sync_pose();
|
||||
#ifdef TOOLS_ENABLED
|
||||
update_gizmos();
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
Quaternion SpringBoneCollision3D::get_rotation_offset() const {
|
||||
return rotation_offset;
|
||||
}
|
||||
|
||||
void SpringBoneCollision3D::sync_pose() {
|
||||
if (bone >= 0) {
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
Transform3D tr = sk->get_global_transform() * sk->get_bone_global_pose(bone);
|
||||
tr.origin += tr.basis.get_rotation_quaternion().xform(position_offset);
|
||||
tr.basis *= Basis(rotation_offset);
|
||||
set_global_transform(tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Transform3D SpringBoneCollision3D::get_transform_from_skeleton(const Transform3D &p_center) const {
|
||||
Transform3D gtr = get_global_transform();
|
||||
Skeleton3D *sk = get_skeleton();
|
||||
if (sk) {
|
||||
Transform3D tr = sk->get_global_transform();
|
||||
gtr = tr.affine_inverse() * p_center * gtr;
|
||||
}
|
||||
return gtr;
|
||||
}
|
||||
|
||||
void SpringBoneCollision3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_skeleton"), &SpringBoneCollision3D::get_skeleton);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &SpringBoneCollision3D::set_bone_name);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_name"), &SpringBoneCollision3D::get_bone_name);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_bone", "bone"), &SpringBoneCollision3D::set_bone);
|
||||
ClassDB::bind_method(D_METHOD("get_bone"), &SpringBoneCollision3D::get_bone);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &SpringBoneCollision3D::set_position_offset);
|
||||
ClassDB::bind_method(D_METHOD("get_position_offset"), &SpringBoneCollision3D::get_position_offset);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_rotation_offset", "offset"), &SpringBoneCollision3D::set_rotation_offset);
|
||||
ClassDB::bind_method(D_METHOD("get_rotation_offset"), &SpringBoneCollision3D::get_rotation_offset);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_bone", "get_bone");
|
||||
|
||||
ADD_GROUP("Offset", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position_offset"), "set_position_offset", "get_position_offset");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "rotation_offset"), "set_rotation_offset", "get_rotation_offset");
|
||||
}
|
||||
|
||||
Vector3 SpringBoneCollision3D::collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const {
|
||||
return _collide(p_center, p_bone_radius, p_bone_length, p_current);
|
||||
}
|
||||
|
||||
Vector3 SpringBoneCollision3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const {
|
||||
return Vector3(0, 0, 0);
|
||||
}
|
||||
72
engine/scene/3d/spring_bone_collision_3d.h
Normal file
72
engine/scene/3d/spring_bone_collision_3d.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/**************************************************************************/
|
||||
/* spring_bone_collision_3d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef SPRING_BONE_COLLISION_3D_H
|
||||
#define SPRING_BONE_COLLISION_3D_H
|
||||
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
|
||||
class SpringBoneCollision3D : public Node3D {
|
||||
GDCLASS(SpringBoneCollision3D, Node3D);
|
||||
|
||||
String bone_name;
|
||||
int bone = -1;
|
||||
|
||||
Vector3 position_offset;
|
||||
Quaternion rotation_offset;
|
||||
|
||||
protected:
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
static void _bind_methods();
|
||||
|
||||
virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const;
|
||||
|
||||
public:
|
||||
Skeleton3D *get_skeleton() const;
|
||||
|
||||
void set_bone_name(const String &p_name);
|
||||
String get_bone_name() const;
|
||||
void set_bone(int p_bone);
|
||||
int get_bone() const;
|
||||
|
||||
void set_position_offset(const Vector3 &p_offset);
|
||||
Vector3 get_position_offset() const;
|
||||
void set_rotation_offset(const Quaternion &p_offset);
|
||||
Quaternion get_rotation_offset() const;
|
||||
|
||||
void sync_pose();
|
||||
Transform3D get_transform_from_skeleton(const Transform3D &p_center) const;
|
||||
|
||||
Vector3 collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const;
|
||||
};
|
||||
|
||||
#endif // SPRING_BONE_COLLISION_3D_H
|
||||
112
engine/scene/3d/spring_bone_collision_capsule_3d.cpp
Normal file
112
engine/scene/3d/spring_bone_collision_capsule_3d.cpp
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/**************************************************************************/
|
||||
/* spring_bone_collision_capsule_3d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "spring_bone_collision_capsule_3d.h"
|
||||
|
||||
#include "scene/3d/spring_bone_collision_sphere_3d.h"
|
||||
|
||||
void SpringBoneCollisionCapsule3D::set_radius(float p_radius) {
|
||||
radius = p_radius;
|
||||
if (radius > height * 0.5) {
|
||||
height = radius * 2.0;
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
update_gizmos();
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
float SpringBoneCollisionCapsule3D::get_radius() const {
|
||||
return radius;
|
||||
}
|
||||
|
||||
void SpringBoneCollisionCapsule3D::set_height(float p_height) {
|
||||
height = p_height;
|
||||
if (radius > height * 0.5) {
|
||||
radius = height * 0.5;
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
update_gizmos();
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
float SpringBoneCollisionCapsule3D::get_height() const {
|
||||
return height;
|
||||
}
|
||||
|
||||
void SpringBoneCollisionCapsule3D::set_inside(bool p_enabled) {
|
||||
inside = p_enabled;
|
||||
#ifdef TOOLS_ENABLED
|
||||
update_gizmos();
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
bool SpringBoneCollisionCapsule3D::is_inside() const {
|
||||
return inside;
|
||||
}
|
||||
|
||||
Pair<Vector3, Vector3> SpringBoneCollisionCapsule3D::get_head_and_tail(const Transform3D &p_center) const {
|
||||
static const Vector3 VECTOR3_UP = Vector3(0, 1, 0);
|
||||
static const Vector3 VECTOR3_DOWN = Vector3(0, -1, 0);
|
||||
Transform3D tr = get_transform_from_skeleton(p_center);
|
||||
return Pair<Vector3, Vector3>(tr.origin + tr.basis.xform(VECTOR3_UP * (height * 0.5 - radius)), tr.origin + tr.basis.xform(VECTOR3_DOWN * (height * 0.5 - radius)));
|
||||
}
|
||||
|
||||
void SpringBoneCollisionCapsule3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SpringBoneCollisionCapsule3D::set_radius);
|
||||
ClassDB::bind_method(D_METHOD("get_radius"), &SpringBoneCollisionCapsule3D::get_radius);
|
||||
ClassDB::bind_method(D_METHOD("set_height", "height"), &SpringBoneCollisionCapsule3D::set_height);
|
||||
ClassDB::bind_method(D_METHOD("get_height"), &SpringBoneCollisionCapsule3D::get_height);
|
||||
ClassDB::bind_method(D_METHOD("set_inside", "enabled"), &SpringBoneCollisionCapsule3D::set_inside);
|
||||
ClassDB::bind_method(D_METHOD("is_inside"), &SpringBoneCollisionCapsule3D::is_inside);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_radius", "get_radius");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_height", "get_height");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "inside"), "set_inside", "is_inside");
|
||||
}
|
||||
|
||||
Vector3 SpringBoneCollisionCapsule3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const {
|
||||
Pair<Vector3, Vector3> head_tail = get_head_and_tail(p_center);
|
||||
Vector3 head = head_tail.first;
|
||||
Vector3 tail = head_tail.second;
|
||||
Vector3 p = tail - head;
|
||||
Vector3 q = p_current - head;
|
||||
float dot = p.dot(q);
|
||||
if (dot <= 0) {
|
||||
return SpringBoneCollisionSphere3D::_collide_sphere(head, radius, inside, p_bone_radius, p_bone_length, p_current);
|
||||
}
|
||||
float pls = p.length_squared();
|
||||
if (Math::is_zero_approx(pls)) {
|
||||
return p_current;
|
||||
}
|
||||
if (pls <= dot) {
|
||||
return SpringBoneCollisionSphere3D::_collide_sphere(head + p, radius, inside, p_bone_radius, p_bone_length, p_current);
|
||||
}
|
||||
return SpringBoneCollisionSphere3D::_collide_sphere(head + p * (dot / pls), radius, inside, p_bone_radius, p_bone_length, p_current);
|
||||
}
|
||||
60
engine/scene/3d/spring_bone_collision_capsule_3d.h
Normal file
60
engine/scene/3d/spring_bone_collision_capsule_3d.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**************************************************************************/
|
||||
/* spring_bone_collision_capsule_3d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef SPRING_BONE_COLLISION_CAPSULE_3D_H
|
||||
#define SPRING_BONE_COLLISION_CAPSULE_3D_H
|
||||
|
||||
#include "scene/3d/spring_bone_collision_3d.h"
|
||||
|
||||
class SpringBoneCollisionCapsule3D : public SpringBoneCollision3D {
|
||||
GDCLASS(SpringBoneCollisionCapsule3D, SpringBoneCollision3D);
|
||||
|
||||
float radius = 0.1;
|
||||
float height = 0.5;
|
||||
bool inside = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const override;
|
||||
|
||||
public:
|
||||
void set_radius(float p_radius);
|
||||
float get_radius() const;
|
||||
void set_height(float p_height);
|
||||
float get_height() const;
|
||||
void set_inside(bool p_enabled);
|
||||
bool is_inside() const;
|
||||
|
||||
// Helper.
|
||||
Pair<Vector3, Vector3> get_head_and_tail(const Transform3D &p_center) const;
|
||||
};
|
||||
|
||||
#endif // SPRING_BONE_COLLISION_CAPSULE_3D_H
|
||||
44
engine/scene/3d/spring_bone_collision_plane_3d.cpp
Normal file
44
engine/scene/3d/spring_bone_collision_plane_3d.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/**************************************************************************/
|
||||
/* spring_bone_collision_plane_3d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "spring_bone_collision_plane_3d.h"
|
||||
|
||||
Vector3 SpringBoneCollisionPlane3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const {
|
||||
static const Vector3 VECTOR3_UP = Vector3(0, 1, 0);
|
||||
Transform3D tr = get_transform_from_skeleton(p_center);
|
||||
Vector3 pos = tr.origin;
|
||||
Vector3 normal = tr.basis.get_rotation_quaternion().xform(VECTOR3_UP);
|
||||
Vector3 to_vec = p_current - pos;
|
||||
float distance = to_vec.dot(normal) - p_bone_radius;
|
||||
if (distance > 0) {
|
||||
return p_current;
|
||||
}
|
||||
return p_current + normal * -distance;
|
||||
}
|
||||
43
engine/scene/3d/spring_bone_collision_plane_3d.h
Normal file
43
engine/scene/3d/spring_bone_collision_plane_3d.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/**************************************************************************/
|
||||
/* spring_bone_collision_plane_3d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef SPRING_BONE_COLLISION_PLANE_3D_H
|
||||
#define SPRING_BONE_COLLISION_PLANE_3D_H
|
||||
|
||||
#include "scene/3d/spring_bone_collision_3d.h"
|
||||
|
||||
class SpringBoneCollisionPlane3D : public SpringBoneCollision3D {
|
||||
GDCLASS(SpringBoneCollisionPlane3D, SpringBoneCollision3D);
|
||||
|
||||
protected:
|
||||
virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const override;
|
||||
};
|
||||
|
||||
#endif // SPRING_BONE_COLLISION_PLANE_3D_H
|
||||
78
engine/scene/3d/spring_bone_collision_sphere_3d.cpp
Normal file
78
engine/scene/3d/spring_bone_collision_sphere_3d.cpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/**************************************************************************/
|
||||
/* spring_bone_collision_sphere_3d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "spring_bone_collision_sphere_3d.h"
|
||||
|
||||
void SpringBoneCollisionSphere3D::set_radius(float p_radius) {
|
||||
radius = p_radius;
|
||||
#ifdef TOOLS_ENABLED
|
||||
update_gizmos();
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
float SpringBoneCollisionSphere3D::get_radius() const {
|
||||
return radius;
|
||||
}
|
||||
|
||||
void SpringBoneCollisionSphere3D::set_inside(bool p_enabled) {
|
||||
inside = p_enabled;
|
||||
#ifdef TOOLS_ENABLED
|
||||
update_gizmos();
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
bool SpringBoneCollisionSphere3D::is_inside() const {
|
||||
return inside;
|
||||
}
|
||||
|
||||
void SpringBoneCollisionSphere3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SpringBoneCollisionSphere3D::set_radius);
|
||||
ClassDB::bind_method(D_METHOD("get_radius"), &SpringBoneCollisionSphere3D::get_radius);
|
||||
ClassDB::bind_method(D_METHOD("set_inside", "enabled"), &SpringBoneCollisionSphere3D::set_inside);
|
||||
ClassDB::bind_method(D_METHOD("is_inside"), &SpringBoneCollisionSphere3D::is_inside);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_radius", "get_radius");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "inside"), "set_inside", "is_inside");
|
||||
}
|
||||
|
||||
Vector3 SpringBoneCollisionSphere3D::_collide_sphere(const Vector3 &p_origin, float p_radius, bool p_inside, float p_bone_radius, float p_bone_length, const Vector3 &p_current) {
|
||||
Vector3 diff = p_current - p_origin;
|
||||
float length = diff.length();
|
||||
float r = p_inside ? p_radius - p_bone_radius : p_bone_radius + p_radius;
|
||||
float distance = p_inside ? r - length : length - r;
|
||||
if (distance > 0) {
|
||||
return p_current;
|
||||
}
|
||||
return p_origin + diff.normalized() * r;
|
||||
}
|
||||
|
||||
Vector3 SpringBoneCollisionSphere3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const {
|
||||
return _collide_sphere(get_transform_from_skeleton(p_center).origin, radius, inside, p_bone_radius, p_bone_length, p_current);
|
||||
}
|
||||
59
engine/scene/3d/spring_bone_collision_sphere_3d.h
Normal file
59
engine/scene/3d/spring_bone_collision_sphere_3d.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**************************************************************************/
|
||||
/* spring_bone_collision_sphere_3d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef SPRING_BONE_COLLISION_SPHERE_3D_H
|
||||
#define SPRING_BONE_COLLISION_SPHERE_3D_H
|
||||
|
||||
#include "scene/3d/spring_bone_collision_3d.h"
|
||||
|
||||
class SpringBoneCollisionCapsule3D;
|
||||
|
||||
class SpringBoneCollisionSphere3D : public SpringBoneCollision3D {
|
||||
GDCLASS(SpringBoneCollisionSphere3D, SpringBoneCollision3D);
|
||||
|
||||
friend class SpringBoneCollisionCapsule3D;
|
||||
|
||||
float radius = 0.1;
|
||||
bool inside = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
static Vector3 _collide_sphere(const Vector3 &p_origin, float p_radius, bool p_inside, float p_bone_radius, float p_bone_length, const Vector3 &p_current);
|
||||
virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const override;
|
||||
|
||||
public:
|
||||
void set_radius(float p_radius);
|
||||
float get_radius() const;
|
||||
void set_inside(bool p_enabled);
|
||||
bool is_inside() const;
|
||||
};
|
||||
|
||||
#endif // SPRING_BONE_COLLISION_SPHERE_3D_H
|
||||
1649
engine/scene/3d/spring_bone_simulator_3d.cpp
Normal file
1649
engine/scene/3d/spring_bone_simulator_3d.cpp
Normal file
File diff suppressed because it is too large
Load diff
290
engine/scene/3d/spring_bone_simulator_3d.h
Normal file
290
engine/scene/3d/spring_bone_simulator_3d.h
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
/**************************************************************************/
|
||||
/* spring_bone_simulator_3d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef SPRING_BONE_SIMULATOR_3D_H
|
||||
#define SPRING_BONE_SIMULATOR_3D_H
|
||||
|
||||
#include "scene/3d/skeleton_modifier_3d.h"
|
||||
|
||||
class SpringBoneSimulator3D : public SkeletonModifier3D {
|
||||
GDCLASS(SpringBoneSimulator3D, SkeletonModifier3D);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool saving = false;
|
||||
#endif //TOOLS_ENABLED
|
||||
|
||||
bool joints_dirty = false;
|
||||
|
||||
LocalVector<ObjectID> collisions; // To process collisions for sync position with skeleton.
|
||||
bool collisions_dirty = false;
|
||||
void _find_collisions();
|
||||
void _process_collisions();
|
||||
void _make_collisions_dirty();
|
||||
|
||||
public:
|
||||
enum BoneDirection {
|
||||
BONE_DIRECTION_PLUS_X,
|
||||
BONE_DIRECTION_MINUS_X,
|
||||
BONE_DIRECTION_PLUS_Y,
|
||||
BONE_DIRECTION_MINUS_Y,
|
||||
BONE_DIRECTION_PLUS_Z,
|
||||
BONE_DIRECTION_MINUS_Z,
|
||||
BONE_DIRECTION_FROM_PARENT,
|
||||
};
|
||||
|
||||
enum CenterFrom {
|
||||
CENTER_FROM_WORLD_ORIGIN,
|
||||
CENTER_FROM_NODE,
|
||||
CENTER_FROM_BONE,
|
||||
};
|
||||
|
||||
enum RotationAxis {
|
||||
ROTATION_AXIS_X,
|
||||
ROTATION_AXIS_Y,
|
||||
ROTATION_AXIS_Z,
|
||||
ROTATION_AXIS_ALL,
|
||||
};
|
||||
|
||||
struct SpringBone3DVerletInfo {
|
||||
Vector3 prev_tail;
|
||||
Vector3 current_tail;
|
||||
Vector3 forward_vector;
|
||||
Quaternion current_rot;
|
||||
float length = 0.0;
|
||||
};
|
||||
|
||||
struct SpringBone3DJointSetting {
|
||||
String bone_name;
|
||||
int bone = -1;
|
||||
|
||||
RotationAxis rotation_axis = ROTATION_AXIS_ALL;
|
||||
float radius = 0.1;
|
||||
float stiffness = 1.0;
|
||||
float drag = 0.0;
|
||||
float gravity = 0.0;
|
||||
Vector3 gravity_direction = Vector3(0, -1, 0);
|
||||
|
||||
// To process.
|
||||
SpringBone3DVerletInfo *verlet = nullptr;
|
||||
};
|
||||
|
||||
struct SpringBone3DSetting {
|
||||
bool joints_dirty = false;
|
||||
|
||||
String root_bone_name;
|
||||
int root_bone = -1;
|
||||
|
||||
String end_bone_name;
|
||||
int end_bone = -1;
|
||||
|
||||
// To make virtual end joint.
|
||||
bool extend_end_bone = false;
|
||||
BoneDirection end_bone_direction = BONE_DIRECTION_FROM_PARENT;
|
||||
float end_bone_length = 0.0;
|
||||
|
||||
CenterFrom center_from = CENTER_FROM_WORLD_ORIGIN;
|
||||
NodePath center_node;
|
||||
String center_bone_name;
|
||||
int center_bone = -1;
|
||||
|
||||
// Cache into joints.
|
||||
bool individual_config = false;
|
||||
float radius = 0.02;
|
||||
Ref<Curve> radius_damping_curve;
|
||||
float stiffness = 1.0;
|
||||
Ref<Curve> stiffness_damping_curve;
|
||||
float drag = 0.4;
|
||||
Ref<Curve> drag_damping_curve;
|
||||
float gravity = 0.0;
|
||||
Ref<Curve> gravity_damping_curve;
|
||||
Vector3 gravity_direction = Vector3(0, -1, 0);
|
||||
RotationAxis rotation_axis = ROTATION_AXIS_ALL;
|
||||
Vector<SpringBone3DJointSetting *> joints;
|
||||
|
||||
// Cache into collisions.
|
||||
bool enable_all_child_collisions = true;
|
||||
Vector<NodePath> collisions;
|
||||
Vector<NodePath> exclude_collisions;
|
||||
LocalVector<ObjectID> cached_collisions;
|
||||
|
||||
// To process.
|
||||
bool simulation_dirty = false;
|
||||
Transform3D cached_center;
|
||||
Transform3D cached_inverted_center;
|
||||
};
|
||||
|
||||
protected:
|
||||
Vector<SpringBone3DSetting *> settings;
|
||||
|
||||
bool _get(const StringName &p_path, Variant &r_ret) const;
|
||||
bool _set(const StringName &p_path, const Variant &p_value);
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
void _notification(int p_what);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
virtual void _set_active(bool p_active) override;
|
||||
virtual void _process_modification() override;
|
||||
void _init_joints(Skeleton3D *p_skeleton, SpringBone3DSetting *p_setting);
|
||||
void _process_joints(double p_delta, Skeleton3D *p_skeleton, Vector<SpringBone3DJointSetting *> &p_joints, const LocalVector<ObjectID> &p_collisions, const Transform3D &p_center_transform, const Transform3D &p_inverted_center_transform, const Quaternion &p_inverted_center_rotation);
|
||||
|
||||
void _make_joints_dirty(int p_index);
|
||||
void _make_all_joints_dirty();
|
||||
|
||||
void _update_joint_array(int p_index);
|
||||
void _update_joints();
|
||||
|
||||
virtual void add_child_notify(Node *p_child) override;
|
||||
virtual void move_child_notify(Node *p_child) override;
|
||||
virtual void remove_child_notify(Node *p_child) override;
|
||||
|
||||
void _validate_rotation_axes(Skeleton3D *p_skeleton) const;
|
||||
void _validate_rotation_axis(Skeleton3D *p_skeleton, int p_index, int p_joint) const;
|
||||
|
||||
public:
|
||||
// Setting.
|
||||
void set_root_bone_name(int p_index, const String &p_bone_name);
|
||||
String get_root_bone_name(int p_index) const;
|
||||
void set_root_bone(int p_index, int p_bone);
|
||||
int get_root_bone(int p_index) const;
|
||||
|
||||
void set_end_bone_name(int p_index, const String &p_bone_name);
|
||||
String get_end_bone_name(int p_index) const;
|
||||
void set_end_bone(int p_index, int p_bone);
|
||||
int get_end_bone(int p_index) const;
|
||||
|
||||
void set_extend_end_bone(int p_index, bool p_enabled);
|
||||
bool is_end_bone_extended(int p_index) const;
|
||||
void set_end_bone_direction(int p_index, BoneDirection p_bone_direction);
|
||||
BoneDirection get_end_bone_direction(int p_index) const;
|
||||
void set_end_bone_length(int p_index, float p_length);
|
||||
float get_end_bone_length(int p_index) const;
|
||||
Vector3 get_end_bone_axis(int p_end_bone, BoneDirection p_direction) const; // Helper.
|
||||
|
||||
void set_center_from(int p_index, CenterFrom p_center_from);
|
||||
CenterFrom get_center_from(int p_index) const;
|
||||
void set_center_node(int p_index, const NodePath &p_node_path);
|
||||
NodePath get_center_node(int p_index) const;
|
||||
void set_center_bone_name(int p_index, const String &p_bone_name);
|
||||
String get_center_bone_name(int p_index) const;
|
||||
void set_center_bone(int p_index, int p_bone);
|
||||
int get_center_bone(int p_index) const;
|
||||
|
||||
void set_rotation_axis(int p_index, RotationAxis p_axis);
|
||||
RotationAxis get_rotation_axis(int p_index) const;
|
||||
void set_radius(int p_index, float p_radius);
|
||||
float get_radius(int p_index) const;
|
||||
void set_radius_damping_curve(int p_index, const Ref<Curve> &p_damping_curve);
|
||||
Ref<Curve> get_radius_damping_curve(int p_index) const;
|
||||
void set_stiffness(int p_index, float p_stiffness);
|
||||
float get_stiffness(int p_index) const;
|
||||
void set_stiffness_damping_curve(int p_index, const Ref<Curve> &p_damping_curve);
|
||||
Ref<Curve> get_stiffness_damping_curve(int p_index) const;
|
||||
void set_drag(int p_index, float p_drag);
|
||||
float get_drag(int p_index) const;
|
||||
void set_drag_damping_curve(int p_index, const Ref<Curve> &p_damping_curve);
|
||||
Ref<Curve> get_drag_damping_curve(int p_index) const;
|
||||
void set_gravity(int p_index, float p_gravity);
|
||||
float get_gravity(int p_index) const;
|
||||
void set_gravity_damping_curve(int p_index, const Ref<Curve> &p_damping_curve);
|
||||
Ref<Curve> get_gravity_damping_curve(int p_index) const;
|
||||
void set_gravity_direction(int p_index, const Vector3 &p_gravity_direction);
|
||||
Vector3 get_gravity_direction(int p_index) const;
|
||||
|
||||
void set_setting_count(int p_count);
|
||||
int get_setting_count() const;
|
||||
void clear_settings();
|
||||
|
||||
// Individual joints.
|
||||
void set_individual_config(int p_index, bool p_enabled);
|
||||
bool is_config_individual(int p_index) const;
|
||||
|
||||
void set_joint_bone_name(int p_index, int p_joint, const String &p_bone_name);
|
||||
String get_joint_bone_name(int p_index, int p_joint) const;
|
||||
void set_joint_bone(int p_index, int p_joint, int p_bone);
|
||||
int get_joint_bone(int p_index, int p_joint) const;
|
||||
|
||||
void set_joint_rotation_axis(int p_index, int p_joint, RotationAxis p_axis);
|
||||
RotationAxis get_joint_rotation_axis(int p_index, int p_joint) const;
|
||||
void set_joint_radius(int p_index, int p_joint, float p_radius);
|
||||
float get_joint_radius(int p_index, int p_joint) const;
|
||||
void set_joint_stiffness(int p_index, int p_joint, float p_stiffness);
|
||||
float get_joint_stiffness(int p_index, int p_joint) const;
|
||||
void set_joint_drag(int p_index, int p_joint, float p_drag);
|
||||
float get_joint_drag(int p_index, int p_joint) const;
|
||||
void set_joint_gravity(int p_index, int p_joint, float p_gravity);
|
||||
float get_joint_gravity(int p_index, int p_joint) const;
|
||||
void set_joint_gravity_direction(int p_index, int p_joint, const Vector3 &p_gravity_direction);
|
||||
Vector3 get_joint_gravity_direction(int p_index, int p_joint) const;
|
||||
|
||||
void set_joint_count(int p_index, int p_count);
|
||||
int get_joint_count(int p_index) const;
|
||||
|
||||
// Individual collisions.
|
||||
void set_enable_all_child_collisions(int p_index, bool p_enabled);
|
||||
bool are_all_child_collisions_enabled(int p_index) const;
|
||||
|
||||
void set_exclude_collision_path(int p_index, int p_collision, const NodePath &p_node_path);
|
||||
NodePath get_exclude_collision_path(int p_index, int p_collision) const;
|
||||
|
||||
void set_exclude_collision_count(int p_index, int p_count);
|
||||
int get_exclude_collision_count(int p_index) const;
|
||||
void clear_exclude_collisions(int p_index);
|
||||
|
||||
void set_collision_path(int p_index, int p_collision, const NodePath &p_node_path);
|
||||
NodePath get_collision_path(int p_index, int p_collision) const;
|
||||
|
||||
void set_collision_count(int p_index, int p_count);
|
||||
int get_collision_count(int p_index) const;
|
||||
void clear_collisions(int p_index);
|
||||
|
||||
LocalVector<ObjectID> get_valid_collision_instance_ids(int p_index);
|
||||
|
||||
// Helper.
|
||||
static Quaternion get_local_pose_rotation(Skeleton3D *p_skeleton, int p_bone, const Quaternion &p_global_pose_rotation);
|
||||
static Quaternion get_from_to_rotation(const Vector3 &p_from, const Vector3 &p_to, const Quaternion &p_prev_rot);
|
||||
static Vector3 snap_position_to_plane(const Transform3D &p_rest, RotationAxis p_axis, const Vector3 &p_position);
|
||||
static Vector3 limit_length(const Vector3 &p_origin, const Vector3 &p_destination, float p_length);
|
||||
|
||||
// To process manually.
|
||||
void reset();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual bool is_processed_on_saving() const override { return true; }
|
||||
#endif
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(SpringBoneSimulator3D::BoneDirection);
|
||||
VARIANT_ENUM_CAST(SpringBoneSimulator3D::CenterFrom);
|
||||
VARIANT_ENUM_CAST(SpringBoneSimulator3D::RotationAxis);
|
||||
|
||||
#endif // SPRING_BONE_SIMULATOR_3D_H
|
||||
|
|
@ -132,7 +132,7 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
|
|||
|
||||
// Properly setup UVs for impostor textures (AtlasTexture).
|
||||
Ref<AtlasTexture> atlas_tex = p_texture;
|
||||
if (atlas_tex != nullptr) {
|
||||
if (atlas_tex.is_valid()) {
|
||||
src_tsize[0] = atlas_tex->get_atlas()->get_width();
|
||||
src_tsize[1] = atlas_tex->get_atlas()->get_height();
|
||||
}
|
||||
|
|
@ -276,6 +276,7 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
|
|||
}
|
||||
if (last_texture != p_texture->get_rid()) {
|
||||
RS::get_singleton()->material_set_param(get_material(), "texture_albedo", p_texture->get_rid());
|
||||
RS::get_singleton()->material_set_param(get_material(), "albedo_texture_size", Vector2i(p_texture->get_width(), p_texture->get_height()));
|
||||
last_texture = p_texture->get_rid();
|
||||
}
|
||||
if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) {
|
||||
|
|
@ -470,7 +471,7 @@ Ref<TriangleMesh> SpriteBase3D::generate_triangle_mesh() const {
|
|||
facesw[j] = vtx;
|
||||
}
|
||||
|
||||
triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh));
|
||||
triangle_mesh.instantiate();
|
||||
triangle_mesh->create(faces);
|
||||
|
||||
return triangle_mesh;
|
||||
|
|
@ -1031,7 +1032,7 @@ void AnimatedSprite3D::_draw() {
|
|||
}
|
||||
|
||||
void AnimatedSprite3D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (!frames.is_valid()) {
|
||||
if (frames.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1086,7 +1087,7 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &p_property) const {
|
|||
void AnimatedSprite3D::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_READY: {
|
||||
if (!Engine::get_singleton()->is_editor_hint() && !frames.is_null() && frames->has_animation(autoplay)) {
|
||||
if (!Engine::get_singleton()->is_editor_hint() && frames.is_valid() && frames->has_animation(autoplay)) {
|
||||
play(autoplay);
|
||||
}
|
||||
} break;
|
||||
|
|
@ -1324,7 +1325,7 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool
|
|||
name = animation;
|
||||
}
|
||||
|
||||
ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name));
|
||||
ERR_FAIL_COND_MSG(frames.is_null(), vformat("There is no animation with name '%s'.", name));
|
||||
ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name));
|
||||
|
||||
if (frames->get_frame_count(name) == 0) {
|
||||
|
|
@ -1402,7 +1403,7 @@ void AnimatedSprite3D::set_animation(const StringName &p_name) {
|
|||
|
||||
emit_signal(SceneStringName(animation_changed));
|
||||
|
||||
if (frames == nullptr) {
|
||||
if (frames.is_null()) {
|
||||
animation = StringName();
|
||||
stop();
|
||||
ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name));
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ public:
|
|||
|
||||
virtual AABB get_aabb() const override;
|
||||
|
||||
Ref<TriangleMesh> generate_triangle_mesh() const;
|
||||
virtual Ref<TriangleMesh> generate_triangle_mesh() const override;
|
||||
|
||||
SpriteBase3D();
|
||||
~SpriteBase3D();
|
||||
|
|
@ -231,7 +231,7 @@ class AnimatedSprite3D : public SpriteBase3D {
|
|||
String autoplay;
|
||||
|
||||
bool playing = false;
|
||||
StringName animation = "default";
|
||||
StringName animation = SceneStringName(default_);
|
||||
int frame = 0;
|
||||
float speed_scale = 1.0;
|
||||
float custom_speed_scale = 1.0;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@
|
|||
|
||||
#include "velocity_tracker_3d.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
|
||||
void VelocityTracker3D::set_track_physics_step(bool p_track_physics_step) {
|
||||
physics_step = p_track_physics_step;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
#ifndef VELOCITY_TRACKER_3D_H
|
||||
#define VELOCITY_TRACKER_3D_H
|
||||
|
||||
#include "scene/3d/node_3d.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class VelocityTracker3D : public RefCounted {
|
||||
struct PositionHistory {
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@
|
|||
|
||||
#include "scene/3d/visual_instance_3d.h"
|
||||
|
||||
class World3D;
|
||||
class Camera3D;
|
||||
class VisibleOnScreenNotifier3D : public VisualInstance3D {
|
||||
GDCLASS(VisibleOnScreenNotifier3D, VisualInstance3D);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,39 @@ 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);
|
||||
}
|
||||
} 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: {
|
||||
|
|
@ -382,14 +441,48 @@ AABB GeometryInstance3D::get_custom_aabb() const {
|
|||
return custom_aabb;
|
||||
}
|
||||
|
||||
void GeometryInstance3D::set_lightmap_texel_scale(float p_scale) {
|
||||
lightmap_texel_scale = p_scale;
|
||||
}
|
||||
|
||||
float GeometryInstance3D::get_lightmap_texel_scale() const {
|
||||
return lightmap_texel_scale;
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void GeometryInstance3D::set_lightmap_scale(LightmapScale p_scale) {
|
||||
ERR_FAIL_INDEX(p_scale, LIGHTMAP_SCALE_MAX);
|
||||
lightmap_scale = p_scale;
|
||||
switch (p_scale) {
|
||||
case GeometryInstance3D::LIGHTMAP_SCALE_1X:
|
||||
lightmap_texel_scale = 1.0f;
|
||||
break;
|
||||
case GeometryInstance3D::LIGHTMAP_SCALE_2X:
|
||||
lightmap_texel_scale = 2.0f;
|
||||
break;
|
||||
case GeometryInstance3D::LIGHTMAP_SCALE_4X:
|
||||
lightmap_texel_scale = 4.0f;
|
||||
break;
|
||||
case GeometryInstance3D::LIGHTMAP_SCALE_8X:
|
||||
lightmap_texel_scale = 8.0f;
|
||||
break;
|
||||
case GeometryInstance3D::LIGHTMAP_SCALE_MAX:
|
||||
break; // Can't happen, but silences warning.
|
||||
}
|
||||
}
|
||||
|
||||
GeometryInstance3D::LightmapScale GeometryInstance3D::get_lightmap_scale() const {
|
||||
return lightmap_scale;
|
||||
// Return closest approximation.
|
||||
if (lightmap_texel_scale < 1.5f) {
|
||||
return GeometryInstance3D::LIGHTMAP_SCALE_1X;
|
||||
} else if (lightmap_texel_scale < 3.0f) {
|
||||
return GeometryInstance3D::LIGHTMAP_SCALE_2X;
|
||||
} else if (lightmap_texel_scale < 6.0f) {
|
||||
return GeometryInstance3D::LIGHTMAP_SCALE_4X;
|
||||
}
|
||||
|
||||
return GeometryInstance3D::LIGHTMAP_SCALE_8X;
|
||||
}
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
void GeometryInstance3D::set_gi_mode(GIMode p_mode) {
|
||||
switch (p_mode) {
|
||||
|
|
@ -424,8 +517,12 @@ bool GeometryInstance3D::is_ignoring_occlusion_culling() {
|
|||
return ignore_occlusion_culling;
|
||||
}
|
||||
|
||||
Ref<TriangleMesh> GeometryInstance3D::generate_triangle_mesh() const {
|
||||
return Ref<TriangleMesh>();
|
||||
}
|
||||
|
||||
PackedStringArray GeometryInstance3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
|
||||
|
||||
if (!Math::is_zero_approx(visibility_range_end) && visibility_range_end <= visibility_range_begin) {
|
||||
warnings.push_back(RTR("The GeometryInstance3D visibility range's End distance is set to a non-zero value, but is lower than the Begin distance.\nThis means the GeometryInstance3D will never be visible.\nTo resolve this, set the End distance to 0 or to a value greater than the Begin distance."));
|
||||
|
|
@ -493,8 +590,13 @@ void GeometryInstance3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_extra_cull_margin", "margin"), &GeometryInstance3D::set_extra_cull_margin);
|
||||
ClassDB::bind_method(D_METHOD("get_extra_cull_margin"), &GeometryInstance3D::get_extra_cull_margin);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_lightmap_texel_scale", "scale"), &GeometryInstance3D::set_lightmap_texel_scale);
|
||||
ClassDB::bind_method(D_METHOD("get_lightmap_texel_scale"), &GeometryInstance3D::get_lightmap_texel_scale);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ClassDB::bind_method(D_METHOD("set_lightmap_scale", "scale"), &GeometryInstance3D::set_lightmap_scale);
|
||||
ClassDB::bind_method(D_METHOD("get_lightmap_scale"), &GeometryInstance3D::get_lightmap_scale);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_gi_mode", "mode"), &GeometryInstance3D::set_gi_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_gi_mode"), &GeometryInstance3D::get_gi_mode);
|
||||
|
|
@ -519,7 +621,10 @@ void GeometryInstance3D::_bind_methods() {
|
|||
|
||||
ADD_GROUP("Global Illumination", "gi_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_mode", PROPERTY_HINT_ENUM, "Disabled,Static,Dynamic"), "set_gi_mode", "get_gi_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_lightmap_scale", PROPERTY_HINT_ENUM, String::utf8("1×,2×,4×,8×")), "set_lightmap_scale", "get_lightmap_scale");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gi_lightmap_texel_scale", PROPERTY_HINT_RANGE, "0.01,10,0.0001,or_greater"), "set_lightmap_texel_scale", "get_lightmap_texel_scale");
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_lightmap_scale", PROPERTY_HINT_ENUM, String::utf8("1×,2×,4×,8×"), PROPERTY_USAGE_NONE), "set_lightmap_scale", "get_lightmap_scale");
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
ADD_GROUP("Visibility Range", "visibility_range_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_begin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_visibility_range_begin", "get_visibility_range_begin");
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue