initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

14
scene/3d/SCsub Normal file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.scene_sources, "*.cpp")
# Chain load SCsubs
if not env["disable_physics_3d"]:
SConscript("physics/SCsub")
if not env["disable_navigation_3d"]:
SConscript("navigation/SCsub")
if not env["disable_xr"]:
SConscript("xr/SCsub")

View File

@@ -0,0 +1,227 @@
/**************************************************************************/
/* aim_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 "aim_modifier_3d.h"
#include "scene/3d/look_at_modifier_3d.h"
bool AimModifier3D::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("settings/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, settings.size(), false);
if (what == "forward_axis") {
set_forward_axis(which, static_cast<BoneAxis>((int)p_value));
} else if (what == "use_euler") {
set_use_euler(which, p_value);
} else if (what == "primary_rotation_axis") {
set_primary_rotation_axis(which, static_cast<Vector3::Axis>((int)p_value));
} else if (what == "use_secondary_rotation") {
set_use_secondary_rotation(which, p_value);
} else {
return false;
}
}
return true;
}
bool AimModifier3D::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("settings/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, settings.size(), false);
if (what == "forward_axis") {
r_ret = (int)get_forward_axis(which);
} else if (what == "use_euler") {
r_ret = is_using_euler(which);
} else if (what == "primary_rotation_axis") {
r_ret = (int)get_primary_rotation_axis(which);
} else if (what == "use_secondary_rotation") {
r_ret = is_using_secondary_rotation(which);
} else {
return false;
}
}
return true;
}
void AimModifier3D::_get_property_list(List<PropertyInfo> *p_list) const {
BoneConstraint3D::get_property_list(p_list);
for (int i = 0; i < settings.size(); i++) {
String path = "settings/" + itos(i) + "/";
int rotation_usage = is_using_euler(i) ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_NONE;
p_list->push_back(PropertyInfo(Variant::INT, path + "forward_axis", PROPERTY_HINT_ENUM, "+X,-X,+Y,-Y,+Z,-Z"));
p_list->push_back(PropertyInfo(Variant::BOOL, path + "use_euler"));
p_list->push_back(PropertyInfo(Variant::INT, path + "primary_rotation_axis", PROPERTY_HINT_ENUM, "X,Y,Z", rotation_usage));
p_list->push_back(PropertyInfo(Variant::BOOL, path + "use_secondary_rotation", PROPERTY_HINT_NONE, "", rotation_usage));
}
}
PackedStringArray AimModifier3D::get_configuration_warnings() const {
PackedStringArray warnings = BoneConstraint3D::get_configuration_warnings();
for (int i = 0; i < settings.size(); i++) {
if (is_using_euler(i) && get_axis_from_bone_axis(get_forward_axis(i)) == get_primary_rotation_axis(i)) {
warnings.push_back(vformat(RTR("Forward axis and primary rotation axis must not be parallel in setting %s."), itos(i)));
}
}
return warnings;
}
void AimModifier3D::_validate_setting(int p_index) {
settings.write[p_index] = memnew(AimModifier3DSetting);
}
void AimModifier3D::set_forward_axis(int p_index, BoneAxis p_axis) {
ERR_FAIL_INDEX(p_index, settings.size());
AimModifier3DSetting *setting = static_cast<AimModifier3DSetting *>(settings[p_index]);
setting->forward_axis = p_axis;
update_configuration_warnings();
}
SkeletonModifier3D::BoneAxis AimModifier3D::get_forward_axis(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), BONE_AXIS_PLUS_Y);
AimModifier3DSetting *setting = static_cast<AimModifier3DSetting *>(settings[p_index]);
return setting->forward_axis;
}
void AimModifier3D::set_use_euler(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
AimModifier3DSetting *setting = static_cast<AimModifier3DSetting *>(settings[p_index]);
setting->use_euler = p_enabled;
notify_property_list_changed();
update_configuration_warnings();
}
bool AimModifier3D::is_using_euler(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
AimModifier3DSetting *setting = static_cast<AimModifier3DSetting *>(settings[p_index]);
return setting->use_euler;
}
void AimModifier3D::set_primary_rotation_axis(int p_index, Vector3::Axis p_axis) {
ERR_FAIL_INDEX(p_index, settings.size());
AimModifier3DSetting *setting = static_cast<AimModifier3DSetting *>(settings[p_index]);
setting->primary_rotation_axis = p_axis;
update_configuration_warnings();
}
Vector3::Axis AimModifier3D::get_primary_rotation_axis(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), Vector3::AXIS_X);
AimModifier3DSetting *setting = static_cast<AimModifier3DSetting *>(settings[p_index]);
return setting->primary_rotation_axis;
}
void AimModifier3D::set_use_secondary_rotation(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
AimModifier3DSetting *setting = static_cast<AimModifier3DSetting *>(settings[p_index]);
setting->use_secondary_rotation = p_enabled;
}
bool AimModifier3D::is_using_secondary_rotation(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
AimModifier3DSetting *setting = static_cast<AimModifier3DSetting *>(settings[p_index]);
return setting->use_secondary_rotation;
}
void AimModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_forward_axis", "index", "axis"), &AimModifier3D::set_forward_axis);
ClassDB::bind_method(D_METHOD("get_forward_axis", "index"), &AimModifier3D::get_forward_axis);
ClassDB::bind_method(D_METHOD("set_use_euler", "index", "enabled"), &AimModifier3D::set_use_euler);
ClassDB::bind_method(D_METHOD("is_using_euler", "index"), &AimModifier3D::is_using_euler);
ClassDB::bind_method(D_METHOD("set_primary_rotation_axis", "index", "axis"), &AimModifier3D::set_primary_rotation_axis);
ClassDB::bind_method(D_METHOD("get_primary_rotation_axis", "index"), &AimModifier3D::get_primary_rotation_axis);
ClassDB::bind_method(D_METHOD("set_use_secondary_rotation", "index", "enabled"), &AimModifier3D::set_use_secondary_rotation);
ClassDB::bind_method(D_METHOD("is_using_secondary_rotation", "index"), &AimModifier3D::is_using_secondary_rotation);
ADD_ARRAY_COUNT("Settings", "setting_count", "set_setting_count", "get_setting_count", "settings/");
}
void AimModifier3D::_process_constraint(int p_index, Skeleton3D *p_skeleton, int p_apply_bone, int p_reference_bone, float p_amount) {
if (p_apply_bone == p_reference_bone) {
ERR_PRINT_ONCE_ED(vformat("In setting %s, the reference bone must not be same with the apply bone.", itos(p_index)));
return;
}
AimModifier3DSetting *setting = static_cast<AimModifier3DSetting *>(settings[p_index]);
// Prepare forward_vector and rest.
Vector3 reference_origin = p_skeleton->get_bone_global_pose(p_reference_bone).origin;
Transform3D src_bone_rest = p_skeleton->get_bone_rest(p_apply_bone);
Transform3D bone_rest_space;
int parent_bone = p_skeleton->get_bone_parent(p_apply_bone);
if (parent_bone < 0) {
bone_rest_space.translate_local(src_bone_rest.origin);
} else {
bone_rest_space = p_skeleton->get_bone_global_pose(parent_bone);
bone_rest_space.translate_local(src_bone_rest.origin);
}
Vector3 forward_vector = bone_rest_space.basis.get_rotation_quaternion().xform_inv(reference_origin - bone_rest_space.origin);
if (forward_vector.is_zero_approx()) {
return;
}
forward_vector.normalize();
// Calculate look at rotation.
Quaternion destination;
if (setting->use_euler) {
Vector3 current_vector = LookAtModifier3D::get_basis_vector_from_bone_axis(src_bone_rest.basis, setting->forward_axis).normalized();
Vector2 src_vec2 = LookAtModifier3D::get_projection_vector(src_bone_rest.basis.xform_inv(forward_vector), setting->primary_rotation_axis).normalized();
Vector2 dst_vec2 = LookAtModifier3D::get_projection_vector(src_bone_rest.basis.xform_inv(current_vector), setting->primary_rotation_axis).normalized();
real_t calculated_angle = src_vec2.angle_to(dst_vec2);
Transform3D primary_result = src_bone_rest.rotated_local(get_vector_from_axis(setting->primary_rotation_axis), calculated_angle);
Transform3D current_result = primary_result;
if (setting->use_secondary_rotation) {
Vector3::Axis secondary_rotation_axis = LookAtModifier3D::get_secondary_rotation_axis(setting->forward_axis, setting->primary_rotation_axis);
current_vector = LookAtModifier3D::get_basis_vector_from_bone_axis(primary_result.basis, setting->forward_axis).normalized();
src_vec2 = LookAtModifier3D::get_projection_vector(primary_result.basis.xform_inv(forward_vector), secondary_rotation_axis).normalized();
dst_vec2 = LookAtModifier3D::get_projection_vector(primary_result.basis.xform_inv(current_vector), secondary_rotation_axis).normalized();
calculated_angle = src_vec2.angle_to(dst_vec2);
current_result = primary_result.rotated_local(get_vector_from_axis(secondary_rotation_axis), calculated_angle);
}
destination = current_result.basis.get_rotation_quaternion();
} else {
Vector3 current_vector = LookAtModifier3D::get_basis_vector_from_bone_axis(src_bone_rest.basis, setting->forward_axis).normalized();
destination = Quaternion(current_vector, forward_vector) * src_bone_rest.basis.get_rotation_quaternion();
}
p_skeleton->set_bone_pose_rotation(p_apply_bone, p_skeleton->get_bone_pose_rotation(p_apply_bone).slerp(destination, p_amount));
}
AimModifier3D::~AimModifier3D() {
clear_settings();
}

View File

@@ -0,0 +1,68 @@
/**************************************************************************/
/* aim_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/bone_constraint_3d.h"
class AimModifier3D : public BoneConstraint3D {
GDCLASS(AimModifier3D, BoneConstraint3D);
public:
struct AimModifier3DSetting : public BoneConstraint3DSetting {
BoneAxis forward_axis = BONE_AXIS_PLUS_Y;
bool use_euler = false;
Vector3::Axis primary_rotation_axis = Vector3::AXIS_X;
bool use_secondary_rotation = true;
};
protected:
bool _get(const StringName &p_path, Variant &r_ret) const;
bool _set(const StringName &p_path, const Variant &p_value);
virtual PackedStringArray get_configuration_warnings() const override;
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
virtual void _process_constraint(int p_index, Skeleton3D *p_skeleton, int p_apply_bone, int p_reference_bone, float p_amount) override;
virtual void _validate_setting(int p_index) override;
public:
void set_forward_axis(int p_index, BoneAxis p_axis);
BoneAxis get_forward_axis(int p_index) const;
void set_use_euler(int p_index, bool p_enabled);
bool is_using_euler(int p_index) const;
void set_primary_rotation_axis(int p_index, Vector3::Axis p_axis);
Vector3::Axis get_primary_rotation_axis(int p_index) const;
void set_use_secondary_rotation(int p_index, bool p_enabled);
bool is_using_secondary_rotation(int p_index) const;
~AimModifier3D();
};

View File

@@ -0,0 +1,193 @@
/**************************************************************************/
/* audio_listener_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 "audio_listener_3d.h"
#include "scene/main/viewport.h"
void AudioListener3D::_update_audio_listener_state() {
}
void AudioListener3D::_request_listener_update() {
_update_listener();
}
bool AudioListener3D::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "current") {
if (p_value.operator bool()) {
make_current();
} else {
clear_current();
}
} else {
return false;
}
return true;
}
bool AudioListener3D::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "current") {
if (is_part_of_edited_scene()) {
r_ret = current;
} else {
r_ret = is_current();
}
} else {
return false;
}
return true;
}
void AudioListener3D::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("current")));
}
void AudioListener3D::_update_listener() {
if (is_inside_tree() && is_current()) {
get_viewport()->_listener_transform_3d_changed_notify();
}
}
void AudioListener3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_WORLD: {
bool first_listener = get_viewport()->_audio_listener_3d_add(this);
if (!is_part_of_edited_scene() && (current || first_listener)) {
make_current();
}
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
_request_listener_update();
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
velocity_tracker->update_position(get_global_transform().origin);
}
} break;
case NOTIFICATION_EXIT_WORLD: {
if (!is_part_of_edited_scene()) {
if (is_current()) {
clear_current();
current = true; //keep it true
} else {
current = false;
}
}
get_viewport()->_audio_listener_3d_remove(this);
} break;
}
}
Transform3D AudioListener3D::get_listener_transform() const {
return get_global_transform().orthonormalized();
}
void AudioListener3D::make_current() {
current = true;
if (!is_inside_tree()) {
return;
}
get_viewport()->_audio_listener_3d_set(this);
}
void AudioListener3D::clear_current() {
current = false;
if (!is_inside_tree()) {
return;
}
if (get_viewport()->get_audio_listener_3d() == this) {
get_viewport()->_audio_listener_3d_set(nullptr);
get_viewport()->_audio_listener_3d_make_next_current(this);
}
}
bool AudioListener3D::is_current() const {
if (is_inside_tree() && !is_part_of_edited_scene()) {
return get_viewport()->get_audio_listener_3d() == this;
} else {
return current;
}
}
void AudioListener3D::set_doppler_tracking(DopplerTracking p_tracking) {
if (doppler_tracking == p_tracking) {
return;
}
doppler_tracking = p_tracking;
if (p_tracking != DOPPLER_TRACKING_DISABLED) {
velocity_tracker->set_track_physics_step(doppler_tracking == DOPPLER_TRACKING_PHYSICS_STEP);
if (is_inside_tree()) {
velocity_tracker->reset(get_global_transform().origin);
}
}
}
AudioListener3D::DopplerTracking AudioListener3D::get_doppler_tracking() const {
return doppler_tracking;
}
void AudioListener3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("make_current"), &AudioListener3D::make_current);
ClassDB::bind_method(D_METHOD("clear_current"), &AudioListener3D::clear_current);
ClassDB::bind_method(D_METHOD("is_current"), &AudioListener3D::is_current);
ClassDB::bind_method(D_METHOD("get_listener_transform"), &AudioListener3D::get_listener_transform);
ClassDB::bind_method(D_METHOD("set_doppler_tracking", "mode"), &AudioListener3D::set_doppler_tracking);
ClassDB::bind_method(D_METHOD("get_doppler_tracking"), &AudioListener3D::get_doppler_tracking);
ADD_PROPERTY(PropertyInfo(Variant::INT, "doppler_tracking", PROPERTY_HINT_ENUM, "Disabled,Idle,Physics"), "set_doppler_tracking", "get_doppler_tracking");
BIND_ENUM_CONSTANT(DOPPLER_TRACKING_DISABLED);
BIND_ENUM_CONSTANT(DOPPLER_TRACKING_IDLE_STEP);
BIND_ENUM_CONSTANT(DOPPLER_TRACKING_PHYSICS_STEP);
}
Vector3 AudioListener3D::get_doppler_tracked_velocity() const {
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
return velocity_tracker->get_tracked_linear_velocity();
} else {
return Vector3();
}
}
AudioListener3D::AudioListener3D() {
set_notify_transform(true);
velocity_tracker.instantiate();
}
AudioListener3D::~AudioListener3D() {
}

View File

@@ -0,0 +1,84 @@
/**************************************************************************/
/* audio_listener_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
#include "scene/3d/velocity_tracker_3d.h"
class AudioListener3D : public Node3D {
GDCLASS(AudioListener3D, Node3D);
public:
enum DopplerTracking {
DOPPLER_TRACKING_DISABLED,
DOPPLER_TRACKING_IDLE_STEP,
DOPPLER_TRACKING_PHYSICS_STEP,
};
void make_current();
void clear_current();
bool is_current() const;
virtual Transform3D get_listener_transform() const;
void set_doppler_tracking(DopplerTracking p_tracking);
DopplerTracking get_doppler_tracking() const;
Vector3 get_doppler_tracked_velocity() const;
AudioListener3D();
~AudioListener3D();
private:
bool force_change = false;
bool current = false;
RID scenario_id;
friend class Viewport;
void _update_audio_listener_state();
DopplerTracking doppler_tracking = DOPPLER_TRACKING_DISABLED;
Ref<VelocityTracker3D> velocity_tracker;
protected:
void _update_listener();
virtual void _request_listener_update();
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
void _notification(int p_what);
static void _bind_methods();
};
VARIANT_ENUM_CAST(AudioListener3D::DopplerTracking);

View File

@@ -0,0 +1,41 @@
/**************************************************************************/
/* audio_stream_player_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
bool AudioStreamPlayer3D::_is_autoplay_enabled_bind_compat_86907() {
return is_autoplay_enabled();
}
void AudioStreamPlayer3D::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer3D::_is_autoplay_enabled_bind_compat_86907);
}
#endif // DISABLE_DEPRECATED

View File

@@ -0,0 +1,928 @@
/**************************************************************************/
/* audio_stream_player_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 "audio_stream_player_3d.h"
#include "audio_stream_player_3d.compat.inc"
#include "core/config/project_settings.h"
#include "scene/3d/audio_listener_3d.h"
#include "scene/3d/camera_3d.h"
#include "scene/3d/velocity_tracker_3d.h"
#include "scene/audio/audio_stream_player_internal.h"
#include "scene/main/viewport.h"
#include "servers/audio/audio_stream.h"
#ifndef PHYSICS_3D_DISABLED
#include "scene/3d/physics/area_3d.h"
#endif // PHYSICS_3D_DISABLED
// Based on "A Novel Multichannel Panning Method for Standard and Arbitrary Loudspeaker Configurations" by Ramy Sadek and Chris Kyriakakis (2004)
// Speaker-Placement Correction Amplitude Panning (SPCAP)
class Spcap {
private:
struct Speaker {
Vector3 direction;
real_t effective_number_of_speakers = 0; // precalculated
mutable real_t squared_gain = 0; // temporary
};
Vector<Speaker> speakers;
public:
Spcap(unsigned int speaker_count, const Vector3 *speaker_directions) {
speakers.resize(speaker_count);
Speaker *w = speakers.ptrw();
for (unsigned int speaker_num = 0; speaker_num < speaker_count; speaker_num++) {
w[speaker_num].direction = speaker_directions[speaker_num];
w[speaker_num].squared_gain = 0.0;
w[speaker_num].effective_number_of_speakers = 0.0;
}
for (unsigned int speaker_num = 0; speaker_num < speaker_count; speaker_num++) {
for (unsigned int other_speaker_num = 0; other_speaker_num < speaker_count; other_speaker_num++) {
w[speaker_num].effective_number_of_speakers += 0.5 * (1.0 + w[speaker_num].direction.dot(w[other_speaker_num].direction));
}
}
}
unsigned int get_speaker_count() const {
return (unsigned int)speakers.size();
}
Vector3 get_speaker_direction(unsigned int index) const {
return speakers.ptr()[index].direction;
}
void calculate(const Vector3 &source_direction, real_t tightness, unsigned int volume_count, real_t *volumes) const {
const Speaker *r = speakers.ptr();
real_t sum_squared_gains = 0.0;
for (unsigned int speaker_num = 0; speaker_num < (unsigned int)speakers.size(); speaker_num++) {
real_t initial_gain = 0.5 * std::pow(1.0 + r[speaker_num].direction.dot(source_direction), tightness) / r[speaker_num].effective_number_of_speakers;
r[speaker_num].squared_gain = initial_gain * initial_gain;
sum_squared_gains += r[speaker_num].squared_gain;
}
for (unsigned int speaker_num = 0; speaker_num < MIN(volume_count, (unsigned int)speakers.size()); speaker_num++) {
volumes[speaker_num] = std::sqrt(r[speaker_num].squared_gain / sum_squared_gains);
}
}
};
//TODO: hardcoded main speaker directions for 2, 3.1, 5.1 and 7.1 setups - these are simplified and could also be made configurable
static const Vector3 speaker_directions[7] = {
Vector3(-1.0, 0.0, -1.0).normalized(), // front-left
Vector3(1.0, 0.0, -1.0).normalized(), // front-right
Vector3(0.0, 0.0, -1.0).normalized(), // center
Vector3(-1.0, 0.0, 1.0).normalized(), // rear-left
Vector3(1.0, 0.0, 1.0).normalized(), // rear-right
Vector3(-1.0, 0.0, 0.0).normalized(), // side-left
Vector3(1.0, 0.0, 0.0).normalized(), // side-right
};
void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tightness, Vector<AudioFrame> &output) {
unsigned int speaker_count = 0; // only main speakers (no LFE)
switch (AudioServer::get_singleton()->get_speaker_mode()) {
case AudioServer::SPEAKER_MODE_STEREO:
speaker_count = 2;
break;
case AudioServer::SPEAKER_SURROUND_31:
speaker_count = 3;
break;
case AudioServer::SPEAKER_SURROUND_51:
speaker_count = 5;
break;
case AudioServer::SPEAKER_SURROUND_71:
speaker_count = 7;
break;
}
Spcap spcap(speaker_count, speaker_directions); //TODO: should only be created/recreated once the speaker mode / speaker positions changes
real_t volumes[7];
spcap.calculate(source_dir, tightness, speaker_count, volumes);
switch (AudioServer::get_singleton()->get_speaker_mode()) {
case AudioServer::SPEAKER_SURROUND_71:
output.write[3].left = volumes[5]; // side-left
output.write[3].right = volumes[6]; // side-right
[[fallthrough]];
case AudioServer::SPEAKER_SURROUND_51:
output.write[2].left = volumes[3]; // rear-left
output.write[2].right = volumes[4]; // rear-right
[[fallthrough]];
case AudioServer::SPEAKER_SURROUND_31:
output.write[1].right = 1.0; // LFE - always full power
output.write[1].left = volumes[2]; // center
[[fallthrough]];
case AudioServer::SPEAKER_MODE_STEREO:
output.write[0].right = volumes[1]; // front-right
output.write[0].left = volumes[0]; // front-left
break;
}
}
// Set the volume to cosine of half horizontal the angle from the source to the left/right speaker direction ignoring elevation.
// Then scale `cosx` so that greatest ratio of the speaker volumes is `1-panning_strength`.
// See https://github.com/godotengine/godot/issues/103989 for evidence that this is the most standard implementation.
AudioFrame AudioStreamPlayer3D::_calc_output_vol_stereo(const Vector3 &source_dir, real_t panning_strength) {
double flatrad = sqrt(source_dir.x * source_dir.x + source_dir.z * source_dir.z);
double g = CLAMP((1.0 - panning_strength) * (1.0 - panning_strength), 0.0, 1.0);
double f = (1.0 - g) / (1.0 + g);
double cosx = CLAMP(source_dir.x / (flatrad == 0.0 ? 1.0 : flatrad), -1.0, 1.0);
double fcosx = cosx * f;
return AudioFrame(sqrt((-fcosx + 1.0) / 2.0), sqrt((fcosx + 1.0) / 2.0));
}
#ifndef PHYSICS_3D_DISABLED
void AudioStreamPlayer3D::_calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol) {
reverb_vol.resize(4);
reverb_vol.write[0] = AudioFrame(0, 0);
reverb_vol.write[1] = AudioFrame(0, 0);
reverb_vol.write[2] = AudioFrame(0, 0);
reverb_vol.write[3] = AudioFrame(0, 0);
float uniformity = area->get_reverb_uniformity();
float area_send = area->get_reverb_amount();
if (uniformity > 0.0) {
float distance = listener_area_pos.length();
float attenuation = Math::db_to_linear(_get_attenuation_db(distance));
// Determine the fraction of sound that would come from each speaker if they were all driven uniformly.
float center_val[3] = { 0.5f, 0.25f, 0.16666f };
int channel_count = AudioServer::get_singleton()->get_channel_count();
AudioFrame center_frame(center_val[channel_count - 1], center_val[channel_count - 1]);
if (attenuation < 1.0) {
//pan the uniform sound
Vector3 rev_pos = listener_area_pos;
rev_pos.y = 0;
rev_pos.normalize();
// Stereo pair.
float c = rev_pos.x * 0.5 + 0.5;
reverb_vol.write[0].left = 1.0 - c;
reverb_vol.write[0].right = c;
if (channel_count >= 3) {
// Center pair + Side pair
float xl = Vector3(-1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5;
float xr = Vector3(1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5;
reverb_vol.write[1].left = xl;
reverb_vol.write[1].right = xr;
reverb_vol.write[2].left = 1.0 - xr;
reverb_vol.write[2].right = 1.0 - xl;
}
if (channel_count >= 4) {
// Rear pair
// FIXME: Not sure what math should be done here
reverb_vol.write[3].left = 1.0 - c;
reverb_vol.write[3].right = c;
}
for (int i = 0; i < channel_count; i++) {
reverb_vol.write[i] = reverb_vol[i].lerp(center_frame, attenuation);
}
} else {
for (int i = 0; i < channel_count; i++) {
reverb_vol.write[i] = center_frame;
}
}
for (int i = 0; i < channel_count; i++) {
reverb_vol.write[i] = direct_path_vol[i].lerp(reverb_vol[i] * attenuation, uniformity);
reverb_vol.write[i] *= area_send;
}
} else {
for (int i = 0; i < 4; i++) {
reverb_vol.write[i] = direct_path_vol[i] * area_send;
}
}
}
#endif // PHYSICS_3D_DISABLED
float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const {
float att = 0;
switch (attenuation_model) {
case ATTENUATION_INVERSE_DISTANCE: {
att = Math::linear_to_db(1.0 / ((p_distance / unit_size) + CMP_EPSILON));
} break;
case ATTENUATION_INVERSE_SQUARE_DISTANCE: {
float d = (p_distance / unit_size);
d *= d;
att = Math::linear_to_db(1.0 / (d + CMP_EPSILON));
} break;
case ATTENUATION_LOGARITHMIC: {
att = -20 * Math::log(p_distance / unit_size + CMP_EPSILON);
} break;
case ATTENUATION_DISABLED:
break;
default: {
ERR_PRINT("Unknown attenuation type");
break;
}
}
att += internal->volume_db;
if (att > max_db) {
att = max_db;
}
return att;
}
void AudioStreamPlayer3D::_notification(int p_what) {
internal->notification(p_what);
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
velocity_tracker->reset(get_global_transform().origin);
AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this);
} break;
case NOTIFICATION_EXIT_TREE: {
AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this);
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
velocity_tracker->update_position(get_global_transform().origin);
}
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
// Update anything related to position first, if possible of course.
Vector<AudioFrame> volume_vector;
if (setplay.get() > 0 || (internal->active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) || force_update_panning) {
force_update_panning = false;
volume_vector = _update_panning();
}
if (setplayback.is_valid() && setplay.get() >= 0) {
internal->active.set();
HashMap<StringName, Vector<AudioFrame>> bus_map;
bus_map[_get_actual_bus()] = volume_vector;
AudioServer::get_singleton()->start_playback_stream(setplayback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
setplayback.unref();
setplay.set(-1);
}
if (!internal->stream_playbacks.is_empty() && internal->active.is_set()) {
internal->process();
}
internal->ensure_playback_limit();
} break;
}
}
#ifndef PHYSICS_3D_DISABLED
// Interacts with PhysicsServer3D, so can only be called during _physics_process
Area3D *AudioStreamPlayer3D::_get_overriding_area() {
//check if any area is diverting sound into a bus
Ref<World3D> world_3d = get_world_3d();
ERR_FAIL_COND_V(world_3d.is_null(), nullptr);
Vector3 global_pos = get_global_transform().origin;
PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space());
PhysicsDirectSpaceState3D::ShapeResult sr[MAX_INTERSECT_AREAS];
PhysicsDirectSpaceState3D::PointParameters point_params;
point_params.position = global_pos;
point_params.collision_mask = area_mask;
point_params.collide_with_bodies = false;
point_params.collide_with_areas = true;
int areas = space_state->intersect_point(point_params, sr, MAX_INTERSECT_AREAS);
for (int i = 0; i < areas; i++) {
if (!sr[i].collider) {
continue;
}
Area3D *tarea = Object::cast_to<Area3D>(sr[i].collider);
if (!tarea) {
continue;
}
if (!tarea->is_overriding_audio_bus() && !tarea->is_using_reverb_bus()) {
continue;
}
return tarea;
}
return nullptr;
}
#endif // PHYSICS_3D_DISABLED
// Interacts with PhysicsServer3D, so can only be called during _physics_process.
StringName AudioStreamPlayer3D::_get_actual_bus() {
#ifndef PHYSICS_3D_DISABLED
Area3D *overriding_area = _get_overriding_area();
if (overriding_area && overriding_area->is_overriding_audio_bus() && !overriding_area->is_using_reverb_bus()) {
return overriding_area->get_audio_bus_name();
}
#endif // PHYSICS_3D_DISABLED
return internal->bus;
}
// Interacts with PhysicsServer3D, so can only be called during _physics_process.
Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() {
Vector<AudioFrame> output_volume_vector;
output_volume_vector.resize(4);
for (AudioFrame &frame : output_volume_vector) {
frame = AudioFrame(0, 0);
}
if (!internal->active.is_set() || internal->stream.is_null()) {
return output_volume_vector;
}
Vector3 linear_velocity;
//compute linear velocity for doppler
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
linear_velocity = velocity_tracker->get_tracked_linear_velocity();
}
Vector3 global_pos = get_global_transform().origin;
Ref<World3D> world_3d = get_world_3d();
ERR_FAIL_COND_V(world_3d.is_null(), output_volume_vector);
HashSet<Camera3D *> cameras = world_3d->get_cameras();
cameras.insert(get_viewport()->get_camera_3d());
#ifndef PHYSICS_3D_DISABLED
PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space());
#endif // PHYSICS_3D_DISABLED
for (Camera3D *camera : cameras) {
if (!camera) {
continue;
}
Viewport *vp = camera->get_viewport();
if (!vp) {
continue;
}
if (!vp->is_audio_listener_3d()) {
continue;
}
Node3D *listener_node = camera;
AudioListener3D *listener = vp->get_audio_listener_3d();
if (listener) {
listener_node = listener;
}
Vector3 local_pos = listener_node->get_global_transform().orthonormalized().affine_inverse().xform(global_pos);
float dist = local_pos.length();
#ifndef PHYSICS_3D_DISABLED
Vector3 area_sound_pos;
Vector3 listener_area_pos;
Area3D *area = _get_overriding_area();
if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) {
area_sound_pos = space_state->get_closest_point_to_object_volume(area->get_rid(), listener_node->get_global_transform().origin);
listener_area_pos = listener_node->get_global_transform().affine_inverse().xform(area_sound_pos);
}
#endif // PHYSICS_3D_DISABLED
if (max_distance > 0) {
float total_max = max_distance;
#ifndef PHYSICS_3D_DISABLED
if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) {
total_max = MAX(total_max, listener_area_pos.length());
}
#endif // PHYSICS_3D_DISABLED
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) {
multiplier *= MAX(0, 1.0 - (dist / max_distance));
}
float db_att = (1.0 - MIN(1.0, multiplier)) * attenuation_filter_db;
if (emission_angle_enabled) {
Vector3 listenertopos = global_pos - listener_node->get_global_transform().origin;
float c = listenertopos.normalized().dot(get_global_transform().basis.get_column(2).normalized()); //it's z negative
float angle = Math::rad_to_deg(Math::acos(c));
if (angle > emission_angle) {
db_att -= -emission_angle_filter_attenuation_db;
}
}
linear_attenuation = Math::db_to_linear(db_att);
for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
AudioServer::get_singleton()->set_playback_highshelf_params(playback, linear_attenuation, attenuation_filter_cutoff_hz);
}
if (AudioServer::get_singleton()->get_speaker_mode() == AudioServer::SPEAKER_MODE_STEREO) {
output_volume_vector.write[0] = _calc_output_vol_stereo(local_pos, cached_global_panning_strength * panning_strength);
output_volume_vector.write[1] = AudioFrame(0, 0);
output_volume_vector.write[2] = AudioFrame(0, 0);
output_volume_vector.write[3] = AudioFrame(0, 0);
} else {
// Bake in a constant factor here to allow the project setting defaults for 2d and 3d to be normalized to 1.0.
float tightness = cached_global_panning_strength * 2.0f;
tightness *= panning_strength;
_calc_output_vol(local_pos.normalized(), tightness, output_volume_vector);
}
for (unsigned int k = 0; k < 4; k++) {
output_volume_vector.write[k] = multiplier * output_volume_vector[k];
}
HashMap<StringName, Vector<AudioFrame>> bus_volumes;
#ifndef PHYSICS_3D_DISABLED
if (area) {
if (area->is_overriding_audio_bus()) {
//override audio bus
bus_volumes[area->get_audio_bus_name()] = output_volume_vector;
}
if (area->is_using_reverb_bus()) {
StringName reverb_bus_name = area->get_reverb_bus_name();
Vector<AudioFrame> reverb_vol;
_calc_reverb_vol(area, listener_area_pos, output_volume_vector, reverb_vol);
bus_volumes[reverb_bus_name] = reverb_vol;
}
} else
#endif // PHYSICS_3D_DISABLED
{
bus_volumes[internal->bus] = output_volume_vector;
}
for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
AudioServer::get_singleton()->set_playback_bus_volumes_linear(playback, bus_volumes);
}
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
Vector3 listener_velocity;
if (listener) {
listener_velocity = listener->get_doppler_tracked_velocity();
} else {
listener_velocity = camera->get_doppler_tracked_velocity();
}
Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity);
if (local_velocity != Vector3()) {
float approaching = local_pos.normalized().dot(local_velocity.normalized());
float velocity = local_velocity.length();
float speed_of_sound = 343.0;
float doppler_pitch_scale = internal->pitch_scale * speed_of_sound / (speed_of_sound + velocity * approaching);
doppler_pitch_scale = CLAMP(doppler_pitch_scale, (1 / 8.0), 8.0); //avoid crazy stuff
actual_pitch_scale = doppler_pitch_scale;
} else {
actual_pitch_scale = internal->pitch_scale;
}
} else {
actual_pitch_scale = internal->pitch_scale;
}
for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
AudioServer::get_singleton()->set_playback_pitch_scale(playback, actual_pitch_scale);
if (playback->get_is_sample()) {
Ref<AudioSamplePlayback> sample_playback = playback->get_sample_playback();
if (sample_playback.is_valid()) {
AudioServer::get_singleton()->update_sample_playback_pitch_scale(sample_playback, actual_pitch_scale);
}
}
}
}
return output_volume_vector;
}
void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) {
internal->set_stream(p_stream);
}
Ref<AudioStream> AudioStreamPlayer3D::get_stream() const {
return internal->stream;
}
void AudioStreamPlayer3D::set_volume_db(float p_volume) {
ERR_FAIL_COND_MSG(Math::is_nan(p_volume), "Volume can't be set to NaN.");
internal->volume_db = p_volume;
}
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();
}
float AudioStreamPlayer3D::get_unit_size() const {
return unit_size;
}
void AudioStreamPlayer3D::set_max_db(float p_boost) {
max_db = p_boost;
}
float AudioStreamPlayer3D::get_max_db() const {
return max_db;
}
void AudioStreamPlayer3D::set_pitch_scale(float p_pitch_scale) {
internal->set_pitch_scale(p_pitch_scale);
}
float AudioStreamPlayer3D::get_pitch_scale() const {
return internal->pitch_scale;
}
void AudioStreamPlayer3D::play(float p_from_pos) {
Ref<AudioStreamPlayback> stream_playback = internal->play_basic();
if (stream_playback.is_null()) {
return;
}
setplayback = stream_playback;
setplay.set(p_from_pos);
// Sample handling.
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();
AudioServer::get_singleton()->start_sample_playback(sample_playback);
}
}
void AudioStreamPlayer3D::seek(float p_seconds) {
internal->seek(p_seconds);
}
void AudioStreamPlayer3D::stop() {
setplay.set(-1);
internal->stop_basic();
}
bool AudioStreamPlayer3D::is_playing() const {
if (setplay.get() >= 0) {
return true; // play() has been called this frame, but no playback exists just yet.
}
return internal->is_playing();
}
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();
}
void AudioStreamPlayer3D::set_bus(const StringName &p_bus) {
internal->bus = p_bus; // This will be pushed to the audio server during the next physics timestep, which is fast enough.
}
StringName AudioStreamPlayer3D::get_bus() const {
return internal->get_bus();
}
void AudioStreamPlayer3D::set_autoplay(bool p_enable) {
internal->autoplay = p_enable;
}
bool AudioStreamPlayer3D::is_autoplay_enabled() const {
return internal->autoplay;
}
void AudioStreamPlayer3D::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
void AudioStreamPlayer3D::_validate_property(PropertyInfo &p_property) const {
internal->validate_property(p_property);
}
void AudioStreamPlayer3D::set_max_distance(float p_metres) {
ERR_FAIL_COND(p_metres < 0.0);
max_distance = p_metres;
update_gizmos();
}
float AudioStreamPlayer3D::get_max_distance() const {
return max_distance;
}
void AudioStreamPlayer3D::set_area_mask(uint32_t p_mask) {
area_mask = p_mask;
}
uint32_t AudioStreamPlayer3D::get_area_mask() const {
return area_mask;
}
void AudioStreamPlayer3D::set_emission_angle_enabled(bool p_enable) {
emission_angle_enabled = p_enable;
update_gizmos();
}
bool AudioStreamPlayer3D::is_emission_angle_enabled() const {
return emission_angle_enabled;
}
void AudioStreamPlayer3D::set_emission_angle(float p_angle) {
ERR_FAIL_COND(p_angle < 0 || p_angle > 90);
emission_angle = p_angle;
update_gizmos();
}
float AudioStreamPlayer3D::get_emission_angle() const {
return emission_angle;
}
void AudioStreamPlayer3D::set_emission_angle_filter_attenuation_db(float p_angle_attenuation_db) {
emission_angle_filter_attenuation_db = p_angle_attenuation_db;
}
float AudioStreamPlayer3D::get_emission_angle_filter_attenuation_db() const {
return emission_angle_filter_attenuation_db;
}
void AudioStreamPlayer3D::set_attenuation_filter_cutoff_hz(float p_hz) {
attenuation_filter_cutoff_hz = p_hz;
}
float AudioStreamPlayer3D::get_attenuation_filter_cutoff_hz() const {
return attenuation_filter_cutoff_hz;
}
void AudioStreamPlayer3D::set_attenuation_filter_db(float p_db) {
attenuation_filter_db = p_db;
}
float AudioStreamPlayer3D::get_attenuation_filter_db() const {
return attenuation_filter_db;
}
void AudioStreamPlayer3D::set_attenuation_model(AttenuationModel p_model) {
ERR_FAIL_INDEX((int)p_model, 4);
attenuation_model = p_model;
update_gizmos();
}
AudioStreamPlayer3D::AttenuationModel AudioStreamPlayer3D::get_attenuation_model() const {
return attenuation_model;
}
void AudioStreamPlayer3D::set_doppler_tracking(DopplerTracking p_tracking) {
if (doppler_tracking == p_tracking) {
return;
}
doppler_tracking = p_tracking;
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
set_notify_transform(true);
velocity_tracker->set_track_physics_step(doppler_tracking == DOPPLER_TRACKING_PHYSICS_STEP);
if (is_inside_tree()) {
velocity_tracker->reset(get_global_transform().origin);
}
} else {
set_notify_transform(false);
}
}
AudioStreamPlayer3D::DopplerTracking AudioStreamPlayer3D::get_doppler_tracking() const {
return doppler_tracking;
}
void AudioStreamPlayer3D::set_stream_paused(bool p_pause) {
internal->set_stream_paused(p_pause);
}
bool AudioStreamPlayer3D::get_stream_paused() const {
return internal->get_stream_paused();
}
bool AudioStreamPlayer3D::has_stream_playback() {
return internal->has_stream_playback();
}
Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() {
return internal->get_stream_playback();
}
void AudioStreamPlayer3D::set_max_polyphony(int p_max_polyphony) {
internal->set_max_polyphony(p_max_polyphony);
}
int AudioStreamPlayer3D::get_max_polyphony() const {
return internal->max_polyphony;
}
void AudioStreamPlayer3D::set_panning_strength(float p_panning_strength) {
ERR_FAIL_COND_MSG(p_panning_strength < 0, "Panning strength must be a positive number.");
panning_strength = p_panning_strength;
}
float AudioStreamPlayer3D::get_panning_strength() const {
return panning_strength;
}
AudioServer::PlaybackType AudioStreamPlayer3D::get_playback_type() const {
return internal->get_playback_type();
}
void AudioStreamPlayer3D::set_playback_type(AudioServer::PlaybackType p_playback_type) {
internal->set_playback_type(p_playback_type);
}
bool AudioStreamPlayer3D::_set(const StringName &p_name, const Variant &p_value) {
return internal->set(p_name, p_value);
}
bool AudioStreamPlayer3D::_get(const StringName &p_name, Variant &r_ret) const {
return internal->get(p_name, r_ret);
}
void AudioStreamPlayer3D::_get_property_list(List<PropertyInfo> *p_list) const {
internal->get_property_list(p_list);
}
void AudioStreamPlayer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer3D::set_stream);
ClassDB::bind_method(D_METHOD("get_stream"), &AudioStreamPlayer3D::get_stream);
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);
ClassDB::bind_method(D_METHOD("set_max_db", "max_db"), &AudioStreamPlayer3D::set_max_db);
ClassDB::bind_method(D_METHOD("get_max_db"), &AudioStreamPlayer3D::get_max_db);
ClassDB::bind_method(D_METHOD("set_pitch_scale", "pitch_scale"), &AudioStreamPlayer3D::set_pitch_scale);
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioStreamPlayer3D::get_pitch_scale);
ClassDB::bind_method(D_METHOD("play", "from_position"), &AudioStreamPlayer3D::play, DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("seek", "to_position"), &AudioStreamPlayer3D::seek);
ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayer3D::stop);
ClassDB::bind_method(D_METHOD("is_playing"), &AudioStreamPlayer3D::is_playing);
ClassDB::bind_method(D_METHOD("get_playback_position"), &AudioStreamPlayer3D::get_playback_position);
ClassDB::bind_method(D_METHOD("set_bus", "bus"), &AudioStreamPlayer3D::set_bus);
ClassDB::bind_method(D_METHOD("get_bus"), &AudioStreamPlayer3D::get_bus);
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("set_max_distance", "meters"), &AudioStreamPlayer3D::set_max_distance);
ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer3D::get_max_distance);
ClassDB::bind_method(D_METHOD("set_area_mask", "mask"), &AudioStreamPlayer3D::set_area_mask);
ClassDB::bind_method(D_METHOD("get_area_mask"), &AudioStreamPlayer3D::get_area_mask);
ClassDB::bind_method(D_METHOD("set_emission_angle", "degrees"), &AudioStreamPlayer3D::set_emission_angle);
ClassDB::bind_method(D_METHOD("get_emission_angle"), &AudioStreamPlayer3D::get_emission_angle);
ClassDB::bind_method(D_METHOD("set_emission_angle_enabled", "enabled"), &AudioStreamPlayer3D::set_emission_angle_enabled);
ClassDB::bind_method(D_METHOD("is_emission_angle_enabled"), &AudioStreamPlayer3D::is_emission_angle_enabled);
ClassDB::bind_method(D_METHOD("set_emission_angle_filter_attenuation_db", "db"), &AudioStreamPlayer3D::set_emission_angle_filter_attenuation_db);
ClassDB::bind_method(D_METHOD("get_emission_angle_filter_attenuation_db"), &AudioStreamPlayer3D::get_emission_angle_filter_attenuation_db);
ClassDB::bind_method(D_METHOD("set_attenuation_filter_cutoff_hz", "degrees"), &AudioStreamPlayer3D::set_attenuation_filter_cutoff_hz);
ClassDB::bind_method(D_METHOD("get_attenuation_filter_cutoff_hz"), &AudioStreamPlayer3D::get_attenuation_filter_cutoff_hz);
ClassDB::bind_method(D_METHOD("set_attenuation_filter_db", "db"), &AudioStreamPlayer3D::set_attenuation_filter_db);
ClassDB::bind_method(D_METHOD("get_attenuation_filter_db"), &AudioStreamPlayer3D::get_attenuation_filter_db);
ClassDB::bind_method(D_METHOD("set_attenuation_model", "model"), &AudioStreamPlayer3D::set_attenuation_model);
ClassDB::bind_method(D_METHOD("get_attenuation_model"), &AudioStreamPlayer3D::get_attenuation_model);
ClassDB::bind_method(D_METHOD("set_doppler_tracking", "mode"), &AudioStreamPlayer3D::set_doppler_tracking);
ClassDB::bind_method(D_METHOD("get_doppler_tracking"), &AudioStreamPlayer3D::get_doppler_tracking);
ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer3D::set_stream_paused);
ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer3D::get_stream_paused);
ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer3D::set_max_polyphony);
ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer3D::get_max_polyphony);
ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer3D::set_panning_strength);
ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer3D::get_panning_strength);
ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer3D::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback);
ClassDB::bind_method(D_METHOD("set_playback_type", "playback_type"), &AudioStreamPlayer3D::set_playback_type);
ClassDB::bind_method(D_METHOD("get_playback_type"), &AudioStreamPlayer3D::get_playback_type);
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_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");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "panning_strength", PROPERTY_HINT_RANGE, "0,3,0.01,or_greater"), "set_panning_strength", "get_panning_strength");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus");
ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_area_mask", "get_area_mask");
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_type", PROPERTY_HINT_ENUM, "Default,Stream,Sample"), "set_playback_type", "get_playback_type");
ADD_GROUP("Emission Angle", "emission_angle");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emission_angle_enabled"), "set_emission_angle_enabled", "is_emission_angle_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_angle_degrees", PROPERTY_HINT_RANGE, "0.1,90,0.1,degrees"), "set_emission_angle", "get_emission_angle");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_angle_filter_attenuation_db", PROPERTY_HINT_RANGE, "-80,0,0.1,suffix:dB"), "set_emission_angle_filter_attenuation_db", "get_emission_angle_filter_attenuation_db");
ADD_GROUP("Attenuation Filter", "attenuation_filter_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation_filter_cutoff_hz", PROPERTY_HINT_RANGE, "1,20500,1,suffix:Hz"), "set_attenuation_filter_cutoff_hz", "get_attenuation_filter_cutoff_hz");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation_filter_db", PROPERTY_HINT_RANGE, "-80,0,0.1,suffix:dB"), "set_attenuation_filter_db", "get_attenuation_filter_db");
ADD_GROUP("Doppler", "doppler_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "doppler_tracking", PROPERTY_HINT_ENUM, "Disabled,Idle,Physics"), "set_doppler_tracking", "get_doppler_tracking");
BIND_ENUM_CONSTANT(ATTENUATION_INVERSE_DISTANCE);
BIND_ENUM_CONSTANT(ATTENUATION_INVERSE_SQUARE_DISTANCE);
BIND_ENUM_CONSTANT(ATTENUATION_LOGARITHMIC);
BIND_ENUM_CONSTANT(ATTENUATION_DISABLED);
BIND_ENUM_CONSTANT(DOPPLER_TRACKING_DISABLED);
BIND_ENUM_CONSTANT(DOPPLER_TRACKING_IDLE_STEP);
BIND_ENUM_CONSTANT(DOPPLER_TRACKING_PHYSICS_STEP);
ADD_SIGNAL(MethodInfo("finished"));
}
AudioStreamPlayer3D::AudioStreamPlayer3D() {
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_CACHED(float, "audio/general/3d_panning_strength");
}
AudioStreamPlayer3D::~AudioStreamPlayer3D() {
memdelete(internal);
}

View File

@@ -0,0 +1,216 @@
/**************************************************************************/
/* audio_stream_player_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
#include "servers/audio_server.h"
#ifndef PHYSICS_3D_DISABLED
class Area3D;
#endif // PHYSICS_3D_DISABLED
struct AudioFrame;
class AudioStream;
class AudioStreamPlayback;
class AudioStreamPlayerInternal;
class VelocityTracker3D;
class AudioStreamPlayer3D : public Node3D {
GDCLASS(AudioStreamPlayer3D, Node3D);
public:
enum AttenuationModel {
ATTENUATION_INVERSE_DISTANCE,
ATTENUATION_INVERSE_SQUARE_DISTANCE,
ATTENUATION_LOGARITHMIC,
ATTENUATION_DISABLED,
};
enum DopplerTracking {
DOPPLER_TRACKING_DISABLED,
DOPPLER_TRACKING_IDLE_STEP,
DOPPLER_TRACKING_PHYSICS_STEP
};
private:
enum {
MAX_OUTPUTS = 8,
MAX_INTERSECT_AREAS = 32
};
AudioStreamPlayerInternal *internal = nullptr;
SafeNumeric<float> setplay{ -1.0 };
Ref<AudioStreamPlayback> setplayback;
AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE;
float unit_size = 10.0;
float max_db = 3.0;
// Internally used to take doppler tracking into account.
float actual_pitch_scale = 1.0;
uint64_t last_mix_count = -1;
bool force_update_panning = false;
static void _calc_output_vol(const Vector3 &source_dir, real_t tightness, Vector<AudioFrame> &output);
static AudioFrame _calc_output_vol_stereo(const Vector3 &source_dir, real_t panning_strength);
#ifndef PHYSICS_3D_DISABLED
void _calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol);
#endif // PHYSICS_3D_DISABLED
static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer3D *>(self)->force_update_panning = true; }
void _set_playing(bool p_enable);
bool _is_active() const;
StringName _get_actual_bus();
#ifndef PHYSICS_3D_DISABLED
Area3D *_get_overriding_area();
#endif // PHYSICS_3D_DISABLED
Vector<AudioFrame> _update_panning();
uint32_t area_mask = 1;
AudioServer::PlaybackType playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT;
bool emission_angle_enabled = false;
float emission_angle = 45.0;
float emission_angle_filter_attenuation_db = -12.0;
float attenuation_filter_cutoff_hz = 5000.0;
float attenuation_filter_db = -24.0;
float linear_attenuation = 0;
float max_distance = 0.0;
bool was_further_than_max_distance_last_frame = false;
Ref<VelocityTracker3D> velocity_tracker;
DopplerTracking doppler_tracking = DOPPLER_TRACKING_DISABLED;
float _get_attenuation_db(float p_distance) const;
float panning_strength = 1.0f;
float cached_global_panning_strength = 0.5f;
protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
static void _bind_methods();
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
#ifndef DISABLE_DEPRECATED
bool _is_autoplay_enabled_bind_compat_86907();
static void _bind_compatibility_methods();
#endif // DISABLE_DEPRECATED
public:
void set_stream(Ref<AudioStream> p_stream);
Ref<AudioStream> get_stream() const;
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;
void set_max_db(float p_boost);
float get_max_db() const;
void set_pitch_scale(float p_pitch_scale);
float get_pitch_scale() const;
void play(float p_from_pos = 0.0);
void seek(float p_seconds);
void stop();
bool is_playing() const;
float get_playback_position();
void set_bus(const StringName &p_bus);
StringName get_bus() const;
void set_max_polyphony(int p_max_polyphony);
int get_max_polyphony() const;
void set_autoplay(bool p_enable);
bool is_autoplay_enabled() const;
void set_max_distance(float p_metres);
float get_max_distance() const;
void set_area_mask(uint32_t p_mask);
uint32_t get_area_mask() const;
void set_emission_angle_enabled(bool p_enable);
bool is_emission_angle_enabled() const;
void set_emission_angle(float p_angle);
float get_emission_angle() const;
void set_emission_angle_filter_attenuation_db(float p_angle_attenuation_db);
float get_emission_angle_filter_attenuation_db() const;
void set_attenuation_filter_cutoff_hz(float p_hz);
float get_attenuation_filter_cutoff_hz() const;
void set_attenuation_filter_db(float p_db);
float get_attenuation_filter_db() const;
void set_attenuation_model(AttenuationModel p_model);
AttenuationModel get_attenuation_model() const;
void set_doppler_tracking(DopplerTracking p_tracking);
DopplerTracking get_doppler_tracking() const;
void set_stream_paused(bool p_pause);
bool get_stream_paused() const;
void set_panning_strength(float p_panning_strength);
float get_panning_strength() const;
bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
AudioServer::PlaybackType get_playback_type() const;
void set_playback_type(AudioServer::PlaybackType p_playback_type);
AudioStreamPlayer3D();
~AudioStreamPlayer3D();
};
VARIANT_ENUM_CAST(AudioStreamPlayer3D::AttenuationModel)
VARIANT_ENUM_CAST(AudioStreamPlayer3D::DopplerTracking)

View File

@@ -0,0 +1,43 @@
/**************************************************************************/
/* bone_attachment_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
#include "bone_attachment_3d.h"
void BoneAttachment3D::_on_bone_pose_update_bind_compat_90575(int p_bone_index) {
return on_skeleton_update();
}
void BoneAttachment3D::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("on_bone_pose_update", "bone_index"), &BoneAttachment3D::_on_bone_pose_update_bind_compat_90575);
}
#endif // DISABLE_DEPRECATED

View File

@@ -0,0 +1,381 @@
/**************************************************************************/
/* bone_attachment_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 "bone_attachment_3d.h"
#include "bone_attachment_3d.compat.inc"
void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const {
if (Engine::get_singleton()->is_editor_hint() && p_property.name == "bone_name") {
// 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()) {
parent = ObjectDB::get_instance<Skeleton3D>(external_skeleton_node_cache);
}
} else {
parent = Object::cast_to<Skeleton3D>(get_parent());
}
if (parent) {
p_property.hint = PROPERTY_HINT_ENUM;
p_property.hint_string = parent->get_concatenated_bone_names();
} else {
p_property.hint = PROPERTY_HINT_NONE;
p_property.hint_string = "";
}
}
if (p_property.name == "external_skeleton" && !use_external_skeleton) {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
PackedStringArray BoneAttachment3D::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();
if (use_external_skeleton) {
if (external_skeleton_node_cache.is_null()) {
warnings.push_back(RTR("External Skeleton3D node not set! Please set a path to an external Skeleton3D node."));
}
} else {
Skeleton3D *parent = Object::cast_to<Skeleton3D>(get_parent());
if (!parent) {
warnings.push_back(RTR("Parent node is not a Skeleton3D node! Please use an external Skeleton3D if you intend to use the BoneAttachment3D without it being a child of a Skeleton3D node."));
}
}
if (bone_idx == -1) {
warnings.push_back(RTR("BoneAttachment3D node is not bound to any bones! Please select a bone to attach this node."));
}
return warnings;
}
void BoneAttachment3D::_update_external_skeleton_cache() {
external_skeleton_node_cache = ObjectID();
if (has_node(external_skeleton_node)) {
Node *node = get_node(external_skeleton_node);
ERR_FAIL_NULL_MSG(node, "Cannot update external skeleton cache: Node cannot be found!");
// Make sure it's a Skeleton3D.
Skeleton3D *sk = Object::cast_to<Skeleton3D>(node);
ERR_FAIL_NULL_MSG(sk, "Cannot update external skeleton cache: Skeleton3D Nodepath does not point to a Skeleton3D node!");
external_skeleton_node_cache = node->get_instance_id();
} else {
if (external_skeleton_node.is_empty()) {
BoneAttachment3D *parent_attachment = Object::cast_to<BoneAttachment3D>(get_parent());
if (parent_attachment) {
parent_attachment->_update_external_skeleton_cache();
if (parent_attachment->has_node(parent_attachment->external_skeleton_node)) {
Node *node = parent_attachment->get_node(parent_attachment->external_skeleton_node);
ERR_FAIL_NULL_MSG(node, "Cannot update external skeleton cache: Parent's Skeleton3D node cannot be found!");
// Make sure it's a Skeleton3D.
Skeleton3D *sk = Object::cast_to<Skeleton3D>(node);
ERR_FAIL_NULL_MSG(sk, "Cannot update external skeleton cache: Parent Skeleton3D Nodepath does not point to a Skeleton3D node!");
external_skeleton_node_cache = node->get_instance_id();
external_skeleton_node = get_path_to(node);
}
}
}
}
}
void BoneAttachment3D::_check_bind() {
Skeleton3D *sk = get_skeleton();
if (sk && !bound) {
if (bone_idx <= -1) {
bone_idx = sk->find_bone(bone_name);
}
if (bone_idx != -1) {
sk->connect(SceneStringName(skeleton_updated), callable_mp(this, &BoneAttachment3D::on_skeleton_update));
bound = true;
on_skeleton_update();
}
}
}
Skeleton3D *BoneAttachment3D::get_skeleton() {
if (use_external_skeleton) {
if (external_skeleton_node_cache.is_valid()) {
return ObjectDB::get_instance<Skeleton3D>(external_skeleton_node_cache);
} else {
_update_external_skeleton_cache();
if (external_skeleton_node_cache.is_valid()) {
return ObjectDB::get_instance<Skeleton3D>(external_skeleton_node_cache);
}
}
} else {
return Object::cast_to<Skeleton3D>(get_parent());
}
return nullptr;
}
void BoneAttachment3D::_check_unbind() {
if (bound) {
Skeleton3D *sk = get_skeleton();
if (sk) {
sk->disconnect(SceneStringName(skeleton_updated), callable_mp(this, &BoneAttachment3D::on_skeleton_update));
}
bound = false;
}
}
void BoneAttachment3D::_transform_changed() {
if (!is_inside_tree()) {
return;
}
if (override_pose && !overriding) {
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!");
Transform3D our_trans = get_transform();
if (use_external_skeleton) {
our_trans = sk->get_global_transform().affine_inverse() * get_global_transform();
}
overriding = true;
sk->set_bone_global_pose(bone_idx, our_trans);
sk->force_update_all_dirty_bones();
}
overriding = false;
}
void BoneAttachment3D::set_bone_name(const String &p_name) {
bone_name = p_name;
Skeleton3D *sk = get_skeleton();
if (sk) {
set_bone_idx(sk->find_bone(bone_name));
}
}
String BoneAttachment3D::get_bone_name() const {
return bone_name;
}
void BoneAttachment3D::set_bone_idx(const int &p_idx) {
if (is_inside_tree()) {
_check_unbind();
}
bone_idx = p_idx;
Skeleton3D *sk = get_skeleton();
if (sk) {
if (bone_idx <= -1 || bone_idx >= sk->get_bone_count()) {
WARN_PRINT("Bone index " + itos(bone_idx) + " out of range! Cannot connect BoneAttachment to node!");
bone_idx = -1;
} else {
bone_name = sk->get_bone_name(bone_idx);
}
}
if (is_inside_tree()) {
_check_bind();
} else {
on_skeleton_update();
}
notify_property_list_changed();
}
int BoneAttachment3D::get_bone_idx() const {
return bone_idx;
}
void BoneAttachment3D::set_override_pose(bool p_override_pose) {
if (override_pose == p_override_pose) {
return;
}
override_pose = p_override_pose;
set_notify_transform(override_pose);
set_process_internal(override_pose);
if (!override_pose && bone_idx >= 0) {
Skeleton3D *sk = get_skeleton();
if (sk) {
sk->reset_bone_pose(bone_idx);
}
}
notify_property_list_changed();
}
bool BoneAttachment3D::get_override_pose() const {
return override_pose;
}
void BoneAttachment3D::set_use_external_skeleton(bool p_use_external_skeleton) {
use_external_skeleton = p_use_external_skeleton;
if (use_external_skeleton) {
_check_unbind();
_update_external_skeleton_cache();
_check_bind();
_transform_changed();
}
notify_property_list_changed();
}
bool BoneAttachment3D::get_use_external_skeleton() const {
return use_external_skeleton;
}
void BoneAttachment3D::set_external_skeleton(NodePath p_external_skeleton) {
external_skeleton_node = p_external_skeleton;
_update_external_skeleton_cache();
notify_property_list_changed();
}
NodePath BoneAttachment3D::get_external_skeleton() const {
return external_skeleton_node;
}
void BoneAttachment3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (use_external_skeleton) {
_update_external_skeleton_cache();
}
_check_bind();
} break;
case NOTIFICATION_EXIT_TREE: {
_check_unbind();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
_transform_changed();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (_override_dirty) {
_override_dirty = false;
}
} break;
}
}
void BoneAttachment3D::on_skeleton_update() {
if (updating) {
return;
}
updating = true;
if (bone_idx >= 0) {
Skeleton3D *sk = get_skeleton();
if (sk) {
if (!override_pose) {
if (use_external_skeleton) {
if (sk->is_inside_tree()) {
set_global_transform(sk->get_global_transform() * sk->get_bone_global_pose(bone_idx));
// Else, do nothing, the transform will be set when the skeleton enters the tree:
// Skeleton3D::_notification(NOTIFICATION_ENTER_TREE) -> calls Skeleton3D::_notification(NOTIFICATION_UPDATE_SKELETON)
// -> emits skeleton_updated signal -> connected to BoneAttachment3D::on_skeleton_update()
}
} else {
set_transform(sk->get_bone_global_pose(bone_idx));
}
} else {
if (!_override_dirty) {
_transform_changed();
_override_dirty = true;
}
}
}
}
updating = false;
}
#ifdef TOOLS_ENABLED
void BoneAttachment3D::notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Dictionary p_rename_map) {
const Skeleton3D *parent = nullptr;
if (use_external_skeleton) {
if (external_skeleton_node_cache.is_valid()) {
parent = ObjectDB::get_instance<Skeleton3D>(external_skeleton_node_cache);
}
} else {
parent = Object::cast_to<Skeleton3D>(get_parent());
}
if (parent && parent == p_skeleton) {
StringName bn = p_rename_map[bone_name];
if (bn) {
set_bone_name(bn);
}
}
}
void BoneAttachment3D::notify_rebind_required() {
// Ensures bindings are properly updated after a scene reload.
_check_unbind();
if (use_external_skeleton) {
_update_external_skeleton_cache();
}
bone_idx = -1;
_check_bind();
}
#endif // TOOLS_ENABLED
BoneAttachment3D::BoneAttachment3D() {
set_physics_interpolation_mode(PHYSICS_INTERPOLATION_MODE_OFF);
}
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);
ClassDB::bind_method(D_METHOD("set_bone_idx", "bone_idx"), &BoneAttachment3D::set_bone_idx);
ClassDB::bind_method(D_METHOD("get_bone_idx"), &BoneAttachment3D::get_bone_idx);
ClassDB::bind_method(D_METHOD("on_skeleton_update"), &BoneAttachment3D::on_skeleton_update);
ClassDB::bind_method(D_METHOD("set_override_pose", "override_pose"), &BoneAttachment3D::set_override_pose);
ClassDB::bind_method(D_METHOD("get_override_pose"), &BoneAttachment3D::get_override_pose);
ClassDB::bind_method(D_METHOD("set_use_external_skeleton", "use_external_skeleton"), &BoneAttachment3D::set_use_external_skeleton);
ClassDB::bind_method(D_METHOD("get_use_external_skeleton"), &BoneAttachment3D::get_use_external_skeleton);
ClassDB::bind_method(D_METHOD("set_external_skeleton", "external_skeleton"), &BoneAttachment3D::set_external_skeleton);
ClassDB::bind_method(D_METHOD("get_external_skeleton"), &BoneAttachment3D::get_external_skeleton);
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_idx"), "set_bone_idx", "get_bone_idx");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_pose"), "set_override_pose", "get_override_pose");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_external_skeleton"), "set_use_external_skeleton", "get_use_external_skeleton");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "external_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_external_skeleton", "get_external_skeleton");
}

View File

@@ -0,0 +1,97 @@
/**************************************************************************/
/* bone_attachment_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/skeleton_3d.h"
class BoneAttachment3D : public Node3D {
GDCLASS(BoneAttachment3D, Node3D);
bool bound = false;
String bone_name;
int bone_idx = -1;
bool override_pose = false;
bool _override_dirty = false;
bool overriding = false;
bool use_external_skeleton = false;
NodePath external_skeleton_node;
ObjectID external_skeleton_node_cache;
void _check_bind();
void _check_unbind();
bool updating = false;
void _transform_changed();
void _update_external_skeleton_cache();
protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
virtual void _on_bone_pose_update_bind_compat_90575(int p_bone_index);
static void _bind_compatibility_methods();
#endif
public:
#ifdef TOOLS_ENABLED
virtual void notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Dictionary p_rename_map);
#endif // TOOLS_ENABLED
virtual PackedStringArray get_configuration_warnings() const override;
Skeleton3D *get_skeleton();
void set_bone_name(const String &p_name);
String get_bone_name() const;
void set_bone_idx(const int &p_idx);
int get_bone_idx() const;
void set_override_pose(bool p_override_pose);
bool get_override_pose() const;
void set_use_external_skeleton(bool p_use_external_skeleton);
bool get_use_external_skeleton() const;
void set_external_skeleton(NodePath p_external_skeleton);
NodePath get_external_skeleton() const;
virtual void on_skeleton_update();
#ifdef TOOLS_ENABLED
virtual void notify_rebind_required();
#endif
BoneAttachment3D();
};

View File

@@ -0,0 +1,314 @@
/**************************************************************************/
/* bone_constraint_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 "bone_constraint_3d.h"
bool BoneConstraint3D::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("settings/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, settings.size(), false);
if (what == "amount") {
set_amount(which, p_value);
} else if (what == "apply_bone_name") {
set_apply_bone_name(which, p_value);
} else if (what == "reference_bone_name") {
set_reference_bone_name(which, p_value);
} else if (what == "apply_bone") {
set_apply_bone(which, p_value);
} else if (what == "reference_bone") {
set_reference_bone(which, p_value);
} else {
return false;
}
}
return true;
}
bool BoneConstraint3D::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("settings/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, settings.size(), false);
if (what == "amount") {
r_ret = get_amount(which);
} else if (what == "apply_bone_name") {
r_ret = get_apply_bone_name(which);
} else if (what == "reference_bone_name") {
r_ret = get_reference_bone_name(which);
} else if (what == "apply_bone") {
r_ret = get_apply_bone(which);
} else if (what == "reference_bone") {
r_ret = get_reference_bone(which);
} else {
return false;
}
}
return true;
}
void BoneConstraint3D::get_property_list(List<PropertyInfo> *p_list) const {
String enum_hint;
Skeleton3D *skeleton = get_skeleton();
if (skeleton) {
enum_hint = skeleton->get_concatenated_bone_names();
}
for (int i = 0; i < settings.size(); i++) {
String path = "settings/" + itos(i) + "/";
p_list->push_back(PropertyInfo(Variant::FLOAT, path + "amount", PROPERTY_HINT_RANGE, "0,1,0.001"));
p_list->push_back(PropertyInfo(Variant::STRING, path + "apply_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint));
p_list->push_back(PropertyInfo(Variant::INT, path + "apply_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
p_list->push_back(PropertyInfo(Variant::STRING, path + "reference_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint));
p_list->push_back(PropertyInfo(Variant::INT, path + "reference_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
}
void BoneConstraint3D::set_setting_count(int p_count) {
ERR_FAIL_COND(p_count < 0);
int delta = p_count - settings.size();
if (delta < 0) {
for (int i = delta; i < 0; i++) {
memdelete(settings[settings.size() + i]);
}
}
settings.resize(p_count);
delta++;
if (delta > 1) {
for (int i = 1; i < delta; i++) {
_validate_setting(p_count - i);
}
}
notify_property_list_changed();
}
int BoneConstraint3D::get_setting_count() const {
return settings.size();
}
void BoneConstraint3D::_validate_setting(int p_index) {
settings.write[p_index] = memnew(BoneConstraint3DSetting);
}
void BoneConstraint3D::clear_settings() {
set_setting_count(0);
}
void BoneConstraint3D::set_amount(int p_index, float p_amount) {
ERR_FAIL_INDEX(p_index, settings.size());
settings[p_index]->amount = p_amount;
}
float BoneConstraint3D::get_amount(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0.0);
return settings[p_index]->amount;
}
void BoneConstraint3D::set_apply_bone_name(int p_index, const String &p_bone_name) {
ERR_FAIL_INDEX(p_index, settings.size());
settings[p_index]->apply_bone_name = p_bone_name;
Skeleton3D *sk = get_skeleton();
if (sk) {
set_apply_bone(p_index, sk->find_bone(settings[p_index]->apply_bone_name));
}
}
String BoneConstraint3D::get_apply_bone_name(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), String());
return settings[p_index]->apply_bone_name;
}
void BoneConstraint3D::set_apply_bone(int p_index, int p_bone) {
ERR_FAIL_INDEX(p_index, settings.size());
settings[p_index]->apply_bone = p_bone;
Skeleton3D *sk = get_skeleton();
if (sk) {
if (settings[p_index]->apply_bone <= -1 || settings[p_index]->apply_bone >= sk->get_bone_count()) {
WARN_PRINT("apply bone index out of range!");
settings[p_index]->apply_bone = -1;
} else {
settings[p_index]->apply_bone_name = sk->get_bone_name(settings[p_index]->apply_bone);
}
}
}
int BoneConstraint3D::get_apply_bone(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), -1);
return settings[p_index]->apply_bone;
}
void BoneConstraint3D::set_reference_bone_name(int p_index, const String &p_bone_name) {
ERR_FAIL_INDEX(p_index, settings.size());
settings[p_index]->reference_bone_name = p_bone_name;
Skeleton3D *sk = get_skeleton();
if (sk) {
set_reference_bone(p_index, sk->find_bone(settings[p_index]->reference_bone_name));
}
}
String BoneConstraint3D::get_reference_bone_name(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), String());
return settings[p_index]->reference_bone_name;
}
void BoneConstraint3D::set_reference_bone(int p_index, int p_bone) {
ERR_FAIL_INDEX(p_index, settings.size());
settings[p_index]->reference_bone = p_bone;
Skeleton3D *sk = get_skeleton();
if (sk) {
if (settings[p_index]->reference_bone <= -1 || settings[p_index]->reference_bone >= sk->get_bone_count()) {
WARN_PRINT("reference bone index out of range!");
settings[p_index]->reference_bone = -1;
} else {
settings[p_index]->reference_bone_name = sk->get_bone_name(settings[p_index]->reference_bone);
}
}
}
int BoneConstraint3D::get_reference_bone(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), -1);
return settings[p_index]->reference_bone;
}
void BoneConstraint3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_amount", "index", "amount"), &BoneConstraint3D::set_amount);
ClassDB::bind_method(D_METHOD("get_amount", "index"), &BoneConstraint3D::get_amount);
ClassDB::bind_method(D_METHOD("set_apply_bone_name", "index", "bone_name"), &BoneConstraint3D::set_apply_bone_name);
ClassDB::bind_method(D_METHOD("get_apply_bone_name", "index"), &BoneConstraint3D::get_apply_bone_name);
ClassDB::bind_method(D_METHOD("set_apply_bone", "index", "bone"), &BoneConstraint3D::set_apply_bone);
ClassDB::bind_method(D_METHOD("get_apply_bone", "index"), &BoneConstraint3D::get_apply_bone);
ClassDB::bind_method(D_METHOD("set_reference_bone_name", "index", "bone_name"), &BoneConstraint3D::set_reference_bone_name);
ClassDB::bind_method(D_METHOD("get_reference_bone_name", "index"), &BoneConstraint3D::get_reference_bone_name);
ClassDB::bind_method(D_METHOD("set_reference_bone", "index", "bone"), &BoneConstraint3D::set_reference_bone);
ClassDB::bind_method(D_METHOD("get_reference_bone", "index"), &BoneConstraint3D::get_reference_bone);
ClassDB::bind_method(D_METHOD("set_setting_count", "count"), &BoneConstraint3D::set_setting_count);
ClassDB::bind_method(D_METHOD("get_setting_count"), &BoneConstraint3D::get_setting_count);
ClassDB::bind_method(D_METHOD("clear_setting"), &BoneConstraint3D::clear_settings);
}
void BoneConstraint3D::_validate_bone_names() {
for (int i = 0; i < settings.size(); i++) {
// Prior bone name.
if (!settings[i]->apply_bone_name.is_empty()) {
set_apply_bone_name(i, settings[i]->apply_bone_name);
} else if (settings[i]->apply_bone != -1) {
set_apply_bone(i, settings[i]->apply_bone);
}
// Prior bone name.
if (!settings[i]->reference_bone_name.is_empty()) {
set_reference_bone_name(i, settings[i]->reference_bone_name);
} else if (settings[i]->reference_bone != -1) {
set_reference_bone(i, settings[i]->reference_bone);
}
}
}
void BoneConstraint3D::_process_modification(double p_delta) {
Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
for (int i = 0; i < settings.size(); i++) {
int apply_bone = settings[i]->apply_bone;
if (apply_bone < 0) {
continue;
}
int reference_bone = settings[i]->reference_bone;
if (reference_bone < 0) {
continue;
}
float amount = settings[i]->amount;
if (amount <= 0) {
continue;
}
_process_constraint(i, skeleton, apply_bone, reference_bone, amount);
}
}
void BoneConstraint3D::_process_constraint(int p_index, Skeleton3D *p_skeleton, int p_apply_bone, int p_reference_bone, float p_amount) {
//
}
double BoneConstraint3D::symmetrize_angle(double p_angle) {
double angle = Math::fposmod(p_angle, Math::TAU);
return angle > Math::PI ? angle - Math::TAU : angle;
}
double BoneConstraint3D::get_roll_angle(const Quaternion &p_rotation, const Vector3 &p_roll_axis) {
// Ensure roll axis is normalized.
Vector3 roll_axis = p_roll_axis.normalized();
// Project the quaternion rotation onto the roll axis.
// This gives us the component of rotation around that axis.
double dot = p_rotation.x * roll_axis.x +
p_rotation.y * roll_axis.y +
p_rotation.z * roll_axis.z;
// Create a quaternion representing just the roll component.
Quaternion roll_component;
roll_component.x = roll_axis.x * dot;
roll_component.y = roll_axis.y * dot;
roll_component.z = roll_axis.z * dot;
roll_component.w = p_rotation.w;
// Normalize this component.
double length = roll_component.length();
if (length > CMP_EPSILON) {
roll_component = roll_component / length;
} else {
return 0.0;
}
// Extract the angle.
double angle = 2.0 * Math::acos(CLAMP(roll_component.w, -1.0, 1.0));
// Determine the sign.
double direction = (roll_component.x * roll_axis.x + roll_component.y * roll_axis.y + roll_component.z * roll_axis.z > 0) ? 1.0 : -1.0;
return symmetrize_angle(angle * direction);
}
BoneConstraint3D::~BoneConstraint3D() {
clear_settings();
}

View File

@@ -0,0 +1,90 @@
/**************************************************************************/
/* bone_constraint_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/skeleton_modifier_3d.h"
class BoneConstraint3D : public SkeletonModifier3D {
GDCLASS(BoneConstraint3D, SkeletonModifier3D);
public:
struct BoneConstraint3DSetting {
float amount = 1.0;
String apply_bone_name;
int apply_bone = -1;
String reference_bone_name;
int reference_bone = -1;
};
protected:
Vector<BoneConstraint3DSetting *> settings;
bool _get(const StringName &p_path, Variant &r_ret) const;
bool _set(const StringName &p_path, const Variant &p_value);
// Define get_property_list() instead of _get_property_list()
// to merge child class properties into parent class array inspector.
void get_property_list(List<PropertyInfo> *p_list) const; // Will be called by child classes.
virtual void _validate_bone_names() override;
static void _bind_methods();
virtual void _process_modification(double p_delta) override;
virtual void _process_constraint(int p_index, Skeleton3D *p_skeleton, int p_apply_bone, int p_reference_bone, float p_amount);
virtual void _validate_setting(int p_index);
public:
void set_amount(int p_index, float p_amount);
float get_amount(int p_index) const;
void set_apply_bone_name(int p_index, const String &p_bone_name);
String get_apply_bone_name(int p_index) const;
void set_apply_bone(int p_index, int p_bone);
int get_apply_bone(int p_index) const;
void set_reference_bone_name(int p_index, const String &p_bone_name);
String get_reference_bone_name(int p_index) const;
void set_reference_bone(int p_index, int p_bone);
int get_reference_bone(int p_index) const;
void set_setting_count(int p_count);
int get_setting_count() const;
void clear_settings();
static double symmetrize_angle(double p_angle); // Helper to make angle 0->TAU become -PI->PI.
static double get_roll_angle(const Quaternion &p_rotation, const Vector3 &p_roll_axis);
~BoneConstraint3D();
};

877
scene/3d/camera_3d.cpp Normal file
View File

@@ -0,0 +1,877 @@
/**************************************************************************/
/* camera_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 "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() {
}
void Camera3D::_request_camera_update() {
_update_camera();
}
void Camera3D::fti_pump_property() {
switch (mode) {
default:
break;
case PROJECTION_PERSPECTIVE: {
fov.pump();
} break;
case PROJECTION_ORTHOGONAL: {
size.pump();
} break;
case PROJECTION_FRUSTUM: {
size.pump();
frustum_offset.pump();
} break;
}
_near.pump();
_far.pump();
Node3D::fti_pump_property();
}
void Camera3D::fti_update_servers_property() {
if (camera.is_valid()) {
float f = Engine::get_singleton()->get_physics_interpolation_fraction();
switch (mode) {
default:
break;
case PROJECTION_PERSPECTIVE: {
// If there have been changes due to interpolation, update the servers.
if (fov.interpolate(f) || _near.interpolate(f) || _far.interpolate(f)) {
RS::get_singleton()->camera_set_perspective(camera, fov.interpolated(), _near.interpolated(), _far.interpolated());
}
} break;
case PROJECTION_ORTHOGONAL: {
if (size.interpolate(f) || _near.interpolate(f) || _far.interpolate(f)) {
RS::get_singleton()->camera_set_orthogonal(camera, size.interpolated(), _near.interpolated(), _far.interpolated());
}
} break;
case PROJECTION_FRUSTUM: {
if (size.interpolate(f) || frustum_offset.interpolate(f) || _near.interpolate(f) || _far.interpolate(f)) {
RS::get_singleton()->camera_set_frustum(camera, size.interpolated(), frustum_offset.interpolated(), _near.interpolated(), _far.interpolated());
}
} break;
}
}
Node3D::fti_update_servers_property();
}
void Camera3D::fti_update_servers_xform() {
if (camera.is_valid()) {
Transform3D tr = _get_adjusted_camera_transform(_get_cached_global_transform_interpolated());
RS::get_singleton()->camera_set_transform(camera, tr);
}
Node3D::fti_update_servers_xform();
}
void Camera3D::_update_camera_mode() {
force_change = true;
switch (mode) {
case PROJECTION_PERSPECTIVE: {
set_perspective(fov, _near, _far);
} break;
case PROJECTION_ORTHOGONAL: {
set_orthogonal(size, _near, _far);
} break;
case PROJECTION_FRUSTUM: {
set_frustum(size, frustum_offset, _near, _far);
} break;
}
fti_notify_node_changed(false);
}
void Camera3D::_validate_property(PropertyInfo &p_property) const {
if (Engine::get_singleton()->is_editor_hint()) {
if (p_property.name == "fov") {
if (mode != PROJECTION_PERSPECTIVE) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
} else if (p_property.name == "size") {
if (mode != PROJECTION_ORTHOGONAL && mode != PROJECTION_FRUSTUM) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
} else if (p_property.name == "frustum_offset") {
if (mode != PROJECTION_FRUSTUM) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
}
if (attributes.is_valid()) {
const CameraAttributesPhysical *physical_attributes = Object::cast_to<CameraAttributesPhysical>(attributes.ptr());
if (physical_attributes) {
if (p_property.name == "near" || p_property.name == "far" || p_property.name == "fov" || p_property.name == "keep_aspect") {
p_property.usage = PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR;
}
}
}
}
void Camera3D::_update_camera() {
if (!is_inside_tree()) {
return;
}
if (!is_physics_interpolated_and_enabled()) {
RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform());
} else {
// Force a refresh next frame.
fti_notify_node_changed();
}
if (is_part_of_edited_scene() || !is_current()) {
return;
}
get_viewport()->_camera_3d_transform_changed_notify();
}
void Camera3D::_physics_interpolated_changed() {
_update_process_mode();
}
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() {
set_process_internal(_desired_process_internal);
set_physics_process_internal(_desired_physics_process_internal);
}
void Camera3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_WORLD: {
// Needs to track the Viewport because it's needed on NOTIFICATION_EXIT_WORLD
// and Spatial will handle it first, including clearing its reference to the Viewport,
// therefore making it impossible to subclasses to access it
viewport = get_viewport();
ERR_FAIL_NULL(viewport);
bool first_camera = viewport->_camera_3d_add(this);
if (current || first_camera) {
viewport->_camera_3d_set(this);
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
viewport->connect(SNAME("size_changed"), callable_mp((Node3D *)this, &Camera3D::update_gizmos));
}
#endif
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
if (is_physics_interpolated_and_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);
}
} break;
case NOTIFICATION_EXIT_WORLD: {
if (!is_part_of_edited_scene()) {
if (is_current()) {
clear_current();
current = true; //keep it true
} else {
current = false;
}
}
if (viewport) {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
viewport->disconnect(SNAME("size_changed"), callable_mp((Node3D *)this, &Camera3D::update_gizmos));
}
#endif
viewport->_camera_3d_remove(this);
viewport = nullptr;
}
} break;
case NOTIFICATION_BECAME_CURRENT: {
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_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()) {
return _get_adjusted_camera_transform(_get_cached_global_transform_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;
switch (mode) {
case PROJECTION_PERSPECTIVE: {
cm.set_perspective(fov, viewport_size.aspect(), p_near, _far, keep_aspect == KEEP_WIDTH);
} break;
case PROJECTION_ORTHOGONAL: {
cm.set_orthogonal(size, viewport_size.aspect(), p_near, _far, keep_aspect == KEEP_WIDTH);
} break;
case PROJECTION_FRUSTUM: {
cm.set_frustum(size, viewport_size.aspect(), frustum_offset, p_near, _far);
} break;
}
return cm;
}
Projection Camera3D::get_camera_projection() const {
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Projection(), "Camera is not inside the scene tree.");
return _get_camera_projection(_near);
}
void Camera3D::set_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far) {
if (!force_change && fov == p_fovy_degrees && p_z_near == _near && p_z_far == _far && mode == PROJECTION_PERSPECTIVE) {
return;
}
fov = p_fovy_degrees;
_near = p_z_near;
_far = p_z_far;
mode = PROJECTION_PERSPECTIVE;
RenderingServer::get_singleton()->camera_set_perspective(camera, fov, _near, _far);
update_gizmos();
force_change = false;
}
void Camera3D::set_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far) {
if (!force_change && size == p_size && p_z_near == _near && p_z_far == _far && mode == PROJECTION_ORTHOGONAL) {
return;
}
size = p_size;
_near = p_z_near;
_far = p_z_far;
mode = PROJECTION_ORTHOGONAL;
force_change = false;
RenderingServer::get_singleton()->camera_set_orthogonal(camera, size, _near, _far);
update_gizmos();
}
void Camera3D::set_frustum(real_t p_size, Vector2 p_offset, real_t p_z_near, real_t p_z_far) {
if (!force_change && size == p_size && frustum_offset == p_offset && p_z_near == _near && p_z_far == _far && mode == PROJECTION_FRUSTUM) {
return;
}
size = p_size;
frustum_offset = p_offset;
_near = p_z_near;
_far = p_z_far;
mode = PROJECTION_FRUSTUM;
force_change = false;
RenderingServer::get_singleton()->camera_set_frustum(camera, size, frustum_offset, _near, _far);
update_gizmos();
}
void Camera3D::set_projection(ProjectionType p_mode) {
if (p_mode == PROJECTION_PERSPECTIVE || p_mode == PROJECTION_ORTHOGONAL || p_mode == PROJECTION_FRUSTUM) {
mode = p_mode;
_update_camera_mode();
notify_property_list_changed();
}
}
RID Camera3D::get_camera() const {
return camera;
}
void Camera3D::make_current() {
current = true;
if (!is_inside_tree()) {
return;
}
get_viewport()->_camera_3d_set(this);
}
void Camera3D::clear_current(bool p_enable_next) {
current = false;
if (!is_inside_tree()) {
return;
}
if (get_viewport()->get_camera_3d() == this) {
get_viewport()->_camera_3d_set(nullptr);
if (p_enable_next && !Engine::get_singleton()->is_editor_hint()) {
get_viewport()->_camera_3d_make_next_current(this);
}
}
}
void Camera3D::set_current(bool p_enabled) {
if (p_enabled) {
make_current();
} else {
clear_current();
}
}
bool Camera3D::is_current() const {
if (is_inside_tree() && !is_part_of_edited_scene()) {
return get_viewport()->get_camera_3d() == this;
} else {
return current;
}
}
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.");
Size2 viewport_size = get_viewport()->get_camera_rect_size();
Vector2 cpos = get_viewport()->get_camera_coords(p_pos);
Vector3 ray;
if (mode == PROJECTION_ORTHOGONAL) {
ray = Vector3(0, 0, -1);
} else {
Projection cm = _get_camera_projection(_near);
Vector2 screen_he = cm.get_viewport_half_extents();
ray = Vector3(((cpos.x / viewport_size.width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (cpos.y / viewport_size.height)) * 2.0 - 1.0) * screen_he.y, -_near).normalized();
}
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.");
Size2 viewport_size = get_viewport()->get_camera_rect_size();
Vector2 cpos = get_viewport()->get_camera_coords(p_pos);
ERR_FAIL_COND_V(viewport_size.y == 0, Vector3());
if (mode == PROJECTION_ORTHOGONAL) {
Vector2 pos = cpos / viewport_size;
real_t vsize, hsize;
if (keep_aspect == KEEP_WIDTH) {
vsize = size / viewport_size.aspect();
hsize = size;
} else {
hsize = size * viewport_size.aspect();
vsize = size;
}
Vector3 ray;
ray.x = pos.x * (hsize)-hsize / 2;
ray.y = (1.0 - pos.y) * (vsize)-vsize / 2;
ray.z = -_near;
ray = get_camera_transform().xform(ray);
return ray;
} else {
return get_camera_transform().origin;
};
}
bool Camera3D::is_position_behind(const Vector3 &p_pos) const {
Transform3D t = get_global_transform();
Vector3 eyedir = -t.basis.get_column(2).normalized();
return eyedir.dot(p_pos - t.origin) < _near;
}
Vector<Vector3> Camera3D::get_near_plane_points() const {
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector<Vector3>(), "Camera is not inside scene.");
Projection cm = _get_camera_projection(_near);
Vector3 endpoints[8];
cm.get_endpoints(Transform3D(), endpoints);
Vector<Vector3> points = {
Vector3(),
endpoints[4],
endpoints[5],
endpoints[6],
endpoints[7]
};
return points;
}
Point2 Camera3D::unproject_position(const Vector3 &p_pos) const {
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector2(), "Camera is not inside scene.");
Size2 viewport_size = get_viewport()->get_visible_rect().size;
Projection cm = _get_camera_projection(_near);
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;
res.x = (p.normal.x * 0.5 + 0.5) * viewport_size.x;
res.y = (-p.normal.y * 0.5 + 0.5) * viewport_size.y;
return res;
}
Vector3 Camera3D::project_position(const Point2 &p_point, real_t p_z_depth) const {
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene.");
if (p_z_depth == 0 && mode != PROJECTION_ORTHOGONAL) {
return get_global_transform().origin;
}
Size2 viewport_size = get_viewport()->get_visible_rect().size;
Projection cm = _get_camera_projection(_near);
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;
point.y = (1.0 - (p_point.y / viewport_size.y)) * 2.0 - 1.0;
point *= vp_he;
Vector3 p(point.x, point.y, -p_z_depth);
return get_camera_transform().xform(p);
}
void Camera3D::set_environment(const Ref<Environment> &p_environment) {
environment = p_environment;
if (environment.is_valid()) {
RS::get_singleton()->camera_set_environment(camera, environment->get_rid());
} else {
RS::get_singleton()->camera_set_environment(camera, RID());
}
_update_camera_mode();
}
Ref<Environment> Camera3D::get_environment() const {
return environment;
}
void Camera3D::set_attributes(const Ref<CameraAttributes> &p_attributes) {
if (attributes.is_valid()) {
CameraAttributesPhysical *physical_attributes = Object::cast_to<CameraAttributesPhysical>(attributes.ptr());
if (physical_attributes) {
attributes->disconnect_changed(callable_mp(this, &Camera3D::_attributes_changed));
}
}
attributes = p_attributes;
if (attributes.is_valid()) {
CameraAttributesPhysical *physical_attributes = Object::cast_to<CameraAttributesPhysical>(attributes.ptr());
if (physical_attributes) {
attributes->connect_changed(callable_mp(this, &Camera3D::_attributes_changed));
_attributes_changed();
}
RS::get_singleton()->camera_set_camera_attributes(camera, attributes->get_rid());
} else {
RS::get_singleton()->camera_set_camera_attributes(camera, RID());
}
notify_property_list_changed();
}
Ref<CameraAttributes> Camera3D::get_attributes() const {
return attributes;
}
void Camera3D::_attributes_changed() {
CameraAttributesPhysical *physical_attributes = Object::cast_to<CameraAttributesPhysical>(attributes.ptr());
ERR_FAIL_NULL(physical_attributes);
fov = physical_attributes->get_fov();
_near = physical_attributes->get_near();
_far = physical_attributes->get_far();
keep_aspect = KEEP_HEIGHT;
_update_camera_mode();
}
void Camera3D::set_compositor(const Ref<Compositor> &p_compositor) {
compositor = p_compositor;
if (compositor.is_valid()) {
RS::get_singleton()->camera_set_compositor(camera, compositor->get_rid());
} else {
RS::get_singleton()->camera_set_compositor(camera, RID());
}
_update_camera_mode();
}
Ref<Compositor> Camera3D::get_compositor() const {
return compositor;
}
void Camera3D::set_keep_aspect_mode(KeepAspect p_aspect) {
keep_aspect = p_aspect;
RenderingServer::get_singleton()->camera_set_use_vertical_aspect(camera, p_aspect == KEEP_WIDTH);
_update_camera_mode();
notify_property_list_changed();
}
Camera3D::KeepAspect Camera3D::get_keep_aspect_mode() const {
return keep_aspect;
}
void Camera3D::set_doppler_tracking(DopplerTracking p_tracking) {
if (doppler_tracking == p_tracking) {
return;
}
doppler_tracking = p_tracking;
if (p_tracking != DOPPLER_TRACKING_DISABLED) {
velocity_tracker->set_track_physics_step(doppler_tracking == DOPPLER_TRACKING_PHYSICS_STEP);
if (is_inside_tree()) {
velocity_tracker->reset(get_global_transform().origin);
}
}
_update_camera_mode();
}
Camera3D::DopplerTracking Camera3D::get_doppler_tracking() const {
return doppler_tracking;
}
void Camera3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("project_ray_normal", "screen_point"), &Camera3D::project_ray_normal);
ClassDB::bind_method(D_METHOD("project_local_ray_normal", "screen_point"), &Camera3D::project_local_ray_normal);
ClassDB::bind_method(D_METHOD("project_ray_origin", "screen_point"), &Camera3D::project_ray_origin);
ClassDB::bind_method(D_METHOD("unproject_position", "world_point"), &Camera3D::unproject_position);
ClassDB::bind_method(D_METHOD("is_position_behind", "world_point"), &Camera3D::is_position_behind);
ClassDB::bind_method(D_METHOD("project_position", "screen_point", "z_depth"), &Camera3D::project_position);
ClassDB::bind_method(D_METHOD("set_perspective", "fov", "z_near", "z_far"), &Camera3D::set_perspective);
ClassDB::bind_method(D_METHOD("set_orthogonal", "size", "z_near", "z_far"), &Camera3D::set_orthogonal);
ClassDB::bind_method(D_METHOD("set_frustum", "size", "offset", "z_near", "z_far"), &Camera3D::set_frustum);
ClassDB::bind_method(D_METHOD("make_current"), &Camera3D::make_current);
ClassDB::bind_method(D_METHOD("clear_current", "enable_next"), &Camera3D::clear_current, DEFVAL(true));
ClassDB::bind_method(D_METHOD("set_current", "enabled"), &Camera3D::set_current);
ClassDB::bind_method(D_METHOD("is_current"), &Camera3D::is_current);
ClassDB::bind_method(D_METHOD("get_camera_transform"), &Camera3D::get_camera_transform);
ClassDB::bind_method(D_METHOD("get_camera_projection"), &Camera3D::get_camera_projection);
ClassDB::bind_method(D_METHOD("get_fov"), &Camera3D::get_fov);
ClassDB::bind_method(D_METHOD("get_frustum_offset"), &Camera3D::get_frustum_offset);
ClassDB::bind_method(D_METHOD("get_size"), &Camera3D::get_size);
ClassDB::bind_method(D_METHOD("get_far"), &Camera3D::get_far);
ClassDB::bind_method(D_METHOD("get_near"), &Camera3D::get_near);
ClassDB::bind_method(D_METHOD("set_fov", "fov"), &Camera3D::set_fov);
ClassDB::bind_method(D_METHOD("set_frustum_offset", "offset"), &Camera3D::set_frustum_offset);
ClassDB::bind_method(D_METHOD("set_size", "size"), &Camera3D::set_size);
ClassDB::bind_method(D_METHOD("set_far", "far"), &Camera3D::set_far);
ClassDB::bind_method(D_METHOD("set_near", "near"), &Camera3D::set_near);
ClassDB::bind_method(D_METHOD("get_projection"), &Camera3D::get_projection);
ClassDB::bind_method(D_METHOD("set_projection", "mode"), &Camera3D::set_projection);
ClassDB::bind_method(D_METHOD("set_h_offset", "offset"), &Camera3D::set_h_offset);
ClassDB::bind_method(D_METHOD("get_h_offset"), &Camera3D::get_h_offset);
ClassDB::bind_method(D_METHOD("set_v_offset", "offset"), &Camera3D::set_v_offset);
ClassDB::bind_method(D_METHOD("get_v_offset"), &Camera3D::get_v_offset);
ClassDB::bind_method(D_METHOD("set_cull_mask", "mask"), &Camera3D::set_cull_mask);
ClassDB::bind_method(D_METHOD("get_cull_mask"), &Camera3D::get_cull_mask);
ClassDB::bind_method(D_METHOD("set_environment", "env"), &Camera3D::set_environment);
ClassDB::bind_method(D_METHOD("get_environment"), &Camera3D::get_environment);
ClassDB::bind_method(D_METHOD("set_attributes", "env"), &Camera3D::set_attributes);
ClassDB::bind_method(D_METHOD("get_attributes"), &Camera3D::get_attributes);
ClassDB::bind_method(D_METHOD("set_compositor", "compositor"), &Camera3D::set_compositor);
ClassDB::bind_method(D_METHOD("get_compositor"), &Camera3D::get_compositor);
ClassDB::bind_method(D_METHOD("set_keep_aspect_mode", "mode"), &Camera3D::set_keep_aspect_mode);
ClassDB::bind_method(D_METHOD("get_keep_aspect_mode"), &Camera3D::get_keep_aspect_mode);
ClassDB::bind_method(D_METHOD("set_doppler_tracking", "mode"), &Camera3D::set_doppler_tracking);
ClassDB::bind_method(D_METHOD("get_doppler_tracking"), &Camera3D::get_doppler_tracking);
ClassDB::bind_method(D_METHOD("get_frustum"), &Camera3D::_get_frustum);
ClassDB::bind_method(D_METHOD("is_position_in_frustum", "world_point"), &Camera3D::is_position_in_frustum);
ClassDB::bind_method(D_METHOD("get_camera_rid"), &Camera3D::get_camera);
#ifndef PHYSICS_3D_DISABLED
ClassDB::bind_method(D_METHOD("get_pyramid_shape_rid"), &Camera3D::get_pyramid_shape_rid);
#endif // PHYSICS_3D_DISABLED
ClassDB::bind_method(D_METHOD("set_cull_mask_value", "layer_number", "value"), &Camera3D::set_cull_mask_value);
ClassDB::bind_method(D_METHOD("get_cull_mask_value", "layer_number"), &Camera3D::get_cull_mask_value);
//ClassDB::bind_method(D_METHOD("_camera_make_current"),&Camera::_camera_make_current );
ADD_PROPERTY(PropertyInfo(Variant::INT, "keep_aspect", PROPERTY_HINT_ENUM, "Keep Width,Keep Height"), "set_keep_aspect_mode", "get_keep_aspect_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "environment", PROPERTY_HINT_RESOURCE_TYPE, "Environment"), "set_environment", "get_environment");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "attributes", PROPERTY_HINT_RESOURCE_TYPE, "CameraAttributesPractical,CameraAttributesPhysical"), "set_attributes", "get_attributes");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "compositor", PROPERTY_HINT_RESOURCE_TYPE, "Compositor"), "set_compositor", "get_compositor");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_h_offset", "get_h_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_v_offset", "get_v_offset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "doppler_tracking", PROPERTY_HINT_ENUM, "Disabled,Idle,Physics"), "set_doppler_tracking", "get_doppler_tracking");
ADD_PROPERTY(PropertyInfo(Variant::INT, "projection", PROPERTY_HINT_ENUM, "Perspective,Orthogonal,Frustum"), "set_projection", "get_projection");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fov", PROPERTY_HINT_RANGE, "1,179,0.1,degrees"), "set_fov", "get_fov");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frustum_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_frustum_offset", "get_frustum_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "near", PROPERTY_HINT_RANGE, "0.001,10,0.001,or_greater,exp,suffix:m"), "set_near", "get_near");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "far", PROPERTY_HINT_RANGE, "0.01,4000,0.01,or_greater,exp,suffix:m"), "set_far", "get_far");
BIND_ENUM_CONSTANT(PROJECTION_PERSPECTIVE);
BIND_ENUM_CONSTANT(PROJECTION_ORTHOGONAL);
BIND_ENUM_CONSTANT(PROJECTION_FRUSTUM);
BIND_ENUM_CONSTANT(KEEP_WIDTH);
BIND_ENUM_CONSTANT(KEEP_HEIGHT);
BIND_ENUM_CONSTANT(DOPPLER_TRACKING_DISABLED);
BIND_ENUM_CONSTANT(DOPPLER_TRACKING_IDLE_STEP);
BIND_ENUM_CONSTANT(DOPPLER_TRACKING_PHYSICS_STEP);
}
real_t Camera3D::get_fov() const {
return fov;
}
real_t Camera3D::get_size() const {
return size;
}
real_t Camera3D::get_near() const {
return _near;
}
Vector2 Camera3D::get_frustum_offset() const {
return frustum_offset;
}
real_t Camera3D::get_far() const {
return _far;
}
Camera3D::ProjectionType Camera3D::get_projection() const {
return mode;
}
void Camera3D::set_fov(real_t p_fov) {
ERR_FAIL_COND(p_fov < 1 || p_fov > 179);
fov = p_fov;
_update_camera_mode();
}
void Camera3D::set_size(real_t p_size) {
ERR_FAIL_COND(p_size <= CMP_EPSILON);
size = p_size;
_update_camera_mode();
}
void Camera3D::set_near(real_t p_near) {
_near = p_near;
_update_camera_mode();
}
void Camera3D::set_frustum_offset(Vector2 p_offset) {
frustum_offset = p_offset;
_update_camera_mode();
}
void Camera3D::set_far(real_t p_far) {
_far = p_far;
_update_camera_mode();
}
void Camera3D::set_cull_mask(uint32_t p_layers) {
layers = p_layers;
RenderingServer::get_singleton()->camera_set_cull_mask(camera, layers);
_update_camera_mode();
}
uint32_t Camera3D::get_cull_mask() const {
return layers;
}
void Camera3D::set_cull_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_cull_mask();
if (p_value) {
mask |= 1 << (p_layer_number - 1);
} else {
mask &= ~(1 << (p_layer_number - 1));
}
set_cull_mask(mask);
}
bool Camera3D::get_cull_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 layers & (1 << (p_layer_number - 1));
}
Vector<Plane> Camera3D::get_frustum() const {
ERR_FAIL_COND_V(!is_inside_world(), Vector<Plane>());
Projection cm = _get_camera_projection(_near);
return cm.get_projection_planes(get_camera_transform());
}
TypedArray<Plane> Camera3D::_get_frustum() const {
Variant ret = get_frustum();
return ret;
}
bool Camera3D::is_position_in_frustum(const Vector3 &p_position) const {
Vector<Plane> frustum = get_frustum();
for (int i = 0; i < frustum.size(); i++) {
if (frustum[i].is_point_over(p_position)) {
return false;
}
}
return true;
}
void Camera3D::set_v_offset(real_t p_offset) {
v_offset = p_offset;
_update_camera();
}
real_t Camera3D::get_v_offset() const {
return v_offset;
}
void Camera3D::set_h_offset(real_t p_offset) {
h_offset = p_offset;
_update_camera();
}
real_t Camera3D::get_h_offset() const {
return h_offset;
}
Vector3 Camera3D::get_doppler_tracked_velocity() const {
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
return velocity_tracker->get_tracked_linear_velocity();
} else {
return Vector3();
}
}
#ifndef PHYSICS_3D_DISABLED
RID Camera3D::get_pyramid_shape_rid() {
ERR_FAIL_COND_V_MSG(!is_inside_tree(), RID(), "Camera is not inside scene.");
if (pyramid_shape == RID()) {
pyramid_shape_points = get_near_plane_points();
pyramid_shape = PhysicsServer3D::get_singleton()->convex_polygon_shape_create();
PhysicsServer3D::get_singleton()->shape_set_data(pyramid_shape, pyramid_shape_points);
} else { //check if points changed
Vector<Vector3> local_points = get_near_plane_points();
bool all_equal = true;
for (int i = 0; i < 5; i++) {
if (local_points[i] != pyramid_shape_points[i]) {
all_equal = false;
break;
}
}
if (!all_equal) {
PhysicsServer3D::get_singleton()->shape_set_data(pyramid_shape, local_points);
pyramid_shape_points = local_points;
}
}
return pyramid_shape;
}
#endif // PHYSICS_3D_DISABLED
Camera3D::Camera3D() {
camera = RenderingServer::get_singleton()->camera_create();
set_perspective(75.0, 0.05, 4000.0);
RenderingServer::get_singleton()->camera_set_cull_mask(camera, layers);
//active=false;
velocity_tracker.instantiate();
set_notify_transform(true);
set_disable_scale(true);
}
Camera3D::~Camera3D() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RenderingServer::get_singleton()->free(camera);
#ifndef PHYSICS_3D_DISABLED
if (pyramid_shape.is_valid()) {
ERR_FAIL_NULL(PhysicsServer3D::get_singleton());
PhysicsServer3D::get_singleton()->free(pyramid_shape);
}
#endif // PHYSICS_3D_DISABLED
}

219
scene/3d/camera_3d.h Normal file
View File

@@ -0,0 +1,219 @@
/**************************************************************************/
/* camera_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. */
/**************************************************************************/
#pragma once
#include "core/templates/interpolated_property.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/velocity_tracker_3d.h"
#include "scene/resources/camera_attributes.h"
#include "scene/resources/compositor.h"
#include "scene/resources/environment.h"
class Camera3D : public Node3D {
GDCLASS(Camera3D, Node3D);
public:
enum ProjectionType {
PROJECTION_PERSPECTIVE,
PROJECTION_ORTHOGONAL,
PROJECTION_FRUSTUM
};
enum KeepAspect {
KEEP_WIDTH,
KEEP_HEIGHT
};
enum DopplerTracking {
DOPPLER_TRACKING_DISABLED,
DOPPLER_TRACKING_IDLE_STEP,
DOPPLER_TRACKING_PHYSICS_STEP
};
private:
bool force_change = false;
bool current = false;
Viewport *viewport = nullptr;
ProjectionType mode = PROJECTION_PERSPECTIVE;
InterpolatedProperty<real_t> fov = 75.0;
InterpolatedProperty<real_t> size = 1.0;
InterpolatedProperty<Vector2> frustum_offset;
// _ prefix to avoid conflict with Windows defines.
InterpolatedProperty<real_t> _near = 0.05;
InterpolatedProperty<real_t> _far = 4000.0;
real_t v_offset = 0.0;
real_t h_offset = 0.0;
KeepAspect keep_aspect = KEEP_HEIGHT;
RID camera;
RID scenario_id;
// String camera_group;
uint32_t layers = 0xfffff;
Ref<Environment> environment;
Ref<CameraAttributes> attributes;
Ref<Compositor> compositor;
void _attributes_changed();
// void _camera_make_current(Node *p_camera);
friend class Viewport;
void _update_audio_listener_state();
TypedArray<Plane> _get_frustum() const;
DopplerTracking doppler_tracking = DOPPLER_TRACKING_DISABLED;
Ref<VelocityTracker3D> velocity_tracker;
#ifndef PHYSICS_3D_DISABLED
RID pyramid_shape;
Vector<Vector3> pyramid_shape_points;
#endif // PHYSICS_3D_DISABLED
// These can be set by derived Cameras.
bool _desired_process_internal = false;
bool _desired_physics_process_internal = false;
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);
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();
virtual void fti_pump_property() override;
virtual void fti_update_servers_property() override;
virtual void fti_update_servers_xform() override;
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
Projection _get_camera_projection(real_t p_near) const;
public:
enum {
NOTIFICATION_BECAME_CURRENT = 50,
NOTIFICATION_LOST_CURRENT = 51
};
void set_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far);
void set_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far);
void set_frustum(real_t p_size, Vector2 p_offset, real_t p_z_near, real_t p_z_far);
void set_projection(Camera3D::ProjectionType p_mode);
void make_current();
void clear_current(bool p_enable_next = true);
void set_current(bool p_enabled);
bool is_current() const;
RID get_camera() const;
real_t get_fov() const;
real_t get_size() const;
real_t get_far() const;
real_t get_near() const;
Vector2 get_frustum_offset() const;
ProjectionType get_projection() const;
void set_fov(real_t p_fov);
void set_size(real_t p_size);
void set_far(real_t p_far);
void set_near(real_t p_near);
void set_frustum_offset(Vector2 p_offset);
virtual Transform3D get_camera_transform() const;
virtual Projection get_camera_projection() const;
virtual Vector3 project_ray_normal(const Point2 &p_pos) const;
virtual Vector3 project_ray_origin(const Point2 &p_pos) const;
virtual Vector3 project_local_ray_normal(const Point2 &p_pos) const;
virtual Point2 unproject_position(const Vector3 &p_pos) const;
bool is_position_behind(const Vector3 &p_pos) const;
virtual Vector3 project_position(const Point2 &p_point, real_t p_z_depth) const;
Vector<Vector3> get_near_plane_points() const;
void set_cull_mask(uint32_t p_layers);
uint32_t get_cull_mask() const;
void set_cull_mask_value(int p_layer_number, bool p_enable);
bool get_cull_mask_value(int p_layer_number) const;
virtual Vector<Plane> get_frustum() const;
bool is_position_in_frustum(const Vector3 &p_position) const;
void set_environment(const Ref<Environment> &p_environment);
Ref<Environment> get_environment() const;
void set_attributes(const Ref<CameraAttributes> &p_effects);
Ref<CameraAttributes> get_attributes() const;
void set_compositor(const Ref<Compositor> &p_compositor);
Ref<Compositor> get_compositor() const;
void set_keep_aspect_mode(KeepAspect p_aspect);
KeepAspect get_keep_aspect_mode() const;
void set_v_offset(real_t p_offset);
real_t get_v_offset() const;
void set_h_offset(real_t p_offset);
real_t get_h_offset() const;
void set_doppler_tracking(DopplerTracking p_tracking);
DopplerTracking get_doppler_tracking() const;
Vector3 get_doppler_tracked_velocity() const;
#ifndef PHYSICS_3D_DISABLED
RID get_pyramid_shape_rid();
#endif // PHYSICS_3D_DISABLED
Camera3D();
~Camera3D();
};
VARIANT_ENUM_CAST(Camera3D::ProjectionType);
VARIANT_ENUM_CAST(Camera3D::KeepAspect);
VARIANT_ENUM_CAST(Camera3D::DopplerTracking);

View File

@@ -0,0 +1,413 @@
/**************************************************************************/
/* convert_transform_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 "convert_transform_modifier_3d.h"
constexpr const char *HINT_POSITION = "-10,10,0.01,or_greater,or_less,suffix:m";
constexpr const char *HINT_ROTATION = "-180,180,0.01,radians_as_degrees";
constexpr const char *HINT_SCALE = "0,10,0.01,or_greater";
bool ConvertTransformModifier3D::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("settings/")) {
int which = path.get_slicec('/', 1).to_int();
String where = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, settings.size(), false);
String what = path.get_slicec('/', 3);
if (where == "apply") {
if (what == "transform_mode") {
set_apply_transform_mode(which, static_cast<TransformMode>((int)p_value));
} else if (what == "axis") {
set_apply_axis(which, static_cast<Vector3::Axis>((int)p_value));
} else if (what == "range_min") {
set_apply_range_min(which, p_value);
} else if (what == "range_max") {
set_apply_range_max(which, p_value);
} else {
return false;
}
} else if (where == "reference") {
if (what == "transform_mode") {
set_reference_transform_mode(which, static_cast<TransformMode>((int)p_value));
} else if (what == "axis") {
set_reference_axis(which, static_cast<Vector3::Axis>((int)p_value));
} else if (what == "range_min") {
set_reference_range_min(which, p_value);
} else if (what == "range_max") {
set_reference_range_max(which, p_value);
} else {
return false;
}
} else if (where == "relative") {
set_relative(which, p_value);
} else if (where == "additive") {
set_additive(which, p_value);
} else {
return false;
}
}
return true;
}
bool ConvertTransformModifier3D::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("settings/")) {
int which = path.get_slicec('/', 1).to_int();
String where = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, settings.size(), false);
String what = path.get_slicec('/', 3);
if (where == "apply") {
if (what == "transform_mode") {
r_ret = (int)get_apply_transform_mode(which);
} else if (what == "axis") {
r_ret = (int)get_apply_axis(which);
} else if (what == "range_min") {
r_ret = get_apply_range_min(which);
} else if (what == "range_max") {
r_ret = get_apply_range_max(which);
} else {
return false;
}
} else if (where == "reference") {
if (what == "transform_mode") {
r_ret = (int)get_reference_transform_mode(which);
} else if (what == "axis") {
r_ret = (int)get_reference_axis(which);
} else if (what == "range_min") {
r_ret = get_reference_range_min(which);
} else if (what == "range_max") {
r_ret = get_reference_range_max(which);
} else {
return false;
}
} else if (where == "relative") {
r_ret = is_relative(which);
} else if (where == "additive") {
r_ret = is_additive(which);
} else {
return false;
}
}
return true;
}
void ConvertTransformModifier3D::_get_property_list(List<PropertyInfo> *p_list) const {
BoneConstraint3D::get_property_list(p_list);
for (int i = 0; i < settings.size(); i++) {
String path = "settings/" + itos(i) + "/";
String hint_apply_range;
if (get_apply_transform_mode(i) == TRANSFORM_MODE_POSITION) {
hint_apply_range = HINT_POSITION;
} else if (get_apply_transform_mode(i) == TRANSFORM_MODE_ROTATION) {
hint_apply_range = HINT_ROTATION;
} else {
hint_apply_range = HINT_SCALE;
}
p_list->push_back(PropertyInfo(Variant::INT, path + "apply/transform_mode", PROPERTY_HINT_ENUM, "Position,Rotation,Scale"));
p_list->push_back(PropertyInfo(Variant::INT, path + "apply/axis", PROPERTY_HINT_ENUM, "X,Y,Z"));
p_list->push_back(PropertyInfo(Variant::FLOAT, path + "apply/range_min", PROPERTY_HINT_RANGE, hint_apply_range));
p_list->push_back(PropertyInfo(Variant::FLOAT, path + "apply/range_max", PROPERTY_HINT_RANGE, hint_apply_range));
String hint_reference_range;
if (get_reference_transform_mode(i) == TRANSFORM_MODE_POSITION) {
hint_reference_range = HINT_POSITION;
} else if (get_reference_transform_mode(i) == TRANSFORM_MODE_ROTATION) {
hint_reference_range = HINT_ROTATION;
} else {
hint_reference_range = HINT_SCALE;
}
p_list->push_back(PropertyInfo(Variant::INT, path + "reference/transform_mode", PROPERTY_HINT_ENUM, "Position,Rotation,Scale"));
p_list->push_back(PropertyInfo(Variant::INT, path + "reference/axis", PROPERTY_HINT_ENUM, "X,Y,Z"));
p_list->push_back(PropertyInfo(Variant::FLOAT, path + "reference/range_min", PROPERTY_HINT_RANGE, hint_reference_range));
p_list->push_back(PropertyInfo(Variant::FLOAT, path + "reference/range_max", PROPERTY_HINT_RANGE, hint_reference_range));
p_list->push_back(PropertyInfo(Variant::BOOL, path + "relative"));
p_list->push_back(PropertyInfo(Variant::BOOL, path + "additive"));
}
}
void ConvertTransformModifier3D::_validate_setting(int p_index) {
settings.write[p_index] = memnew(ConvertTransform3DSetting);
}
void ConvertTransformModifier3D::set_apply_transform_mode(int p_index, TransformMode p_transform_mode) {
ERR_FAIL_INDEX(p_index, settings.size());
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
setting->apply_transform_mode = p_transform_mode;
notify_property_list_changed();
}
ConvertTransformModifier3D::TransformMode ConvertTransformModifier3D::get_apply_transform_mode(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), TRANSFORM_MODE_POSITION);
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
return setting->apply_transform_mode;
}
void ConvertTransformModifier3D::set_apply_axis(int p_index, Vector3::Axis p_axis) {
ERR_FAIL_INDEX(p_index, settings.size());
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
setting->apply_axis = p_axis;
}
Vector3::Axis ConvertTransformModifier3D::get_apply_axis(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), Vector3::AXIS_X);
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
return setting->apply_axis;
}
void ConvertTransformModifier3D::set_apply_range_min(int p_index, float p_range_min) {
ERR_FAIL_INDEX(p_index, settings.size());
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
setting->apply_range_min = p_range_min;
}
float ConvertTransformModifier3D::get_apply_range_min(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0);
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
return setting->apply_range_min;
}
void ConvertTransformModifier3D::set_apply_range_max(int p_index, float p_range_max) {
ERR_FAIL_INDEX(p_index, settings.size());
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
setting->apply_range_max = p_range_max;
}
float ConvertTransformModifier3D::get_apply_range_max(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0);
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
return setting->apply_range_max;
}
void ConvertTransformModifier3D::set_reference_transform_mode(int p_index, TransformMode p_transform_mode) {
ERR_FAIL_INDEX(p_index, settings.size());
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
setting->reference_transform_mode = p_transform_mode;
notify_property_list_changed();
}
ConvertTransformModifier3D::TransformMode ConvertTransformModifier3D::get_reference_transform_mode(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), TRANSFORM_MODE_POSITION);
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
return setting->reference_transform_mode;
}
void ConvertTransformModifier3D::set_reference_axis(int p_index, Vector3::Axis p_axis) {
ERR_FAIL_INDEX(p_index, settings.size());
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
setting->reference_axis = p_axis;
}
Vector3::Axis ConvertTransformModifier3D::get_reference_axis(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), Vector3::AXIS_X);
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
return setting->reference_axis;
}
void ConvertTransformModifier3D::set_reference_range_min(int p_index, float p_range_min) {
ERR_FAIL_INDEX(p_index, settings.size());
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
setting->reference_range_min = p_range_min;
}
float ConvertTransformModifier3D::get_reference_range_min(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0);
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
return setting->reference_range_min;
}
void ConvertTransformModifier3D::set_reference_range_max(int p_index, float p_range_max) {
ERR_FAIL_INDEX(p_index, settings.size());
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
setting->reference_range_max = p_range_max;
}
float ConvertTransformModifier3D::get_reference_range_max(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0);
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
return setting->reference_range_max;
}
void ConvertTransformModifier3D::set_relative(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
setting->relative = p_enabled;
}
bool ConvertTransformModifier3D::is_relative(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0);
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
return setting->relative;
}
void ConvertTransformModifier3D::set_additive(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
setting->additive = p_enabled;
}
bool ConvertTransformModifier3D::is_additive(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0);
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
return setting->additive;
}
void ConvertTransformModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_apply_transform_mode", "index", "transform_mode"), &ConvertTransformModifier3D::set_apply_transform_mode);
ClassDB::bind_method(D_METHOD("get_apply_transform_mode", "index"), &ConvertTransformModifier3D::get_apply_transform_mode);
ClassDB::bind_method(D_METHOD("set_apply_axis", "index", "axis"), &ConvertTransformModifier3D::set_apply_axis);
ClassDB::bind_method(D_METHOD("get_apply_axis", "index"), &ConvertTransformModifier3D::get_apply_axis);
ClassDB::bind_method(D_METHOD("set_apply_range_min", "index", "range_min"), &ConvertTransformModifier3D::set_apply_range_min);
ClassDB::bind_method(D_METHOD("get_apply_range_min", "index"), &ConvertTransformModifier3D::get_apply_range_min);
ClassDB::bind_method(D_METHOD("set_apply_range_max", "index", "range_max"), &ConvertTransformModifier3D::set_apply_range_max);
ClassDB::bind_method(D_METHOD("get_apply_range_max", "index"), &ConvertTransformModifier3D::get_apply_range_max);
ClassDB::bind_method(D_METHOD("set_reference_transform_mode", "index", "transform_mode"), &ConvertTransformModifier3D::set_reference_transform_mode);
ClassDB::bind_method(D_METHOD("get_reference_transform_mode", "index"), &ConvertTransformModifier3D::get_reference_transform_mode);
ClassDB::bind_method(D_METHOD("set_reference_axis", "index", "axis"), &ConvertTransformModifier3D::set_reference_axis);
ClassDB::bind_method(D_METHOD("get_reference_axis", "index"), &ConvertTransformModifier3D::get_reference_axis);
ClassDB::bind_method(D_METHOD("set_reference_range_min", "index", "range_min"), &ConvertTransformModifier3D::set_reference_range_min);
ClassDB::bind_method(D_METHOD("get_reference_range_min", "index"), &ConvertTransformModifier3D::get_reference_range_min);
ClassDB::bind_method(D_METHOD("set_reference_range_max", "index", "range_max"), &ConvertTransformModifier3D::set_reference_range_max);
ClassDB::bind_method(D_METHOD("get_reference_range_max", "index"), &ConvertTransformModifier3D::get_reference_range_max);
ClassDB::bind_method(D_METHOD("set_relative", "index", "enabled"), &ConvertTransformModifier3D::set_relative);
ClassDB::bind_method(D_METHOD("is_relative", "index"), &ConvertTransformModifier3D::is_relative);
ClassDB::bind_method(D_METHOD("set_additive", "index", "enabled"), &ConvertTransformModifier3D::set_additive);
ClassDB::bind_method(D_METHOD("is_additive", "index"), &ConvertTransformModifier3D::is_additive);
ADD_ARRAY_COUNT("Settings", "setting_count", "set_setting_count", "get_setting_count", "settings/");
BIND_ENUM_CONSTANT(TRANSFORM_MODE_POSITION);
BIND_ENUM_CONSTANT(TRANSFORM_MODE_ROTATION);
BIND_ENUM_CONSTANT(TRANSFORM_MODE_SCALE);
}
void ConvertTransformModifier3D::_process_constraint(int p_index, Skeleton3D *p_skeleton, int p_apply_bone, int p_reference_bone, float p_amount) {
ConvertTransform3DSetting *setting = static_cast<ConvertTransform3DSetting *>(settings[p_index]);
Transform3D destination = p_skeleton->get_bone_pose(p_reference_bone);
if (setting->relative) {
Vector3 scl_relative = destination.basis.get_scale() / p_skeleton->get_bone_rest(p_reference_bone).basis.get_scale();
destination.basis = p_skeleton->get_bone_rest(p_reference_bone).basis.get_rotation_quaternion().inverse() * destination.basis.get_rotation_quaternion();
destination.basis.scale_local(scl_relative);
destination.origin = destination.origin - p_skeleton->get_bone_rest(p_reference_bone).origin;
}
// Retrieve point from reference.
double point = 0.0;
int axis = (int)setting->reference_axis;
switch (setting->reference_transform_mode) {
case TRANSFORM_MODE_POSITION: {
point = destination.origin[axis];
} break;
case TRANSFORM_MODE_ROTATION: {
Quaternion tgt_rot = destination.basis.get_rotation_quaternion();
point = get_roll_angle(tgt_rot, get_vector_from_axis(setting->reference_axis));
} break;
case TRANSFORM_MODE_SCALE: {
point = destination.basis.get_scale()[axis];
} break;
}
// Convert point to apply.
destination = p_skeleton->get_bone_pose(p_apply_bone);
if (Math::is_equal_approx(setting->reference_range_min, setting->reference_range_max)) {
point = point <= (double)setting->reference_range_min ? 0 : 1;
} else {
point = Math::inverse_lerp((double)setting->reference_range_min, (double)setting->reference_range_max, point);
}
point = Math::lerp((double)setting->apply_range_min, (double)setting->apply_range_max, CLAMP(point, 0, 1));
axis = (int)setting->apply_axis;
switch (setting->apply_transform_mode) {
case TRANSFORM_MODE_POSITION: {
if (setting->additive) {
point = p_skeleton->get_bone_pose(p_apply_bone).origin[axis] + point;
} else if (setting->relative) {
point = p_skeleton->get_bone_rest(p_apply_bone).origin[axis] + point;
}
destination.origin[axis] = point;
} break;
case TRANSFORM_MODE_ROTATION: {
Vector3 rot_axis = get_vector_from_axis(setting->apply_axis);
Vector3 dest_scl = destination.basis.get_scale();
if (influence < 1.0 || p_amount < 1.0) {
point = CLAMP(point, CMP_EPSILON - Math::PI, Math::PI - CMP_EPSILON); // Hack to consistent slerp (interpolate_with) orientation since -180/180 deg rot is mixed in slerp.
}
Quaternion rot = Quaternion(rot_axis, point);
if (setting->additive) {
destination.basis = p_skeleton->get_bone_pose(p_apply_bone).basis.get_rotation_quaternion() * rot;
} else if (setting->relative) {
destination.basis = p_skeleton->get_bone_rest(p_apply_bone).basis.get_rotation_quaternion() * rot;
} else {
destination.basis = rot;
}
// Scale may not have meaning, but it might affect when it is negative.
destination.basis.scale_local(dest_scl);
} break;
case TRANSFORM_MODE_SCALE: {
Vector3 dest_scl = Vector3(1, 1, 1);
if (setting->additive) {
dest_scl = p_skeleton->get_bone_pose(p_apply_bone).basis.get_scale();
dest_scl[axis] = dest_scl[axis] * point;
} else if (setting->relative) {
dest_scl = p_skeleton->get_bone_rest(p_apply_bone).basis.get_scale();
dest_scl[axis] = dest_scl[axis] * point;
} else {
dest_scl = p_skeleton->get_bone_pose(p_apply_bone).basis.get_scale();
dest_scl[axis] = point;
}
destination.basis = destination.basis.orthonormalized().scaled_local(dest_scl);
} break;
}
// Process interpolation depends on the amount.
destination = p_skeleton->get_bone_pose(p_apply_bone).interpolate_with(destination, p_amount);
// Apply transform depends on the mode.
switch (setting->apply_transform_mode) {
case TRANSFORM_MODE_POSITION: {
p_skeleton->set_bone_pose_position(p_apply_bone, destination.origin);
} break;
case TRANSFORM_MODE_ROTATION: {
p_skeleton->set_bone_pose_rotation(p_apply_bone, destination.basis.get_rotation_quaternion());
} break;
case TRANSFORM_MODE_SCALE: {
p_skeleton->set_bone_pose_scale(p_apply_bone, destination.basis.get_scale());
} break;
}
}
ConvertTransformModifier3D::~ConvertTransformModifier3D() {
clear_settings();
}

View File

@@ -0,0 +1,98 @@
/**************************************************************************/
/* convert_transform_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/bone_constraint_3d.h"
class ConvertTransformModifier3D : public BoneConstraint3D {
GDCLASS(ConvertTransformModifier3D, BoneConstraint3D);
public:
enum TransformMode {
TRANSFORM_MODE_POSITION,
TRANSFORM_MODE_ROTATION,
TRANSFORM_MODE_SCALE,
};
struct ConvertTransform3DSetting : public BoneConstraint3DSetting {
TransformMode apply_transform_mode = TRANSFORM_MODE_POSITION;
Vector3::Axis apply_axis = Vector3::AXIS_X;
float apply_range_min = 0.0;
float apply_range_max = 0.0;
TransformMode reference_transform_mode = TRANSFORM_MODE_POSITION;
Vector3::Axis reference_axis = Vector3::AXIS_X;
float reference_range_min = 0.0;
float reference_range_max = 0.0;
bool relative = true;
bool additive = false;
};
protected:
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;
static void _bind_methods();
virtual void _process_constraint(int p_index, Skeleton3D *p_skeleton, int p_apply_bone, int p_reference_bone, float p_amount) override;
virtual void _validate_setting(int p_index) override;
public:
void set_apply_transform_mode(int p_index, TransformMode p_transform_mode);
TransformMode get_apply_transform_mode(int p_index) const;
void set_apply_axis(int p_index, Vector3::Axis p_axis);
Vector3::Axis get_apply_axis(int p_index) const;
void set_apply_range_min(int p_index, float p_range_min);
float get_apply_range_min(int p_index) const;
void set_apply_range_max(int p_index, float p_range_max);
float get_apply_range_max(int p_index) const;
void set_reference_transform_mode(int p_index, TransformMode p_transform_mode);
TransformMode get_reference_transform_mode(int p_index) const;
void set_reference_axis(int p_index, Vector3::Axis p_axis);
Vector3::Axis get_reference_axis(int p_index) const;
void set_reference_range_min(int p_index, float p_range_min);
float get_reference_range_min(int p_index) const;
void set_reference_range_max(int p_index, float p_range_max);
float get_reference_range_max(int p_index) const;
void set_relative(int p_index, bool p_enabled);
bool is_relative(int p_index) const;
void set_additive(int p_index, bool p_enabled);
bool is_additive(int p_index) const;
~ConvertTransformModifier3D();
};
VARIANT_ENUM_CAST(ConvertTransformModifier3D::TransformMode);

View File

@@ -0,0 +1,452 @@
/**************************************************************************/
/* copy_transform_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 "copy_transform_modifier_3d.h"
bool CopyTransformModifier3D::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("settings/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, settings.size(), false);
if (what == "copy") {
set_copy_flags(which, static_cast<BitField<TransformFlag>>((int)p_value));
} else if (what == "axes") {
set_axis_flags(which, static_cast<BitField<AxisFlag>>((int)p_value));
} else if (what == "invert") {
set_invert_flags(which, static_cast<BitField<AxisFlag>>((int)p_value));
} else if (what == "relative") {
set_relative(which, p_value);
} else if (what == "additive") {
set_additive(which, p_value);
} else {
return false;
}
}
return true;
}
bool CopyTransformModifier3D::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("settings/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, settings.size(), false);
if (what == "copy") {
r_ret = (int)get_copy_flags(which);
} else if (what == "axes") {
r_ret = (int)get_axis_flags(which);
} else if (what == "invert") {
r_ret = (int)get_invert_flags(which);
} else if (what == "relative") {
r_ret = is_relative(which);
} else if (what == "additive") {
r_ret = is_additive(which);
} else {
return false;
}
}
return true;
}
void CopyTransformModifier3D::_get_property_list(List<PropertyInfo> *p_list) const {
BoneConstraint3D::get_property_list(p_list);
for (int i = 0; i < settings.size(); i++) {
String path = "settings/" + itos(i) + "/";
p_list->push_back(PropertyInfo(Variant::INT, path + "copy", PROPERTY_HINT_FLAGS, "Position,Rotation,Scale"));
p_list->push_back(PropertyInfo(Variant::INT, path + "axes", PROPERTY_HINT_FLAGS, "X,Y,Z"));
p_list->push_back(PropertyInfo(Variant::INT, path + "invert", PROPERTY_HINT_FLAGS, "X,Y,Z"));
p_list->push_back(PropertyInfo(Variant::BOOL, path + "relative"));
p_list->push_back(PropertyInfo(Variant::BOOL, path + "additive"));
}
}
void CopyTransformModifier3D::_validate_setting(int p_index) {
settings.write[p_index] = memnew(CopyTransform3DSetting);
}
void CopyTransformModifier3D::set_copy_flags(int p_index, BitField<TransformFlag> p_copy_flags) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
setting->copy_flags = p_copy_flags;
notify_property_list_changed();
}
BitField<CopyTransformModifier3D::TransformFlag> CopyTransformModifier3D::get_copy_flags(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->copy_flags;
}
void CopyTransformModifier3D::set_axis_flags(int p_index, BitField<AxisFlag> p_axis_flags) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
setting->axis_flags = p_axis_flags;
notify_property_list_changed();
}
BitField<CopyTransformModifier3D::AxisFlag> CopyTransformModifier3D::get_axis_flags(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->axis_flags;
}
void CopyTransformModifier3D::set_invert_flags(int p_index, BitField<AxisFlag> p_axis_flags) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
setting->invert_flags = p_axis_flags;
notify_property_list_changed();
}
BitField<CopyTransformModifier3D::AxisFlag> CopyTransformModifier3D::get_invert_flags(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->invert_flags;
}
void CopyTransformModifier3D::set_copy_position(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
if (p_enabled) {
setting->copy_flags.set_flag(TRANSFORM_FLAG_POSITION);
} else {
setting->copy_flags.clear_flag(TRANSFORM_FLAG_POSITION);
}
}
bool CopyTransformModifier3D::is_position_copying(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->copy_flags.has_flag(TRANSFORM_FLAG_POSITION);
}
void CopyTransformModifier3D::set_copy_rotation(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
if (p_enabled) {
setting->copy_flags.set_flag(TRANSFORM_FLAG_ROTATION);
} else {
setting->copy_flags.clear_flag(TRANSFORM_FLAG_ROTATION);
}
}
bool CopyTransformModifier3D::is_rotation_copying(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->copy_flags.has_flag(TRANSFORM_FLAG_ROTATION);
}
void CopyTransformModifier3D::set_copy_scale(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
if (p_enabled) {
setting->copy_flags.set_flag(TRANSFORM_FLAG_SCALE);
} else {
setting->copy_flags.clear_flag(TRANSFORM_FLAG_SCALE);
}
}
bool CopyTransformModifier3D::is_scale_copying(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->copy_flags.has_flag(TRANSFORM_FLAG_SCALE);
}
void CopyTransformModifier3D::set_axis_x_enabled(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
if (p_enabled) {
setting->axis_flags.set_flag(AXIS_FLAG_X);
} else {
setting->axis_flags.clear_flag(AXIS_FLAG_X);
}
}
bool CopyTransformModifier3D::is_axis_x_enabled(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->axis_flags.has_flag(AXIS_FLAG_X);
}
void CopyTransformModifier3D::set_axis_y_enabled(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
if (p_enabled) {
setting->axis_flags.set_flag(AXIS_FLAG_Y);
} else {
setting->axis_flags.clear_flag(AXIS_FLAG_Y);
}
}
bool CopyTransformModifier3D::is_axis_y_enabled(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->axis_flags.has_flag(AXIS_FLAG_Y);
}
void CopyTransformModifier3D::set_axis_z_enabled(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
if (p_enabled) {
setting->axis_flags.set_flag(AXIS_FLAG_Z);
} else {
setting->axis_flags.clear_flag(AXIS_FLAG_Z);
}
}
bool CopyTransformModifier3D::is_axis_z_enabled(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->axis_flags.has_flag(AXIS_FLAG_Z);
}
void CopyTransformModifier3D::set_axis_x_inverted(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
if (p_enabled) {
setting->invert_flags.set_flag(AXIS_FLAG_X);
} else {
setting->invert_flags.clear_flag(AXIS_FLAG_X);
}
}
bool CopyTransformModifier3D::is_axis_x_inverted(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->invert_flags.has_flag(AXIS_FLAG_X);
}
void CopyTransformModifier3D::set_axis_y_inverted(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
if (p_enabled) {
setting->invert_flags.set_flag(AXIS_FLAG_Y);
} else {
setting->invert_flags.clear_flag(AXIS_FLAG_Y);
}
}
bool CopyTransformModifier3D::is_axis_y_inverted(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->invert_flags.has_flag(AXIS_FLAG_Y);
}
void CopyTransformModifier3D::set_axis_z_inverted(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
if (p_enabled) {
setting->invert_flags.set_flag(AXIS_FLAG_Z);
} else {
setting->invert_flags.clear_flag(AXIS_FLAG_Z);
}
}
bool CopyTransformModifier3D::is_axis_z_inverted(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), false);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->invert_flags.has_flag(AXIS_FLAG_Z);
}
void CopyTransformModifier3D::set_relative(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
setting->relative = p_enabled;
}
bool CopyTransformModifier3D::is_relative(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->relative;
}
void CopyTransformModifier3D::set_additive(int p_index, bool p_enabled) {
ERR_FAIL_INDEX(p_index, settings.size());
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
setting->additive = p_enabled;
}
bool CopyTransformModifier3D::is_additive(int p_index) const {
ERR_FAIL_INDEX_V(p_index, settings.size(), 0);
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
return setting->additive;
}
void CopyTransformModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_copy_flags", "index", "copy_flags"), &CopyTransformModifier3D::set_copy_flags);
ClassDB::bind_method(D_METHOD("get_copy_flags", "index"), &CopyTransformModifier3D::get_copy_flags);
ClassDB::bind_method(D_METHOD("set_axis_flags", "index", "axis_flags"), &CopyTransformModifier3D::set_axis_flags);
ClassDB::bind_method(D_METHOD("get_axis_flags", "index"), &CopyTransformModifier3D::get_axis_flags);
ClassDB::bind_method(D_METHOD("set_invert_flags", "index", "axis_flags"), &CopyTransformModifier3D::set_invert_flags);
ClassDB::bind_method(D_METHOD("get_invert_flags", "index"), &CopyTransformModifier3D::get_invert_flags);
ClassDB::bind_method(D_METHOD("set_copy_position", "index", "enabled"), &CopyTransformModifier3D::set_copy_position);
ClassDB::bind_method(D_METHOD("is_position_copying", "index"), &CopyTransformModifier3D::is_position_copying);
ClassDB::bind_method(D_METHOD("set_copy_rotation", "index", "enabled"), &CopyTransformModifier3D::set_copy_rotation);
ClassDB::bind_method(D_METHOD("is_rotation_copying", "index"), &CopyTransformModifier3D::is_rotation_copying);
ClassDB::bind_method(D_METHOD("set_copy_scale", "index", "enabled"), &CopyTransformModifier3D::set_copy_scale);
ClassDB::bind_method(D_METHOD("is_scale_copying", "index"), &CopyTransformModifier3D::is_scale_copying);
ClassDB::bind_method(D_METHOD("set_axis_x_enabled", "index", "enabled"), &CopyTransformModifier3D::set_axis_x_enabled);
ClassDB::bind_method(D_METHOD("is_axis_x_enabled", "index"), &CopyTransformModifier3D::is_axis_x_enabled);
ClassDB::bind_method(D_METHOD("set_axis_y_enabled", "index", "enabled"), &CopyTransformModifier3D::set_axis_y_enabled);
ClassDB::bind_method(D_METHOD("is_axis_y_enabled", "index"), &CopyTransformModifier3D::is_axis_y_enabled);
ClassDB::bind_method(D_METHOD("set_axis_z_enabled", "index", "enabled"), &CopyTransformModifier3D::set_axis_z_enabled);
ClassDB::bind_method(D_METHOD("is_axis_z_enabled", "index"), &CopyTransformModifier3D::is_axis_z_enabled);
ClassDB::bind_method(D_METHOD("set_axis_x_inverted", "index", "enabled"), &CopyTransformModifier3D::set_axis_x_inverted);
ClassDB::bind_method(D_METHOD("is_axis_x_inverted", "index"), &CopyTransformModifier3D::is_axis_x_inverted);
ClassDB::bind_method(D_METHOD("set_axis_y_inverted", "index", "enabled"), &CopyTransformModifier3D::set_axis_y_inverted);
ClassDB::bind_method(D_METHOD("is_axis_y_inverted", "index"), &CopyTransformModifier3D::is_axis_y_inverted);
ClassDB::bind_method(D_METHOD("set_axis_z_inverted", "index", "enabled"), &CopyTransformModifier3D::set_axis_z_inverted);
ClassDB::bind_method(D_METHOD("is_axis_z_inverted", "index"), &CopyTransformModifier3D::is_axis_z_inverted);
ClassDB::bind_method(D_METHOD("set_relative", "index", "enabled"), &CopyTransformModifier3D::set_relative);
ClassDB::bind_method(D_METHOD("is_relative", "index"), &CopyTransformModifier3D::is_relative);
ClassDB::bind_method(D_METHOD("set_additive", "index", "enabled"), &CopyTransformModifier3D::set_additive);
ClassDB::bind_method(D_METHOD("is_additive", "index"), &CopyTransformModifier3D::is_additive);
ADD_ARRAY_COUNT("Settings", "setting_count", "set_setting_count", "get_setting_count", "settings/");
BIND_BITFIELD_FLAG(TRANSFORM_FLAG_POSITION);
BIND_BITFIELD_FLAG(TRANSFORM_FLAG_ROTATION);
BIND_BITFIELD_FLAG(TRANSFORM_FLAG_SCALE);
BIND_BITFIELD_FLAG(TRANSFORM_FLAG_ALL);
BIND_BITFIELD_FLAG(AXIS_FLAG_X);
BIND_BITFIELD_FLAG(AXIS_FLAG_Y);
BIND_BITFIELD_FLAG(AXIS_FLAG_Z);
BIND_BITFIELD_FLAG(AXIS_FLAG_ALL);
}
void CopyTransformModifier3D::_process_constraint(int p_index, Skeleton3D *p_skeleton, int p_apply_bone, int p_reference_bone, float p_amount) {
CopyTransform3DSetting *setting = static_cast<CopyTransform3DSetting *>(settings[p_index]);
Transform3D destination = p_skeleton->get_bone_pose(p_reference_bone);
if (setting->relative) {
Vector3 scl_relative = destination.basis.get_scale() / p_skeleton->get_bone_rest(p_reference_bone).basis.get_scale();
destination.basis = p_skeleton->get_bone_rest(p_reference_bone).basis.get_rotation_quaternion().inverse() * destination.basis.get_rotation_quaternion();
destination.basis.scale_local(scl_relative);
destination.origin = destination.origin - p_skeleton->get_bone_rest(p_reference_bone).origin;
}
Vector3 dest_pos = destination.origin;
Quaternion dest_rot = destination.basis.get_rotation_quaternion();
Vector3 dest_scl = destination.basis.get_scale();
// Mask pos and scale.
for (int i = 0; i < 3; i++) {
if (!setting->axis_flags.has_flag(static_cast<AxisFlag>(1 << i))) {
dest_pos[i] = 0.0;
dest_scl[i] = 1.0;
}
}
// Mask rot.
switch (static_cast<int>(setting->axis_flags)) {
case 0: {
dest_rot = Quaternion();
} break;
case AXIS_FLAG_X: {
Vector3 axis = get_vector_from_axis(Vector3::AXIS_X);
dest_rot = Quaternion(axis, get_roll_angle(dest_rot, axis));
} break;
case AXIS_FLAG_Y: {
Vector3 axis = get_vector_from_axis(Vector3::AXIS_Y);
dest_rot = Quaternion(axis, get_roll_angle(dest_rot, axis));
} break;
case AXIS_FLAG_Z: {
Vector3 axis = get_vector_from_axis(Vector3::AXIS_Z);
dest_rot = Quaternion(axis, get_roll_angle(dest_rot, axis));
} break;
case AXIS_FLAG_X | AXIS_FLAG_Y: {
Vector3 axis = get_vector_from_axis(Vector3::AXIS_Z);
dest_rot = dest_rot * Quaternion(axis, get_roll_angle(dest_rot, axis)).inverse();
} break;
case AXIS_FLAG_Y | AXIS_FLAG_Z: {
Vector3 axis = get_vector_from_axis(Vector3::AXIS_X);
dest_rot = dest_rot * Quaternion(axis, get_roll_angle(dest_rot, axis)).inverse();
} break;
case AXIS_FLAG_Z | AXIS_FLAG_X: {
Vector3 axis = get_vector_from_axis(Vector3::AXIS_Y);
dest_rot = dest_rot * Quaternion(axis, get_roll_angle(dest_rot, axis)).inverse();
} break;
case AXIS_FLAG_ALL: {
} break;
}
// Process inversion.
for (int i = 0; i < 3; i++) {
AxisFlag axis = static_cast<AxisFlag>(1 << i);
if (setting->axis_flags.has_flag(axis) && setting->invert_flags.has_flag(axis)) {
dest_pos[i] *= -1;
dest_rot[i] *= -1;
dest_scl[i] = 1.0 / dest_scl[i];
}
}
dest_rot.normalize();
if (setting->additive) {
destination.origin = p_skeleton->get_bone_pose_position(p_apply_bone) + dest_pos;
destination.basis = p_skeleton->get_bone_pose_rotation(p_apply_bone) * Basis(dest_rot);
destination.basis.scale_local(p_skeleton->get_bone_pose_scale(p_apply_bone) * dest_scl);
} else if (setting->relative) {
Transform3D rest = p_skeleton->get_bone_rest(p_apply_bone);
destination.origin = rest.origin + dest_pos;
destination.basis = rest.basis.get_rotation_quaternion() * Basis(dest_rot);
destination.basis.scale_local(rest.basis.get_scale() * dest_scl);
} else {
destination.origin = dest_pos;
destination.basis = Basis(dest_rot);
destination.basis.scale_local(dest_scl);
}
// Process interpolation depends on the amount.
destination = p_skeleton->get_bone_pose(p_apply_bone).interpolate_with(destination, p_amount);
// Apply transform depends on the element mask.
if (setting->copy_flags.has_flag(TRANSFORM_FLAG_POSITION)) {
p_skeleton->set_bone_pose_position(p_apply_bone, destination.origin);
}
if (setting->copy_flags.has_flag(TRANSFORM_FLAG_ROTATION)) {
p_skeleton->set_bone_pose_rotation(p_apply_bone, destination.basis.get_rotation_quaternion());
}
if (setting->copy_flags.has_flag(TRANSFORM_FLAG_SCALE)) {
p_skeleton->set_bone_pose_scale(p_apply_bone, destination.basis.get_scale());
}
}
CopyTransformModifier3D::~CopyTransformModifier3D() {
clear_settings();
}

View File

@@ -0,0 +1,112 @@
/**************************************************************************/
/* copy_transform_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/bone_constraint_3d.h"
class CopyTransformModifier3D : public BoneConstraint3D {
GDCLASS(CopyTransformModifier3D, BoneConstraint3D);
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,
};
enum AxisFlag {
AXIS_FLAG_X = 1,
AXIS_FLAG_Y = 2,
AXIS_FLAG_Z = 4,
AXIS_FLAG_ALL = AXIS_FLAG_X | AXIS_FLAG_Y | AXIS_FLAG_Z,
};
struct CopyTransform3DSetting : public BoneConstraint3DSetting {
BitField<TransformFlag> copy_flags = TRANSFORM_FLAG_ALL;
BitField<AxisFlag> axis_flags = AXIS_FLAG_ALL;
BitField<AxisFlag> invert_flags = 0;
bool relative = true;
bool additive = false;
};
protected:
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;
static void _bind_methods();
virtual void _process_constraint(int p_index, Skeleton3D *p_skeleton, int p_apply_bone, int p_reference_bone, float p_amount) override;
virtual void _validate_setting(int p_index) override;
public:
void set_copy_flags(int p_index, BitField<TransformFlag> p_copy_flags);
BitField<TransformFlag> get_copy_flags(int p_index) const;
void set_copy_position(int p_index, bool p_enabled);
bool is_position_copying(int p_index) const;
void set_copy_rotation(int p_index, bool p_enabled);
bool is_rotation_copying(int p_index) const;
void set_copy_scale(int p_index, bool p_enabled);
bool is_scale_copying(int p_index) const;
void set_axis_flags(int p_index, BitField<AxisFlag> p_axis_flags);
BitField<AxisFlag> get_axis_flags(int p_index) const;
void set_axis_x_enabled(int p_index, bool p_enabled);
bool is_axis_x_enabled(int p_index) const;
void set_axis_y_enabled(int p_index, bool p_enabled);
bool is_axis_y_enabled(int p_index) const;
void set_axis_z_enabled(int p_index, bool p_enabled);
bool is_axis_z_enabled(int p_index) const;
void set_invert_flags(int p_index, BitField<AxisFlag> p_axis_flags);
BitField<AxisFlag> get_invert_flags(int p_index) const;
void set_axis_x_inverted(int p_index, bool p_enabled);
bool is_axis_x_inverted(int p_index) const;
void set_axis_y_inverted(int p_index, bool p_enabled);
bool is_axis_y_inverted(int p_index) const;
void set_axis_z_inverted(int p_index, bool p_enabled);
bool is_axis_z_inverted(int p_index) const;
void set_relative(int p_index, bool p_enabled);
bool is_relative(int p_index) const;
void set_additive(int p_index, bool p_enabled);
bool is_additive(int p_index) const;
~CopyTransformModifier3D();
};
VARIANT_BITFIELD_CAST(CopyTransformModifier3D::TransformFlag);
VARIANT_BITFIELD_CAST(CopyTransformModifier3D::AxisFlag);

View 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

File diff suppressed because it is too large Load Diff

346
scene/3d/cpu_particles_3d.h Normal file
View File

@@ -0,0 +1,346 @@
/**************************************************************************/
/* cpu_particles_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/visual_instance_3d.h"
class RandomNumberGenerator;
class CPUParticles3D : public GeometryInstance3D {
private:
GDCLASS(CPUParticles3D, GeometryInstance3D);
public:
enum DrawOrder {
DRAW_ORDER_INDEX,
DRAW_ORDER_LIFETIME,
DRAW_ORDER_VIEW_DEPTH,
DRAW_ORDER_MAX
};
enum Parameter {
PARAM_INITIAL_LINEAR_VELOCITY,
PARAM_ANGULAR_VELOCITY,
PARAM_ORBIT_VELOCITY,
PARAM_LINEAR_ACCEL,
PARAM_RADIAL_ACCEL,
PARAM_TANGENTIAL_ACCEL,
PARAM_DAMPING,
PARAM_ANGLE,
PARAM_SCALE,
PARAM_HUE_VARIATION,
PARAM_ANIM_SPEED,
PARAM_ANIM_OFFSET,
PARAM_MAX
};
enum ParticleFlags {
PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY,
PARTICLE_FLAG_ROTATE_Y,
PARTICLE_FLAG_DISABLE_Z,
PARTICLE_FLAG_MAX
};
enum EmissionShape {
EMISSION_SHAPE_POINT,
EMISSION_SHAPE_SPHERE,
EMISSION_SHAPE_SPHERE_SURFACE,
EMISSION_SHAPE_BOX,
EMISSION_SHAPE_POINTS,
EMISSION_SHAPE_DIRECTED_POINTS,
EMISSION_SHAPE_RING,
EMISSION_SHAPE_MAX
};
private:
bool emitting = false;
bool active = false;
struct Particle {
Transform3D transform;
Color color;
real_t custom[4] = {};
Vector3 velocity;
bool active = false;
real_t angle_rand = 0.0;
real_t scale_rand = 0.0;
real_t hue_rot_rand = 0.0;
real_t anim_offset_rand = 0.0;
Color start_color_rand;
double time = 0.0;
double lifetime = 0.0;
Color base_color;
uint32_t seed = 0;
};
double time = 0.0;
double frame_remainder = 0.0;
int cycle = 0;
bool redraw = false;
RID multimesh;
Vector<Particle> particles;
Vector<float> particle_data;
Vector<int> particle_order;
struct SortLifetime {
const Particle *particles = nullptr;
bool operator()(int p_a, int p_b) const {
return particles[p_a].time > particles[p_b].time;
}
};
struct SortAxis {
const Particle *particles = nullptr;
Vector3 axis;
bool operator()(int p_a, int p_b) const {
return axis.dot(particles[p_a].transform.origin) < axis.dot(particles[p_b].transform.origin);
}
};
//
bool one_shot = false;
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;
double speed_scale = 1.0;
AABB visibility_aabb;
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;
SafeFlag can_update;
DrawOrder draw_order = DRAW_ORDER_INDEX;
Ref<Mesh> mesh;
////////
Vector3 direction = Vector3(1, 0, 0);
real_t spread = 45.0;
real_t flatness = 0.0;
real_t parameters_min[PARAM_MAX] = {};
real_t parameters_max[PARAM_MAX] = {};
Ref<Curve> curve_parameters[PARAM_MAX];
Color color = Color(1, 1, 1, 1);
Ref<Gradient> color_ramp;
Ref<Gradient> color_initial_ramp;
bool particle_flags[PARTICLE_FLAG_MAX] = {};
EmissionShape emission_shape = EMISSION_SHAPE_POINT;
real_t emission_sphere_radius = 1.0;
Vector3 emission_box_extents = Vector3(1, 1, 1);
Vector<Vector3> emission_points;
Vector<Vector3> emission_normals;
Vector<Color> emission_colors;
int emission_point_count = 0;
Vector3 emission_ring_axis;
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;
Ref<Curve> scale_curve_z;
bool split_scale = false;
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;
void _update_render_thread();
void _set_redraw(bool p_redraw);
protected:
static void _bind_methods();
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;
void set_emitting(bool p_emitting);
void set_amount(int p_amount);
void set_lifetime(double p_lifetime);
void set_one_shot(bool p_one_shot);
void set_pre_process_time(double p_time);
void set_explosiveness_ratio(real_t p_ratio);
void set_randomness_ratio(real_t p_ratio);
void set_visibility_aabb(const AABB &p_aabb);
void set_lifetime_randomness(double p_random);
void set_use_local_coordinates(bool p_enable);
void set_speed_scale(double p_scale);
bool is_emitting() const;
int get_amount() const;
double get_lifetime() const;
bool get_one_shot() const;
double get_pre_process_time() const;
real_t get_explosiveness_ratio() const;
real_t get_randomness_ratio() const;
AABB get_visibility_aabb() const;
double get_lifetime_randomness() const;
bool get_use_local_coordinates() const;
double get_speed_scale() const;
void set_fixed_fps(int p_count);
int get_fixed_fps() const;
void set_fractional_delta(bool p_enable);
bool get_fractional_delta() const;
void set_draw_order(DrawOrder p_order);
DrawOrder get_draw_order() const;
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);
Vector3 get_direction() const;
void set_spread(real_t p_spread);
real_t get_spread() const;
void set_flatness(real_t p_flatness);
real_t get_flatness() const;
void set_param_min(Parameter p_param, real_t p_value);
real_t get_param_min(Parameter p_param) const;
void set_param_max(Parameter p_param, real_t p_value);
real_t get_param_max(Parameter p_param) const;
void set_param_curve(Parameter p_param, const Ref<Curve> &p_curve);
Ref<Curve> get_param_curve(Parameter p_param) const;
void set_color(const Color &p_color);
Color get_color() const;
void set_color_ramp(const Ref<Gradient> &p_ramp);
Ref<Gradient> get_color_ramp() const;
void set_color_initial_ramp(const Ref<Gradient> &p_ramp);
Ref<Gradient> get_color_initial_ramp() const;
void set_particle_flag(ParticleFlags p_particle_flag, bool p_enable);
bool get_particle_flag(ParticleFlags p_particle_flag) const;
void set_emission_shape(EmissionShape p_shape);
void set_emission_sphere_radius(real_t p_radius);
void set_emission_box_extents(Vector3 p_extents);
void set_emission_points(const Vector<Vector3> &p_points);
void set_emission_normals(const Vector<Vector3> &p_normals);
void set_emission_colors(const Vector<Color> &p_colors);
void set_emission_ring_axis(Vector3 p_axis);
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);
void set_split_scale(bool p_split_scale);
EmissionShape get_emission_shape() const;
real_t get_emission_sphere_radius() const;
Vector3 get_emission_box_extents() const;
Vector<Vector3> get_emission_points() const;
Vector<Vector3> get_emission_normals() const;
Vector<Color> get_emission_colors() const;
Vector3 get_emission_ring_axis() const;
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;
bool get_split_scale();
void set_gravity(const Vector3 &p_gravity);
Vector3 get_gravity() const;
PackedStringArray get_configuration_warnings() const override;
void restart(bool p_keep_seed = false);
void convert_from_particles(Node *p_particles);
AABB capture_aabb() const;
CPUParticles3D();
~CPUParticles3D();
};
VARIANT_ENUM_CAST(CPUParticles3D::DrawOrder)
VARIANT_ENUM_CAST(CPUParticles3D::Parameter)
VARIANT_ENUM_CAST(CPUParticles3D::ParticleFlags)
VARIANT_ENUM_CAST(CPUParticles3D::EmissionShape)

302
scene/3d/decal.cpp Normal file
View File

@@ -0,0 +1,302 @@
/**************************************************************************/
/* decal.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 "decal.h"
void Decal::set_size(const Vector3 &p_size) {
size = p_size.maxf(0.001);
RS::get_singleton()->decal_set_size(decal, size);
update_gizmos();
}
Vector3 Decal::get_size() const {
return size;
}
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();
}
Ref<Texture2D> Decal::get_texture(DecalTexture p_type) const {
ERR_FAIL_INDEX_V(p_type, TEXTURE_MAX, Ref<Texture2D>());
return textures[p_type];
}
void Decal::set_emission_energy(real_t p_energy) {
emission_energy = p_energy;
RS::get_singleton()->decal_set_emission_energy(decal, emission_energy);
}
real_t Decal::get_emission_energy() const {
return emission_energy;
}
void Decal::set_albedo_mix(real_t p_mix) {
albedo_mix = p_mix;
RS::get_singleton()->decal_set_albedo_mix(decal, albedo_mix);
}
real_t Decal::get_albedo_mix() const {
return albedo_mix;
}
void Decal::set_upper_fade(real_t p_fade) {
upper_fade = MAX(p_fade, 0.0);
RS::get_singleton()->decal_set_fade(decal, upper_fade, lower_fade);
}
real_t Decal::get_upper_fade() const {
return upper_fade;
}
void Decal::set_lower_fade(real_t p_fade) {
lower_fade = MAX(p_fade, 0.0);
RS::get_singleton()->decal_set_fade(decal, upper_fade, lower_fade);
}
real_t Decal::get_lower_fade() const {
return lower_fade;
}
void Decal::set_normal_fade(real_t p_fade) {
normal_fade = p_fade;
RS::get_singleton()->decal_set_normal_fade(decal, normal_fade);
}
real_t Decal::get_normal_fade() const {
return normal_fade;
}
void Decal::set_modulate(Color p_modulate) {
modulate = p_modulate;
RS::get_singleton()->decal_set_modulate(decal, p_modulate);
}
Color Decal::get_modulate() const {
return modulate;
}
void Decal::set_enable_distance_fade(bool p_enable) {
distance_fade_enabled = p_enable;
RS::get_singleton()->decal_set_distance_fade(decal, distance_fade_enabled, distance_fade_begin, distance_fade_length);
notify_property_list_changed();
}
bool Decal::is_distance_fade_enabled() const {
return distance_fade_enabled;
}
void Decal::set_distance_fade_begin(real_t p_distance) {
distance_fade_begin = p_distance;
RS::get_singleton()->decal_set_distance_fade(decal, distance_fade_enabled, distance_fade_begin, distance_fade_length);
}
real_t Decal::get_distance_fade_begin() const {
return distance_fade_begin;
}
void Decal::set_distance_fade_length(real_t p_length) {
distance_fade_length = p_length;
RS::get_singleton()->decal_set_distance_fade(decal, distance_fade_enabled, distance_fade_begin, distance_fade_length);
}
real_t Decal::get_distance_fade_length() const {
return distance_fade_length;
}
void Decal::set_cull_mask(uint32_t p_layers) {
cull_mask = p_layers;
RS::get_singleton()->decal_set_cull_mask(decal, cull_mask);
update_configuration_warnings();
}
uint32_t Decal::get_cull_mask() const {
return cull_mask;
}
AABB Decal::get_aabb() const {
AABB aabb;
aabb.position = -size / 2;
aabb.size = size;
return aabb;
}
void Decal::_validate_property(PropertyInfo &p_property) const {
if (Engine::get_singleton()->is_editor_hint() && !distance_fade_enabled && (p_property.name == "distance_fade_begin" || p_property.name == "distance_fade_length")) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
if (p_property.name == "sorting_offset") {
p_property.usage = PROPERTY_USAGE_DEFAULT;
}
}
PackedStringArray Decal::get_configuration_warnings() const {
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "dummy") {
warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile renderers."));
return warnings;
}
if (textures[TEXTURE_ALBEDO].is_null() && textures[TEXTURE_NORMAL].is_null() && textures[TEXTURE_ORM].is_null() && textures[TEXTURE_EMISSION].is_null()) {
warnings.push_back(RTR("The decal has no textures loaded into any of its texture properties, and will therefore not be visible."));
}
if ((textures[TEXTURE_NORMAL].is_valid() || textures[TEXTURE_ORM].is_valid()) && textures[TEXTURE_ALBEDO].is_null()) {
warnings.push_back(RTR("The decal has a Normal and/or ORM texture, but no Albedo texture is set.\nAn Albedo texture with an alpha channel is required to blend the normal/ORM maps onto the underlying surface.\nIf you don't want the Albedo texture to be visible, set Albedo Mix to 0."));
}
if (cull_mask == 0) {
warnings.push_back(RTR("The decal's Cull Mask has no bits enabled, which means the decal will not paint objects on any layer.\nTo resolve this, enable at least one bit in the Cull Mask property."));
}
return warnings;
}
void Decal::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_size", "size"), &Decal::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &Decal::get_size);
ClassDB::bind_method(D_METHOD("set_texture", "type", "texture"), &Decal::set_texture);
ClassDB::bind_method(D_METHOD("get_texture", "type"), &Decal::get_texture);
ClassDB::bind_method(D_METHOD("set_emission_energy", "energy"), &Decal::set_emission_energy);
ClassDB::bind_method(D_METHOD("get_emission_energy"), &Decal::get_emission_energy);
ClassDB::bind_method(D_METHOD("set_albedo_mix", "energy"), &Decal::set_albedo_mix);
ClassDB::bind_method(D_METHOD("get_albedo_mix"), &Decal::get_albedo_mix);
ClassDB::bind_method(D_METHOD("set_modulate", "color"), &Decal::set_modulate);
ClassDB::bind_method(D_METHOD("get_modulate"), &Decal::get_modulate);
ClassDB::bind_method(D_METHOD("set_upper_fade", "fade"), &Decal::set_upper_fade);
ClassDB::bind_method(D_METHOD("get_upper_fade"), &Decal::get_upper_fade);
ClassDB::bind_method(D_METHOD("set_lower_fade", "fade"), &Decal::set_lower_fade);
ClassDB::bind_method(D_METHOD("get_lower_fade"), &Decal::get_lower_fade);
ClassDB::bind_method(D_METHOD("set_normal_fade", "fade"), &Decal::set_normal_fade);
ClassDB::bind_method(D_METHOD("get_normal_fade"), &Decal::get_normal_fade);
ClassDB::bind_method(D_METHOD("set_enable_distance_fade", "enable"), &Decal::set_enable_distance_fade);
ClassDB::bind_method(D_METHOD("is_distance_fade_enabled"), &Decal::is_distance_fade_enabled);
ClassDB::bind_method(D_METHOD("set_distance_fade_begin", "distance"), &Decal::set_distance_fade_begin);
ClassDB::bind_method(D_METHOD("get_distance_fade_begin"), &Decal::get_distance_fade_begin);
ClassDB::bind_method(D_METHOD("set_distance_fade_length", "distance"), &Decal::set_distance_fade_length);
ClassDB::bind_method(D_METHOD("get_distance_fade_length"), &Decal::get_distance_fade_length);
ClassDB::bind_method(D_METHOD("set_cull_mask", "mask"), &Decal::set_cull_mask);
ClassDB::bind_method(D_METHOD("get_cull_mask"), &Decal::get_cull_mask);
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_");
// 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");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "albedo_mix", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_albedo_mix", "get_albedo_mix");
// A Normal Fade of 1.0 causes the decal to be invisible even if fully perpendicular to a surface.
// Due to this, limit Normal Fade to 0.999.
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "normal_fade", PROPERTY_HINT_RANGE, "0,0.999,0.001"), "set_normal_fade", "get_normal_fade");
ADD_GROUP("Vertical Fade", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "upper_fade", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_upper_fade", "get_upper_fade");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lower_fade", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_lower_fade", "get_lower_fade");
ADD_GROUP("Distance Fade", "distance_fade_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distance_fade_enabled"), "set_enable_distance_fade", "is_distance_fade_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_begin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_distance_fade_begin", "get_distance_fade_begin");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_length", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_distance_fade_length", "get_distance_fade_length");
ADD_GROUP("Cull Mask", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask");
BIND_ENUM_CONSTANT(TEXTURE_ALBEDO);
BIND_ENUM_CONSTANT(TEXTURE_NORMAL);
BIND_ENUM_CONSTANT(TEXTURE_ORM);
BIND_ENUM_CONSTANT(TEXTURE_EMISSION);
BIND_ENUM_CONSTANT(TEXTURE_MAX);
}
#ifndef DISABLE_DEPRECATED
bool Decal::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "extents") { // Compatibility with Godot 3.x.
set_size((Vector3)p_value * 2);
return true;
}
return false;
}
bool Decal::_get(const StringName &p_name, Variant &r_property) const {
if (p_name == "extents") { // Compatibility with Godot 3.x.
r_property = size / 2;
return true;
}
return false;
}
#endif // DISABLE_DEPRECATED
Decal::Decal() {
decal = RenderingServer::get_singleton()->decal_create();
RS::get_singleton()->instance_set_base(get_instance(), decal);
}
Decal::~Decal() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(decal);
}

115
scene/3d/decal.h Normal file
View File

@@ -0,0 +1,115 @@
/**************************************************************************/
/* decal.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. */
/**************************************************************************/
#pragma once
#include "scene/3d/visual_instance_3d.h"
class Decal : public VisualInstance3D {
GDCLASS(Decal, VisualInstance3D);
public:
enum DecalTexture {
TEXTURE_ALBEDO,
TEXTURE_NORMAL,
TEXTURE_ORM,
TEXTURE_EMISSION,
TEXTURE_MAX
};
private:
RID decal;
Vector3 size = Vector3(2, 2, 2);
Ref<Texture2D> textures[TEXTURE_MAX];
real_t emission_energy = 1.0;
real_t albedo_mix = 1.0;
Color modulate = Color(1, 1, 1, 1);
uint32_t cull_mask = (1 << 20) - 1;
real_t normal_fade = 0.0;
real_t upper_fade = 0.3;
real_t lower_fade = 0.3;
bool distance_fade_enabled = false;
real_t distance_fade_begin = 40.0;
real_t distance_fade_length = 10.0;
protected:
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_property) const;
#endif // DISABLE_DEPRECATED
public:
virtual PackedStringArray get_configuration_warnings() const override;
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
void set_texture(DecalTexture p_type, const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture(DecalTexture p_type) const;
void set_emission_energy(real_t p_energy);
real_t get_emission_energy() const;
void set_albedo_mix(real_t p_mix);
real_t get_albedo_mix() const;
void set_modulate(Color p_modulate);
Color get_modulate() const;
void set_upper_fade(real_t p_energy);
real_t get_upper_fade() const;
void set_lower_fade(real_t p_fade);
real_t get_lower_fade() const;
void set_normal_fade(real_t p_fade);
real_t get_normal_fade() const;
void set_enable_distance_fade(bool p_enable);
bool is_distance_fade_enabled() const;
void set_distance_fade_begin(real_t p_distance);
real_t get_distance_fade_begin() const;
void set_distance_fade_length(real_t p_length);
real_t get_distance_fade_length() const;
void set_cull_mask(uint32_t p_layers);
uint32_t get_cull_mask() const;
virtual AABB get_aabb() const override;
Decal();
~Decal();
};
VARIANT_ENUM_CAST(Decal::DecalTexture);

146
scene/3d/fog_volume.cpp Normal file
View File

@@ -0,0 +1,146 @@
/**************************************************************************/
/* fog_volume.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 "fog_volume.h"
#include "scene/main/viewport.h"
#include "scene/resources/environment.h"
///////////////////////////
void FogVolume::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_size", "size"), &FogVolume::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &FogVolume::get_size);
ClassDB::bind_method(D_METHOD("set_shape", "shape"), &FogVolume::set_shape);
ClassDB::bind_method(D_METHOD("get_shape"), &FogVolume::get_shape);
ClassDB::bind_method(D_METHOD("set_material", "material"), &FogVolume::set_material);
ClassDB::bind_method(D_METHOD("get_material"), &FogVolume::get_material);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::INT, "shape", PROPERTY_HINT_ENUM, "Ellipsoid (Local),Cone (Local),Cylinder (Local),Box (Local),World (Global)"), "set_shape", "get_shape");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "FogMaterial,ShaderMaterial"), "set_material", "get_material");
}
void FogVolume::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "size" && shape == RS::FOG_VOLUME_SHAPE_WORLD) {
p_property.usage = PROPERTY_USAGE_NONE;
return;
}
}
#ifndef DISABLE_DEPRECATED
bool FogVolume::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "extents") { // Compatibility with Godot 3.x.
set_size((Vector3)p_value * 2);
return true;
}
return false;
}
bool FogVolume::_get(const StringName &p_name, Variant &r_property) const {
if (p_name == "extents") { // Compatibility with Godot 3.x.
r_property = size / 2;
return true;
}
return false;
}
#endif // DISABLE_DEPRECATED
void FogVolume::set_size(const Vector3 &p_size) {
size = p_size;
size = size.maxf(0);
RS::get_singleton()->fog_volume_set_size(_get_volume(), size);
update_gizmos();
}
Vector3 FogVolume::get_size() const {
return size;
}
void FogVolume::set_shape(RS::FogVolumeShape p_type) {
shape = p_type;
RS::get_singleton()->fog_volume_set_shape(_get_volume(), shape);
RS::get_singleton()->instance_set_ignore_culling(get_instance(), shape == RS::FOG_VOLUME_SHAPE_WORLD);
update_gizmos();
notify_property_list_changed();
}
RS::FogVolumeShape FogVolume::get_shape() const {
return shape;
}
void FogVolume::set_material(const Ref<Material> &p_material) {
material = p_material;
RID material_rid;
if (material.is_valid()) {
material_rid = material->get_rid();
}
RS::get_singleton()->fog_volume_set_material(_get_volume(), material_rid);
update_gizmos();
}
Ref<Material> FogVolume::get_material() const {
return material;
}
AABB FogVolume::get_aabb() const {
if (shape != RS::FOG_VOLUME_SHAPE_WORLD) {
return AABB(-size / 2, size);
}
return AABB();
}
PackedStringArray FogVolume::get_configuration_warnings() const {
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+ renderer."));
return warnings;
}
if (environment.is_valid() && !environment->is_volumetric_fog_enabled()) {
warnings.push_back(RTR("Fog Volumes need volumetric fog to be enabled in the scene's Environment in order to be visible."));
}
return warnings;
}
FogVolume::FogVolume() {
volume = RS::get_singleton()->fog_volume_create();
RS::get_singleton()->fog_volume_set_shape(volume, RS::FOG_VOLUME_SHAPE_BOX);
set_base(volume);
}
FogVolume::~FogVolume() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(volume);
}

70
scene/3d/fog_volume.h Normal file
View File

@@ -0,0 +1,70 @@
/**************************************************************************/
/* fog_volume.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. */
/**************************************************************************/
#pragma once
#include "core/templates/rid.h"
#include "scene/3d/visual_instance_3d.h"
#include "scene/resources/material.h"
class FogVolume : public VisualInstance3D {
GDCLASS(FogVolume, VisualInstance3D);
Vector3 size = Vector3(2, 2, 2);
Ref<Material> material;
RS::FogVolumeShape shape = RS::FOG_VOLUME_SHAPE_BOX;
RID volume;
protected:
_FORCE_INLINE_ RID _get_volume() { return volume; }
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_property) const;
#endif // DISABLE_DEPRECATED
public:
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
void set_shape(RS::FogVolumeShape p_type);
RS::FogVolumeShape get_shape() const;
void set_material(const Ref<Material> &p_material);
Ref<Material> get_material() const;
virtual AABB get_aabb() const override;
PackedStringArray get_configuration_warnings() const override;
FogVolume();
~FogVolume();
};

View 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

View File

@@ -0,0 +1,908 @@
/**************************************************************************/
/* gpu_particles_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 "gpu_particles_3d.h"
#include "gpu_particles_3d.compat.inc"
#include "scene/3d/cpu_particles_3d.h"
#include "scene/resources/curve_texture.h"
#include "scene/resources/gradient_texture.h"
#include "scene/resources/particle_process_material.h"
AABB GPUParticles3D::get_aabb() const {
return AABB();
}
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 && one_shot) {
set_seed(Math::rand());
}
if (p_emitting && one_shot) {
if (!active && !emitting) {
// Last cycle ended.
active = true;
time = 0;
signal_canceled = false;
emission_time = lifetime;
active_time = lifetime * (2 - explosiveness_ratio);
} else {
signal_canceled = true;
}
set_process_internal(true);
} else if (!p_emitting) {
if (one_shot) {
set_process_internal(true);
} else {
set_process_internal(false);
}
} else {
set_process_internal(true);
}
emitting = p_emitting;
RS::get_singleton()->particles_set_emitting(particles, p_emitting);
}
void GPUParticles3D::set_amount(int p_amount) {
ERR_FAIL_COND_MSG(p_amount < 1, "Amount of particles cannot be smaller than 1.");
amount = p_amount;
RS::get_singleton()->particles_set_amount(particles, amount);
}
void GPUParticles3D::set_lifetime(double p_lifetime) {
ERR_FAIL_COND_MSG(p_lifetime <= 0, "Particles lifetime must be greater than 0.");
lifetime = p_lifetime;
RS::get_singleton()->particles_set_lifetime(particles, lifetime);
}
void GPUParticles3D::set_interp_to_end(float p_interp) {
interp_to_end_factor = CLAMP(p_interp, 0.0, 1.0);
RS::get_singleton()->particles_set_interp_to_end(particles, interp_to_end_factor);
}
void GPUParticles3D::set_one_shot(bool p_one_shot) {
one_shot = p_one_shot;
RS::get_singleton()->particles_set_one_shot(particles, one_shot);
if (is_emitting()) {
if (!one_shot) {
RenderingServer::get_singleton()->particles_restart(particles);
}
}
}
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);
}
void GPUParticles3D::set_explosiveness_ratio(real_t p_ratio) {
explosiveness_ratio = p_ratio;
RS::get_singleton()->particles_set_explosiveness_ratio(particles, explosiveness_ratio);
}
void GPUParticles3D::set_randomness_ratio(real_t p_ratio) {
randomness_ratio = p_ratio;
RS::get_singleton()->particles_set_randomness_ratio(particles, randomness_ratio);
}
void GPUParticles3D::set_visibility_aabb(const AABB &p_aabb) {
visibility_aabb = p_aabb;
RS::get_singleton()->particles_set_custom_aabb(particles, visibility_aabb);
update_gizmos();
}
void GPUParticles3D::set_use_local_coordinates(bool p_enable) {
local_coords = p_enable;
RS::get_singleton()->particles_set_use_local_coordinates(particles, local_coords);
}
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);
update_configuration_warnings();
}
void GPUParticles3D::set_speed_scale(double p_scale) {
speed_scale = p_scale;
RS::get_singleton()->particles_set_speed_scale(particles, p_scale);
}
void GPUParticles3D::set_collision_base_size(real_t p_size) {
collision_base_size = p_size;
RS::get_singleton()->particles_set_collision_base_size(particles, p_size);
}
bool GPUParticles3D::is_emitting() const {
return emitting;
}
int GPUParticles3D::get_amount() const {
return amount;
}
double GPUParticles3D::get_lifetime() const {
return lifetime;
}
float GPUParticles3D::get_interp_to_end() const {
return interp_to_end_factor;
}
bool GPUParticles3D::get_one_shot() const {
return one_shot;
}
double GPUParticles3D::get_pre_process_time() const {
return pre_process_time;
}
real_t GPUParticles3D::get_explosiveness_ratio() const {
return explosiveness_ratio;
}
real_t GPUParticles3D::get_randomness_ratio() const {
return randomness_ratio;
}
AABB GPUParticles3D::get_visibility_aabb() const {
return visibility_aabb;
}
bool GPUParticles3D::get_use_local_coordinates() const {
return local_coords;
}
Ref<Material> GPUParticles3D::get_process_material() const {
return process_material;
}
double GPUParticles3D::get_speed_scale() const {
return speed_scale;
}
real_t GPUParticles3D::get_collision_base_size() const {
return collision_base_size;
}
void GPUParticles3D::set_draw_order(DrawOrder p_order) {
draw_order = p_order;
RS::get_singleton()->particles_set_draw_order(particles, RS::ParticlesDrawOrder(p_order));
}
void GPUParticles3D::set_trail_enabled(bool p_enabled) {
trail_enabled = p_enabled;
RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime);
update_configuration_warnings();
}
void GPUParticles3D::set_trail_lifetime(double p_seconds) {
ERR_FAIL_COND(p_seconds < 0.01 - CMP_EPSILON);
trail_lifetime = p_seconds;
RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime);
}
bool GPUParticles3D::is_trail_enabled() const {
return trail_enabled;
}
double GPUParticles3D::get_trail_lifetime() const {
return trail_lifetime;
}
GPUParticles3D::DrawOrder GPUParticles3D::get_draw_order() const {
return draw_order;
}
void GPUParticles3D::set_draw_passes(int p_count) {
ERR_FAIL_COND(p_count < 1);
for (int i = p_count; i < draw_passes.size(); i++) {
set_draw_pass_mesh(i, Ref<Mesh>());
}
draw_passes.resize(p_count);
RS::get_singleton()->particles_set_draw_passes(particles, p_count);
notify_property_list_changed();
}
int GPUParticles3D::get_draw_passes() const {
return draw_passes.size();
}
void GPUParticles3D::set_draw_pass_mesh(int p_pass, const Ref<Mesh> &p_mesh) {
ERR_FAIL_INDEX(p_pass, draw_passes.size());
if (Engine::get_singleton()->is_editor_hint() && draw_passes.write[p_pass].is_valid()) {
draw_passes.write[p_pass]->disconnect_changed(callable_mp((Node *)this, &Node::update_configuration_warnings));
}
draw_passes.write[p_pass] = p_mesh;
if (Engine::get_singleton()->is_editor_hint() && draw_passes.write[p_pass].is_valid()) {
draw_passes.write[p_pass]->connect_changed(callable_mp((Node *)this, &Node::update_configuration_warnings), CONNECT_DEFERRED);
}
RID mesh_rid;
if (p_mesh.is_valid()) {
mesh_rid = p_mesh->get_rid();
}
RS::get_singleton()->particles_set_draw_pass_mesh(particles, p_pass, mesh_rid);
_skinning_changed();
update_configuration_warnings();
}
Ref<Mesh> GPUParticles3D::get_draw_pass_mesh(int p_pass) const {
ERR_FAIL_INDEX_V(p_pass, draw_passes.size(), Ref<Mesh>());
return draw_passes[p_pass];
}
void GPUParticles3D::set_fixed_fps(int p_count) {
fixed_fps = p_count;
RS::get_singleton()->particles_set_fixed_fps(particles, p_count);
}
int GPUParticles3D::get_fixed_fps() const {
return fixed_fps;
}
void GPUParticles3D::set_fractional_delta(bool p_enable) {
fractional_delta = p_enable;
RS::get_singleton()->particles_set_fractional_delta(particles, p_enable);
}
bool GPUParticles3D::get_fractional_delta() const {
return fractional_delta;
}
void GPUParticles3D::set_interpolate(bool p_enable) {
interpolate = p_enable;
RS::get_singleton()->particles_set_interpolate(particles, p_enable);
}
bool GPUParticles3D::get_interpolate() const {
return interpolate;
}
PackedStringArray GPUParticles3D::get_configuration_warnings() const {
PackedStringArray warnings = GeometryInstance3D::get_configuration_warnings();
bool meshes_found = false;
bool anim_material_found = false;
for (int i = 0; i < draw_passes.size(); i++) {
if (draw_passes[i].is_valid()) {
meshes_found = true;
for (int j = 0; j < draw_passes[i]->get_surface_count(); j++) {
anim_material_found = Object::cast_to<ShaderMaterial>(draw_passes[i]->surface_get_material(j).ptr()) != nullptr;
BaseMaterial3D *spat = Object::cast_to<BaseMaterial3D>(draw_passes[i]->surface_get_material(j).ptr());
anim_material_found = anim_material_found || (spat && spat->get_billboard_mode() == StandardMaterial3D::BILLBOARD_PARTICLES);
}
if (anim_material_found) {
break;
}
}
}
anim_material_found = anim_material_found || Object::cast_to<ShaderMaterial>(get_material_override().ptr()) != nullptr;
{
BaseMaterial3D *spat = Object::cast_to<BaseMaterial3D>(get_material_override().ptr());
anim_material_found = anim_material_found || (spat && spat->get_billboard_mode() == BaseMaterial3D::BILLBOARD_PARTICLES);
}
if (!meshes_found) {
warnings.push_back(RTR("Nothing is visible because meshes have not been assigned to draw passes."));
}
if (process_material.is_null()) {
warnings.push_back(RTR("A material to process the particles is not assigned, so no behavior is imprinted."));
} else {
const ParticleProcessMaterial *process = Object::cast_to<ParticleProcessMaterial>(process_material.ptr());
if (!anim_material_found && process &&
(process->get_param_max(ParticleProcessMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param_max(ParticleProcessMaterial::PARAM_ANIM_OFFSET) != 0.0 ||
process->get_param_texture(ParticleProcessMaterial::PARAM_ANIM_SPEED).is_valid() || process->get_param_texture(ParticleProcessMaterial::PARAM_ANIM_OFFSET).is_valid())) {
warnings.push_back(RTR("Particles animation requires the usage of a BaseMaterial3D whose Billboard Mode is set to \"Particle Billboard\"."));
}
}
if (trail_enabled) {
int dp_count = 0;
bool missing_trails = false;
bool no_materials = false;
for (int i = 0; i < draw_passes.size(); i++) {
Ref<Mesh> draw_pass = draw_passes[i];
if (draw_pass.is_valid() && draw_pass->get_builtin_bind_pose_count() > 0) {
dp_count++;
}
if (draw_pass.is_valid()) {
int mats_found = 0;
for (int j = 0; j < draw_passes[i]->get_surface_count(); j++) {
BaseMaterial3D *spat = Object::cast_to<BaseMaterial3D>(draw_passes[i]->surface_get_material(j).ptr());
if (spat) {
mats_found++;
}
if (spat && !spat->get_flag(BaseMaterial3D::FLAG_PARTICLE_TRAILS_MODE)) {
missing_trails = true;
}
}
if (mats_found != draw_passes[i]->get_surface_count()) {
no_materials = true;
}
}
}
BaseMaterial3D *spat = Object::cast_to<BaseMaterial3D>(get_material_override().ptr());
if (spat) {
no_materials = false;
}
if (spat && !spat->get_flag(BaseMaterial3D::FLAG_PARTICLE_TRAILS_MODE)) {
missing_trails = true;
}
if (dp_count && skin.is_valid()) {
warnings.push_back(RTR("Using Trail meshes with a skin causes Skin to override Trail poses. Suggest removing the Skin."));
} else if (dp_count == 0 && skin.is_null()) {
warnings.push_back(RTR("Trails active, but neither Trail meshes or a Skin were found."));
} else if (dp_count > 1) {
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_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" || OS::get_singleton()->get_current_rendering_method() == "dummy") {
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" || OS::get_singleton()->get_current_rendering_method() == "dummy")) {
warnings.push_back(RTR("Particle sub-emitters are only available when using the Forward+ or Mobile renderers."));
}
return warnings;
}
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);
emitting = true;
active = true;
signal_canceled = false;
time = 0;
emission_time = lifetime * (1 - explosiveness_ratio);
active_time = lifetime * (2 - explosiveness_ratio);
set_process_internal(true);
}
AABB GPUParticles3D::capture_aabb() const {
return RS::get_singleton()->particles_get_current_aabb(particles);
}
void GPUParticles3D::_validate_property(PropertyInfo &p_property) const {
if (Engine::get_singleton()->is_editor_hint() && 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()) {
p_property.usage = PROPERTY_USAGE_NONE;
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) {
RS::get_singleton()->particles_emit(particles, p_transform, p_velocity, p_color, p_custom, p_emit_flags);
}
void GPUParticles3D::_attach_sub_emitter() {
Node *n = get_node_or_null(sub_emitter);
if (n) {
GPUParticles3D *sen = Object::cast_to<GPUParticles3D>(n);
if (sen && sen != this) {
RS::get_singleton()->particles_set_subemitter(particles, sen->particles);
}
}
}
void GPUParticles3D::set_sub_emitter(const NodePath &p_path) {
if (is_inside_tree()) {
RS::get_singleton()->particles_set_subemitter(particles, RID());
}
sub_emitter = p_path;
if (is_inside_tree() && sub_emitter != NodePath()) {
_attach_sub_emitter();
}
update_configuration_warnings();
}
NodePath GPUParticles3D::get_sub_emitter() const {
return sub_emitter;
}
void GPUParticles3D::_notification(int p_what) {
switch (p_what) {
// Use internal process when emitting and one_shot is on so that when
// the shot ends the editor can properly update.
case NOTIFICATION_INTERNAL_PROCESS: {
const Vector3 velocity = (get_global_position() - previous_position) / get_process_delta_time();
if (velocity != previous_velocity) {
RS::get_singleton()->particles_set_emitter_velocity(particles, velocity);
previous_velocity = velocity;
}
previous_position = get_global_position();
if (one_shot) {
time += get_process_delta_time();
if (time > emission_time) {
emitting = false;
if (!active) {
set_process_internal(false);
}
}
if (time > active_time) {
if (active && !signal_canceled) {
emit_signal(SceneStringName(finished));
}
active = false;
if (!emitting) {
set_process_internal(false);
}
}
}
} 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();
}
if (can_process()) {
RS::get_singleton()->particles_set_speed_scale(particles, speed_scale);
} else {
RS::get_singleton()->particles_set_speed_scale(particles, 0);
}
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()) {
if (can_process()) {
RS::get_singleton()->particles_set_speed_scale(particles, speed_scale);
} else {
RS::get_singleton()->particles_set_speed_scale(particles, 0);
}
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
// Make sure particles are updated before rendering occurs if they were active before.
if (is_visible_in_tree() && !RS::get_singleton()->particles_is_inactive(particles)) {
RS::get_singleton()->particles_request_process(particles);
}
} break;
}
}
void GPUParticles3D::_skinning_changed() {
Vector<Transform3D> xforms;
if (skin.is_valid()) {
xforms.resize(skin->get_bind_count());
for (int i = 0; i < skin->get_bind_count(); i++) {
xforms.write[i] = skin->get_bind_pose(i);
}
} else {
for (int i = 0; i < draw_passes.size(); i++) {
Ref<Mesh> draw_pass = draw_passes[i];
if (draw_pass.is_valid() && draw_pass->get_builtin_bind_pose_count() > 0) {
xforms.resize(draw_pass->get_builtin_bind_pose_count());
for (int j = 0; j < draw_pass->get_builtin_bind_pose_count(); j++) {
xforms.write[j] = draw_pass->get_builtin_bind_pose(j);
}
break;
}
}
}
RS::get_singleton()->particles_set_trail_bind_poses(particles, xforms);
update_configuration_warnings();
}
void GPUParticles3D::set_skin(const Ref<Skin> &p_skin) {
skin = p_skin;
_skinning_changed();
}
Ref<Skin> GPUParticles3D::get_skin() const {
return skin;
}
void GPUParticles3D::set_transform_align(TransformAlign p_align) {
ERR_FAIL_INDEX(uint32_t(p_align), 4);
transform_align = p_align;
RS::get_singleton()->particles_set_transform_align(particles, RS::ParticlesTransformAlign(transform_align));
}
GPUParticles3D::TransformAlign GPUParticles3D::get_transform_align() const {
return transform_align;
}
void GPUParticles3D::convert_from_particles(Node *p_particles) {
CPUParticles3D *cpu_particles = Object::cast_to<CPUParticles3D>(p_particles);
ERR_FAIL_NULL_MSG(cpu_particles, "Only CPUParticles3D nodes can be converted to GPUParticles3D.");
set_emitting(cpu_particles->is_emitting());
set_amount(cpu_particles->get_amount());
set_lifetime(cpu_particles->get_lifetime());
set_one_shot(cpu_particles->get_one_shot());
set_pre_process_time(cpu_particles->get_pre_process_time());
set_explosiveness_ratio(cpu_particles->get_explosiveness_ratio());
set_randomness_ratio(cpu_particles->get_randomness_ratio());
set_use_local_coordinates(cpu_particles->get_use_local_coordinates());
set_fixed_fps(cpu_particles->get_fixed_fps());
set_fractional_delta(cpu_particles->get_fractional_delta());
set_speed_scale(cpu_particles->get_speed_scale());
set_draw_order(DrawOrder(cpu_particles->get_draw_order()));
set_draw_pass_mesh(0, cpu_particles->get_mesh());
Ref<ParticleProcessMaterial> proc_mat = memnew(ParticleProcessMaterial);
set_process_material(proc_mat);
proc_mat->set_direction(cpu_particles->get_direction());
proc_mat->set_spread(cpu_particles->get_spread());
proc_mat->set_flatness(cpu_particles->get_flatness());
proc_mat->set_color(cpu_particles->get_color());
Ref<Gradient> grad = cpu_particles->get_color_ramp();
if (grad.is_valid()) {
Ref<GradientTexture1D> tex = memnew(GradientTexture1D);
tex->set_gradient(grad);
proc_mat->set_color_ramp(tex);
}
Ref<Gradient> grad_init = cpu_particles->get_color_initial_ramp();
if (grad_init.is_valid()) {
Ref<GradientTexture1D> tex = memnew(GradientTexture1D);
tex->set_gradient(grad_init);
proc_mat->set_color_initial_ramp(tex);
}
proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, cpu_particles->get_particle_flag(CPUParticles3D::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY));
proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ROTATE_Y, cpu_particles->get_particle_flag(CPUParticles3D::PARTICLE_FLAG_ROTATE_Y));
proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_DISABLE_Z, cpu_particles->get_particle_flag(CPUParticles3D::PARTICLE_FLAG_DISABLE_Z));
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);
scale3D->set_curve_x(cpu_particles->get_scale_curve_x());
scale3D->set_curve_y(cpu_particles->get_scale_curve_y());
scale3D->set_curve_z(cpu_particles->get_scale_curve_z());
proc_mat->set_param_texture(ParticleProcessMaterial::PARAM_SCALE, scale3D);
}
proc_mat->set_gravity(cpu_particles->get_gravity());
proc_mat->set_lifetime_randomness(cpu_particles->get_lifetime_randomness());
#define CONVERT_PARAM(m_param) \
proc_mat->set_param_min(ParticleProcessMaterial::m_param, cpu_particles->get_param_min(CPUParticles3D::m_param)); \
{ \
Ref<Curve> curve = cpu_particles->get_param_curve(CPUParticles3D::m_param); \
if (curve.is_valid()) { \
Ref<CurveTexture> tex = memnew(CurveTexture); \
tex->set_curve(curve); \
proc_mat->set_param_texture(ParticleProcessMaterial::m_param, tex); \
} \
} \
proc_mat->set_param_max(ParticleProcessMaterial::m_param, cpu_particles->get_param_max(CPUParticles3D::m_param));
CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY);
CONVERT_PARAM(PARAM_ANGULAR_VELOCITY);
CONVERT_PARAM(PARAM_ORBIT_VELOCITY);
CONVERT_PARAM(PARAM_LINEAR_ACCEL);
CONVERT_PARAM(PARAM_RADIAL_ACCEL);
CONVERT_PARAM(PARAM_TANGENTIAL_ACCEL);
CONVERT_PARAM(PARAM_DAMPING);
CONVERT_PARAM(PARAM_ANGLE);
CONVERT_PARAM(PARAM_SCALE);
CONVERT_PARAM(PARAM_HUE_VARIATION);
CONVERT_PARAM(PARAM_ANIM_SPEED);
CONVERT_PARAM(PARAM_ANIM_OFFSET);
#undef CONVERT_PARAM
}
void GPUParticles3D::set_amount_ratio(float p_ratio) {
amount_ratio = p_ratio;
RS::get_singleton()->particles_set_amount_ratio(particles, p_ratio);
}
float GPUParticles3D::get_amount_ratio() const {
return amount_ratio;
}
void GPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emitting", "emitting"), &GPUParticles3D::set_emitting);
ClassDB::bind_method(D_METHOD("set_amount", "amount"), &GPUParticles3D::set_amount);
ClassDB::bind_method(D_METHOD("set_lifetime", "secs"), &GPUParticles3D::set_lifetime);
ClassDB::bind_method(D_METHOD("set_one_shot", "enable"), &GPUParticles3D::set_one_shot);
ClassDB::bind_method(D_METHOD("set_pre_process_time", "secs"), &GPUParticles3D::set_pre_process_time);
ClassDB::bind_method(D_METHOD("set_explosiveness_ratio", "ratio"), &GPUParticles3D::set_explosiveness_ratio);
ClassDB::bind_method(D_METHOD("set_randomness_ratio", "ratio"), &GPUParticles3D::set_randomness_ratio);
ClassDB::bind_method(D_METHOD("set_visibility_aabb", "aabb"), &GPUParticles3D::set_visibility_aabb);
ClassDB::bind_method(D_METHOD("set_use_local_coordinates", "enable"), &GPUParticles3D::set_use_local_coordinates);
ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &GPUParticles3D::set_fixed_fps);
ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &GPUParticles3D::set_fractional_delta);
ClassDB::bind_method(D_METHOD("set_interpolate", "enable"), &GPUParticles3D::set_interpolate);
ClassDB::bind_method(D_METHOD("set_process_material", "material"), &GPUParticles3D::set_process_material);
ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &GPUParticles3D::set_speed_scale);
ClassDB::bind_method(D_METHOD("set_collision_base_size", "size"), &GPUParticles3D::set_collision_base_size);
ClassDB::bind_method(D_METHOD("set_interp_to_end", "interp"), &GPUParticles3D::set_interp_to_end);
ClassDB::bind_method(D_METHOD("is_emitting"), &GPUParticles3D::is_emitting);
ClassDB::bind_method(D_METHOD("get_amount"), &GPUParticles3D::get_amount);
ClassDB::bind_method(D_METHOD("get_lifetime"), &GPUParticles3D::get_lifetime);
ClassDB::bind_method(D_METHOD("get_one_shot"), &GPUParticles3D::get_one_shot);
ClassDB::bind_method(D_METHOD("get_pre_process_time"), &GPUParticles3D::get_pre_process_time);
ClassDB::bind_method(D_METHOD("get_explosiveness_ratio"), &GPUParticles3D::get_explosiveness_ratio);
ClassDB::bind_method(D_METHOD("get_randomness_ratio"), &GPUParticles3D::get_randomness_ratio);
ClassDB::bind_method(D_METHOD("get_visibility_aabb"), &GPUParticles3D::get_visibility_aabb);
ClassDB::bind_method(D_METHOD("get_use_local_coordinates"), &GPUParticles3D::get_use_local_coordinates);
ClassDB::bind_method(D_METHOD("get_fixed_fps"), &GPUParticles3D::get_fixed_fps);
ClassDB::bind_method(D_METHOD("get_fractional_delta"), &GPUParticles3D::get_fractional_delta);
ClassDB::bind_method(D_METHOD("get_interpolate"), &GPUParticles3D::get_interpolate);
ClassDB::bind_method(D_METHOD("get_process_material"), &GPUParticles3D::get_process_material);
ClassDB::bind_method(D_METHOD("get_speed_scale"), &GPUParticles3D::get_speed_scale);
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);
ClassDB::bind_method(D_METHOD("set_draw_passes", "passes"), &GPUParticles3D::set_draw_passes);
ClassDB::bind_method(D_METHOD("set_draw_pass_mesh", "pass", "mesh"), &GPUParticles3D::set_draw_pass_mesh);
ClassDB::bind_method(D_METHOD("get_draw_passes"), &GPUParticles3D::get_draw_passes);
ClassDB::bind_method(D_METHOD("get_draw_pass_mesh", "pass"), &GPUParticles3D::get_draw_pass_mesh);
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", "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);
ClassDB::bind_method(D_METHOD("get_sub_emitter"), &GPUParticles3D::get_sub_emitter);
ClassDB::bind_method(D_METHOD("emit_particle", "xform", "velocity", "color", "custom", "flags"), &GPUParticles3D::emit_particle);
ClassDB::bind_method(D_METHOD("set_trail_enabled", "enabled"), &GPUParticles3D::set_trail_enabled);
ClassDB::bind_method(D_METHOD("set_trail_lifetime", "secs"), &GPUParticles3D::set_trail_lifetime);
ClassDB::bind_method(D_METHOD("is_trail_enabled"), &GPUParticles3D::is_trail_enabled);
ClassDB::bind_method(D_METHOD("get_trail_lifetime"), &GPUParticles3D::get_trail_lifetime);
ClassDB::bind_method(D_METHOD("set_transform_align", "align"), &GPUParticles3D::set_transform_align);
ClassDB::bind_method(D_METHOD("get_transform_align"), &GPUParticles3D::get_transform_align);
ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &GPUParticles3D::convert_from_particles);
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", 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"); // 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,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", "");
ADD_PROPERTY(PropertyInfo(Variant::AABB, "visibility_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_visibility_aabb", "get_visibility_aabb");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates");
ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,Reverse Lifetime,View Depth"), "set_draw_order", "get_draw_order");
ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_align", PROPERTY_HINT_ENUM, "Disabled,Z-Billboard,Y to Velocity,Z-Billboard + Y to Velocity"), "set_transform_align", "get_transform_align");
ADD_GROUP("Trails", "trail_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "trail_enabled", PROPERTY_HINT_GROUP_ENABLE), "set_trail_enabled", "is_trail_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_lifetime", PROPERTY_HINT_RANGE, "0.01,10,0.01,or_greater,suffix:s"), "set_trail_lifetime", "get_trail_lifetime");
ADD_GROUP("Process Material", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ParticleProcessMaterial,ShaderMaterial"), "set_process_material", "get_process_material");
ADD_GROUP("Draw Passes", "draw_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_passes", PROPERTY_HINT_RANGE, "0," + itos(MAX_DRAW_PASSES) + ",1"), "set_draw_passes", "get_draw_passes");
for (int i = 0; i < MAX_DRAW_PASSES; i++) {
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "draw_pass_" + itos(i + 1), PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_draw_pass_mesh", "get_draw_pass_mesh", i);
}
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "draw_skin", PROPERTY_HINT_RESOURCE_TYPE, "Skin"), "set_skin", "get_skin");
BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX);
BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME);
BIND_ENUM_CONSTANT(DRAW_ORDER_REVERSE_LIFETIME);
BIND_ENUM_CONSTANT(DRAW_ORDER_VIEW_DEPTH);
BIND_ENUM_CONSTANT(EMIT_FLAG_POSITION);
BIND_ENUM_CONSTANT(EMIT_FLAG_ROTATION_SCALE);
BIND_ENUM_CONSTANT(EMIT_FLAG_VELOCITY);
BIND_ENUM_CONSTANT(EMIT_FLAG_COLOR);
BIND_ENUM_CONSTANT(EMIT_FLAG_CUSTOM);
BIND_CONSTANT(MAX_DRAW_PASSES);
BIND_ENUM_CONSTANT(TRANSFORM_ALIGN_DISABLED);
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() {
particles = RS::get_singleton()->particles_create();
RS::get_singleton()->particles_set_mode(particles, RS::PARTICLES_MODE_3D);
set_base(particles);
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);
set_fixed_fps(30);
set_fractional_delta(true);
set_interpolate(true);
set_pre_process_time(0);
set_explosiveness_ratio(0);
set_randomness_ratio(0);
set_trail_lifetime(0.3);
set_visibility_aabb(AABB(Vector3(-4, -4, -4), Vector3(8, 8, 8)));
set_use_local_coordinates(false);
set_draw_passes(1);
set_draw_order(DRAW_ORDER_INDEX);
set_speed_scale(1);
set_collision_base_size(collision_base_size);
set_transform_align(TRANSFORM_ALIGN_DISABLED);
set_use_fixed_seed(false);
}
GPUParticles3D::~GPUParticles3D() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(particles);
}

212
scene/3d/gpu_particles_3d.h Normal file
View File

@@ -0,0 +1,212 @@
/**************************************************************************/
/* gpu_particles_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/visual_instance_3d.h"
#include "scene/resources/3d/skin.h"
class GPUParticles3D : public GeometryInstance3D {
private:
GDCLASS(GPUParticles3D, GeometryInstance3D);
public:
enum DrawOrder {
DRAW_ORDER_INDEX,
DRAW_ORDER_LIFETIME,
DRAW_ORDER_REVERSE_LIFETIME,
DRAW_ORDER_VIEW_DEPTH,
};
enum TransformAlign {
TRANSFORM_ALIGN_DISABLED,
TRANSFORM_ALIGN_Z_BILLBOARD,
TRANSFORM_ALIGN_Y_TO_VELOCITY,
TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY
};
enum {
MAX_DRAW_PASSES = 4
};
private:
RID particles;
bool emitting = false;
bool active = false;
bool signal_canceled = false;
bool one_shot = false;
int amount = 0;
float amount_ratio = 1.0;
double lifetime = 0.0;
double pre_process_time = 0.0;
real_t explosiveness_ratio = 0.0;
real_t randomness_ratio = 0.0;
double speed_scale = 0.0;
AABB visibility_aabb;
bool local_coords = false;
int fixed_fps = 0;
bool fractional_delta = false;
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;
TransformAlign transform_align = TRANSFORM_ALIGN_DISABLED;
Ref<Material> process_material;
DrawOrder draw_order = DRAW_ORDER_INDEX;
Vector<Ref<Mesh>> draw_passes;
Ref<Skin> skin;
double time = 0.0;
double emission_time = 0.0;
double active_time = 0.0;
float interp_to_end_factor = 0;
Vector3 previous_velocity;
Vector3 previous_position;
void _attach_sub_emitter();
void _skinning_changed();
protected:
static void _bind_methods();
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;
void set_emitting(bool p_emitting);
void set_amount(int p_amount);
void set_lifetime(double p_lifetime);
void set_one_shot(bool p_one_shot);
void set_pre_process_time(double p_time);
void set_explosiveness_ratio(real_t p_ratio);
void set_randomness_ratio(real_t p_ratio);
void set_visibility_aabb(const AABB &p_aabb);
void set_use_local_coordinates(bool p_enable);
void set_process_material(const Ref<Material> &p_material);
void set_speed_scale(double p_scale);
void set_collision_base_size(real_t p_ratio);
void set_trail_enabled(bool p_enabled);
void set_trail_lifetime(double p_seconds);
void set_interp_to_end(float p_interp);
bool is_emitting() const;
int get_amount() const;
double get_lifetime() const;
bool get_one_shot() const;
double get_pre_process_time() const;
real_t get_explosiveness_ratio() const;
real_t get_randomness_ratio() const;
AABB get_visibility_aabb() const;
bool get_use_local_coordinates() const;
Ref<Material> get_process_material() const;
double get_speed_scale() const;
real_t get_collision_base_size() const;
bool is_trail_enabled() const;
double get_trail_lifetime() const;
float get_interp_to_end() const;
void set_amount_ratio(float p_ratio);
float get_amount_ratio() const;
void set_fixed_fps(int p_count);
int get_fixed_fps() const;
void set_fractional_delta(bool p_enable);
bool get_fractional_delta() const;
void set_interpolate(bool p_enable);
bool get_interpolate() const;
void set_draw_order(DrawOrder p_order);
DrawOrder get_draw_order() const;
void set_draw_passes(int p_count);
int get_draw_passes() const;
void set_draw_pass_mesh(int p_pass, const Ref<Mesh> &p_mesh);
Ref<Mesh> get_draw_pass_mesh(int p_pass) const;
PackedStringArray get_configuration_warnings() const override;
void set_sub_emitter(const NodePath &p_path);
NodePath get_sub_emitter() const;
void set_skin(const Ref<Skin> &p_skin);
Ref<Skin> get_skin() const;
void set_transform_align(TransformAlign p_align);
TransformAlign get_transform_align() const;
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,
EMIT_FLAG_ROTATION_SCALE = RS::PARTICLES_EMIT_FLAG_ROTATION_SCALE,
EMIT_FLAG_VELOCITY = RS::PARTICLES_EMIT_FLAG_VELOCITY,
EMIT_FLAG_COLOR = RS::PARTICLES_EMIT_FLAG_COLOR,
EMIT_FLAG_CUSTOM = RS::PARTICLES_EMIT_FLAG_CUSTOM
};
void emit_particle(const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags);
AABB capture_aabb() const;
void convert_from_particles(Node *p_particles);
GPUParticles3D();
~GPUParticles3D();
};
VARIANT_ENUM_CAST(GPUParticles3D::DrawOrder)
VARIANT_ENUM_CAST(GPUParticles3D::TransformAlign)
VARIANT_ENUM_CAST(GPUParticles3D::EmitFlags)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,365 @@
/**************************************************************************/
/* gpu_particles_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. */
/**************************************************************************/
#pragma once
#include "core/templates/local_vector.h"
#include "scene/3d/visual_instance_3d.h"
class GPUParticlesCollision3D : public VisualInstance3D {
GDCLASS(GPUParticlesCollision3D, VisualInstance3D);
uint32_t cull_mask = 0xFFFFFFFF;
RID collision;
protected:
_FORCE_INLINE_ RID _get_collision() { return collision; }
static void _bind_methods();
GPUParticlesCollision3D(RS::ParticlesCollisionType p_type);
public:
void set_cull_mask(uint32_t p_cull_mask);
uint32_t get_cull_mask() const;
~GPUParticlesCollision3D();
};
class GPUParticlesCollisionSphere3D : public GPUParticlesCollision3D {
GDCLASS(GPUParticlesCollisionSphere3D, GPUParticlesCollision3D);
real_t radius = 1.0;
protected:
static void _bind_methods();
public:
void set_radius(real_t p_radius);
real_t get_radius() const;
virtual AABB get_aabb() const override;
GPUParticlesCollisionSphere3D();
~GPUParticlesCollisionSphere3D();
};
class GPUParticlesCollisionBox3D : public GPUParticlesCollision3D {
GDCLASS(GPUParticlesCollisionBox3D, GPUParticlesCollision3D);
Vector3 size = Vector3(2, 2, 2);
protected:
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_property) const;
#endif // DISABLE_DEPRECATED
public:
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
virtual AABB get_aabb() const override;
GPUParticlesCollisionBox3D();
~GPUParticlesCollisionBox3D();
};
class GPUParticlesCollisionSDF3D : public GPUParticlesCollision3D {
GDCLASS(GPUParticlesCollisionSDF3D, GPUParticlesCollision3D);
public:
enum Resolution {
RESOLUTION_16,
RESOLUTION_32,
RESOLUTION_64,
RESOLUTION_128,
RESOLUTION_256,
RESOLUTION_512,
RESOLUTION_MAX,
};
typedef void (*BakeBeginFunc)(int);
typedef void (*BakeStepFunc)(int, const String &);
typedef void (*BakeEndFunc)();
private:
Vector3 size = Vector3(2, 2, 2);
Resolution resolution = RESOLUTION_64;
uint32_t bake_mask = 0xFFFFFFFF;
Ref<Texture3D> texture;
float thickness = 1.0;
struct PlotMesh {
Ref<Mesh> mesh;
Transform3D local_xform;
};
void _find_meshes(const AABB &p_aabb, Node *p_at_node, List<PlotMesh> &plot_meshes);
struct BVH {
enum {
LEAF_BIT = 1 << 30,
LEAF_MASK = LEAF_BIT - 1
};
AABB bounds;
uint32_t children[2] = {};
};
struct FacePos {
Vector3 center;
uint32_t index = 0;
};
struct FaceSort {
uint32_t axis = 0;
bool operator()(const FacePos &p_left, const FacePos &p_right) const {
return p_left.center[axis] < p_right.center[axis];
}
};
uint32_t _create_bvh(LocalVector<BVH> &bvh_tree, FacePos *p_faces, uint32_t p_face_count, const Face3 *p_triangles, float p_thickness);
struct ComputeSDFParams {
float *cells = nullptr;
Vector3i size;
float cell_size = 0.0;
Vector3 cell_offset;
const BVH *bvh = nullptr;
const Face3 *triangles = nullptr;
float thickness = 0.0;
};
void _find_closest_distance(const Vector3 &p_pos, const BVH *p_bvh, uint32_t p_bvh_cell, const Face3 *p_triangles, float p_thickness, float &r_closest_distance);
void _compute_sdf_z(uint32_t p_z, ComputeSDFParams *params);
void _compute_sdf(ComputeSDFParams *params);
protected:
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_property) const;
#endif // DISABLE_DEPRECATED
public:
virtual PackedStringArray get_configuration_warnings() const override;
void set_thickness(float p_thickness);
float get_thickness() const;
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
void set_resolution(Resolution p_resolution);
Resolution get_resolution() const;
void set_bake_mask(uint32_t p_mask);
uint32_t get_bake_mask() const;
void set_bake_mask_value(int p_layer_number, bool p_enable);
bool get_bake_mask_value(int p_layer_number) const;
void set_texture(const Ref<Texture3D> &p_texture);
Ref<Texture3D> get_texture() const;
Vector3i get_estimated_cell_size() const;
Ref<Image> bake();
virtual AABB get_aabb() const override;
static BakeBeginFunc bake_begin_function;
static BakeStepFunc bake_step_function;
static BakeEndFunc bake_end_function;
GPUParticlesCollisionSDF3D();
~GPUParticlesCollisionSDF3D();
};
VARIANT_ENUM_CAST(GPUParticlesCollisionSDF3D::Resolution)
class GPUParticlesCollisionHeightField3D : public GPUParticlesCollision3D {
GDCLASS(GPUParticlesCollisionHeightField3D, GPUParticlesCollision3D);
public:
enum Resolution {
RESOLUTION_256,
RESOLUTION_512,
RESOLUTION_1024,
RESOLUTION_2048,
RESOLUTION_4096,
RESOLUTION_8192,
RESOLUTION_MAX,
};
enum UpdateMode {
UPDATE_MODE_WHEN_MOVED,
UPDATE_MODE_ALWAYS,
};
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;
UpdateMode update_mode = UPDATE_MODE_WHEN_MOVED;
protected:
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_property) const;
#endif // DISABLE_DEPRECATED
public:
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
void set_resolution(Resolution p_resolution);
Resolution get_resolution() const;
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;
virtual AABB get_aabb() const override;
GPUParticlesCollisionHeightField3D();
~GPUParticlesCollisionHeightField3D();
};
VARIANT_ENUM_CAST(GPUParticlesCollisionHeightField3D::Resolution)
VARIANT_ENUM_CAST(GPUParticlesCollisionHeightField3D::UpdateMode)
class GPUParticlesAttractor3D : public VisualInstance3D {
GDCLASS(GPUParticlesAttractor3D, VisualInstance3D);
uint32_t cull_mask = 0xFFFFFFFF;
RID collision;
real_t strength = 1.0;
real_t attenuation = 1.0;
real_t directionality = 0.0;
protected:
_FORCE_INLINE_ RID _get_collision() { return collision; }
static void _bind_methods();
GPUParticlesAttractor3D(RS::ParticlesCollisionType p_type);
public:
void set_cull_mask(uint32_t p_cull_mask);
uint32_t get_cull_mask() const;
void set_strength(real_t p_strength);
real_t get_strength() const;
void set_attenuation(real_t p_attenuation);
real_t get_attenuation() const;
void set_directionality(real_t p_directionality);
real_t get_directionality() const;
~GPUParticlesAttractor3D();
};
class GPUParticlesAttractorSphere3D : public GPUParticlesAttractor3D {
GDCLASS(GPUParticlesAttractorSphere3D, GPUParticlesAttractor3D);
real_t radius = 1.0;
protected:
static void _bind_methods();
public:
void set_radius(real_t p_radius);
real_t get_radius() const;
virtual AABB get_aabb() const override;
GPUParticlesAttractorSphere3D();
~GPUParticlesAttractorSphere3D();
};
class GPUParticlesAttractorBox3D : public GPUParticlesAttractor3D {
GDCLASS(GPUParticlesAttractorBox3D, GPUParticlesAttractor3D);
Vector3 size = Vector3(2, 2, 2);
protected:
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_property) const;
#endif // DISABLE_DEPRECATED
public:
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
virtual AABB get_aabb() const override;
GPUParticlesAttractorBox3D();
~GPUParticlesAttractorBox3D();
};
class GPUParticlesAttractorVectorField3D : public GPUParticlesAttractor3D {
GDCLASS(GPUParticlesAttractorVectorField3D, GPUParticlesAttractor3D);
Vector3 size = Vector3(2, 2, 2);
Ref<Texture3D> texture;
protected:
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_property) const;
#endif // DISABLE_DEPRECATED
public:
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
void set_texture(const Ref<Texture3D> &p_texture);
Ref<Texture3D> get_texture() const;
virtual AABB get_aabb() const override;
GPUParticlesAttractorVectorField3D();
~GPUParticlesAttractorVectorField3D();
};

View File

@@ -0,0 +1,177 @@
/**************************************************************************/
/* importer_mesh_instance_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 "importer_mesh_instance_3d.h"
#include "scene/resources/3d/importer_mesh.h"
void ImporterMeshInstance3D::set_mesh(const Ref<ImporterMesh> &p_mesh) {
mesh = p_mesh;
}
Ref<ImporterMesh> ImporterMeshInstance3D::get_mesh() const {
return mesh;
}
void ImporterMeshInstance3D::set_skin(const Ref<Skin> &p_skin) {
skin = p_skin;
}
Ref<Skin> ImporterMeshInstance3D::get_skin() const {
return skin;
}
void ImporterMeshInstance3D::set_surface_material(int p_idx, const Ref<Material> &p_material) {
ERR_FAIL_COND(p_idx < 0);
if (p_idx >= surface_materials.size()) {
surface_materials.resize(p_idx + 1);
}
surface_materials.write[p_idx] = p_material;
}
Ref<Material> ImporterMeshInstance3D::get_surface_material(int p_idx) const {
ERR_FAIL_COND_V(p_idx < 0, Ref<Material>());
if (p_idx >= surface_materials.size()) {
return Ref<Material>();
}
return surface_materials[p_idx];
}
void ImporterMeshInstance3D::set_skeleton_path(const NodePath &p_path) {
skeleton_path = p_path;
}
NodePath ImporterMeshInstance3D::get_skeleton_path() const {
return skeleton_path;
}
uint32_t ImporterMeshInstance3D::get_layer_mask() const {
return layer_mask;
}
void ImporterMeshInstance3D::set_layer_mask(const uint32_t p_layer_mask) {
layer_mask = p_layer_mask;
}
void ImporterMeshInstance3D::set_cast_shadows_setting(GeometryInstance3D::ShadowCastingSetting p_shadow_casting_setting) {
shadow_casting_setting = p_shadow_casting_setting;
}
GeometryInstance3D::ShadowCastingSetting ImporterMeshInstance3D::get_cast_shadows_setting() const {
return shadow_casting_setting;
}
void ImporterMeshInstance3D::set_visibility_range_begin(float p_dist) {
visibility_range_begin = p_dist;
update_configuration_warnings();
}
float ImporterMeshInstance3D::get_visibility_range_begin() const {
return visibility_range_begin;
}
void ImporterMeshInstance3D::set_visibility_range_end(float p_dist) {
visibility_range_end = p_dist;
update_configuration_warnings();
}
float ImporterMeshInstance3D::get_visibility_range_end() const {
return visibility_range_end;
}
void ImporterMeshInstance3D::set_visibility_range_begin_margin(float p_dist) {
visibility_range_begin_margin = p_dist;
update_configuration_warnings();
}
float ImporterMeshInstance3D::get_visibility_range_begin_margin() const {
return visibility_range_begin_margin;
}
void ImporterMeshInstance3D::set_visibility_range_end_margin(float p_dist) {
visibility_range_end_margin = p_dist;
update_configuration_warnings();
}
float ImporterMeshInstance3D::get_visibility_range_end_margin() const {
return visibility_range_end_margin;
}
void ImporterMeshInstance3D::set_visibility_range_fade_mode(GeometryInstance3D::VisibilityRangeFadeMode p_mode) {
visibility_range_fade_mode = p_mode;
update_configuration_warnings();
}
GeometryInstance3D::VisibilityRangeFadeMode ImporterMeshInstance3D::get_visibility_range_fade_mode() const {
return visibility_range_fade_mode;
}
void ImporterMeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &ImporterMeshInstance3D::set_mesh);
ClassDB::bind_method(D_METHOD("get_mesh"), &ImporterMeshInstance3D::get_mesh);
ClassDB::bind_method(D_METHOD("set_skin", "skin"), &ImporterMeshInstance3D::set_skin);
ClassDB::bind_method(D_METHOD("get_skin"), &ImporterMeshInstance3D::get_skin);
ClassDB::bind_method(D_METHOD("set_skeleton_path", "skeleton_path"), &ImporterMeshInstance3D::set_skeleton_path);
ClassDB::bind_method(D_METHOD("get_skeleton_path"), &ImporterMeshInstance3D::get_skeleton_path);
ClassDB::bind_method(D_METHOD("set_layer_mask", "layer_mask"), &ImporterMeshInstance3D::set_layer_mask);
ClassDB::bind_method(D_METHOD("get_layer_mask"), &ImporterMeshInstance3D::get_layer_mask);
ClassDB::bind_method(D_METHOD("set_cast_shadows_setting", "shadow_casting_setting"), &ImporterMeshInstance3D::set_cast_shadows_setting);
ClassDB::bind_method(D_METHOD("get_cast_shadows_setting"), &ImporterMeshInstance3D::get_cast_shadows_setting);
ClassDB::bind_method(D_METHOD("set_visibility_range_end_margin", "distance"), &ImporterMeshInstance3D::set_visibility_range_end_margin);
ClassDB::bind_method(D_METHOD("get_visibility_range_end_margin"), &ImporterMeshInstance3D::get_visibility_range_end_margin);
ClassDB::bind_method(D_METHOD("set_visibility_range_end", "distance"), &ImporterMeshInstance3D::set_visibility_range_end);
ClassDB::bind_method(D_METHOD("get_visibility_range_end"), &ImporterMeshInstance3D::get_visibility_range_end);
ClassDB::bind_method(D_METHOD("set_visibility_range_begin_margin", "distance"), &ImporterMeshInstance3D::set_visibility_range_begin_margin);
ClassDB::bind_method(D_METHOD("get_visibility_range_begin_margin"), &ImporterMeshInstance3D::get_visibility_range_begin_margin);
ClassDB::bind_method(D_METHOD("set_visibility_range_begin", "distance"), &ImporterMeshInstance3D::set_visibility_range_begin);
ClassDB::bind_method(D_METHOD("get_visibility_range_begin"), &ImporterMeshInstance3D::get_visibility_range_begin);
ClassDB::bind_method(D_METHOD("set_visibility_range_fade_mode", "mode"), &ImporterMeshInstance3D::set_visibility_range_fade_mode);
ClassDB::bind_method(D_METHOD("get_visibility_range_fade_mode"), &ImporterMeshInstance3D::get_visibility_range_fade_mode);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "ImporterMesh"), "set_mesh", "get_mesh");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skin", PROPERTY_HINT_RESOURCE_TYPE, "Skin"), "set_skin", "get_skin");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton"), "set_skeleton_path", "get_skeleton_path");
ADD_PROPERTY(PropertyInfo(Variant::INT, "layer_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_layer_mask", "get_layer_mask");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadow", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows_setting", "get_cast_shadows_setting");
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");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_begin_margin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_visibility_range_begin_margin", "get_visibility_range_begin_margin");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_end", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_visibility_range_end", "get_visibility_range_end");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_end_margin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_visibility_range_end_margin", "get_visibility_range_end_margin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_range_fade_mode", PROPERTY_HINT_ENUM, "Disabled,Self,Dependencies"), "set_visibility_range_fade_mode", "get_visibility_range_fade_mode");
}

View File

@@ -0,0 +1,90 @@
/**************************************************************************/
/* importer_mesh_instance_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
#include "scene/3d/visual_instance_3d.h"
#include "scene/resources/3d/skin.h"
class ImporterMesh;
class ImporterMeshInstance3D : public Node3D {
GDCLASS(ImporterMeshInstance3D, Node3D)
Ref<ImporterMesh> mesh;
Ref<Skin> skin;
NodePath skeleton_path;
Vector<Ref<Material>> surface_materials;
uint32_t layer_mask = 1;
GeometryInstance3D::ShadowCastingSetting shadow_casting_setting = GeometryInstance3D::SHADOW_CASTING_SETTING_ON;
float visibility_range_begin = 0.0;
float visibility_range_end = 0.0;
float visibility_range_begin_margin = 0.0;
float visibility_range_end_margin = 0.0;
GeometryInstance3D::VisibilityRangeFadeMode visibility_range_fade_mode = GeometryInstance3D::VISIBILITY_RANGE_FADE_DISABLED;
protected:
static void _bind_methods();
public:
void set_mesh(const Ref<ImporterMesh> &p_mesh);
Ref<ImporterMesh> get_mesh() const;
void set_skin(const Ref<Skin> &p_skin);
Ref<Skin> get_skin() const;
void set_surface_material(int p_idx, const Ref<Material> &p_material);
Ref<Material> get_surface_material(int p_idx) const;
void set_skeleton_path(const NodePath &p_path);
NodePath get_skeleton_path() const;
void set_layer_mask(const uint32_t p_layer_mask);
uint32_t get_layer_mask() const;
void set_cast_shadows_setting(GeometryInstance3D::ShadowCastingSetting p_shadow_casting_setting);
GeometryInstance3D::ShadowCastingSetting get_cast_shadows_setting() const;
void set_visibility_range_begin(float p_dist);
float get_visibility_range_begin() const;
void set_visibility_range_end(float p_dist);
float get_visibility_range_end() const;
void set_visibility_range_begin_margin(float p_dist);
float get_visibility_range_begin_margin() const;
void set_visibility_range_end_margin(float p_dist);
float get_visibility_range_end_margin() const;
void set_visibility_range_fade_mode(GeometryInstance3D::VisibilityRangeFadeMode p_mode);
GeometryInstance3D::VisibilityRangeFadeMode get_visibility_range_fade_mode() const;
};

1113
scene/3d/label_3d.cpp Normal file

File diff suppressed because it is too large Load Diff

266
scene/3d/label_3d.h Normal file
View File

@@ -0,0 +1,266 @@
/**************************************************************************/
/* label_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/visual_instance_3d.h"
#include "scene/resources/font.h"
#include "servers/text_server.h"
class Label3D : public GeometryInstance3D {
GDCLASS(Label3D, GeometryInstance3D);
public:
enum DrawFlags {
FLAG_SHADED,
FLAG_DOUBLE_SIDED,
FLAG_DISABLE_DEPTH_TEST,
FLAG_FIXED_SIZE,
FLAG_MAX
};
enum AlphaCutMode {
ALPHA_CUT_DISABLED,
ALPHA_CUT_DISCARD,
ALPHA_CUT_OPAQUE_PREPASS,
ALPHA_CUT_HASH,
ALPHA_CUT_MAX
};
private:
real_t pixel_size = 0.005;
bool flags[FLAG_MAX] = {};
AlphaCutMode alpha_cut = ALPHA_CUT_DISABLED;
float alpha_scissor_threshold = 0.5;
float alpha_hash_scale = 1.0;
StandardMaterial3D::AlphaAntiAliasing alpha_antialiasing_mode = StandardMaterial3D::ALPHA_ANTIALIASING_OFF;
float alpha_antialiasing_edge = 0.0f;
AABB aabb;
mutable Ref<TriangleMesh> triangle_mesh;
RID mesh;
struct SurfaceData {
PackedVector3Array mesh_vertices;
PackedVector3Array mesh_normals;
PackedFloat32Array mesh_tangents;
PackedColorArray mesh_colors;
PackedVector2Array mesh_uvs;
PackedInt32Array indices;
int offset = 0;
float z_shift = 0.0;
RID material;
};
struct SurfaceKey {
uint64_t texture_id;
int32_t priority;
int32_t outline_size;
bool operator==(const SurfaceKey &p_b) const {
return (texture_id == p_b.texture_id) && (priority == p_b.priority) && (outline_size == p_b.outline_size);
}
SurfaceKey(uint64_t p_texture_id, int p_priority, int p_outline_size) {
texture_id = p_texture_id;
priority = p_priority;
outline_size = p_outline_size;
}
};
struct SurfaceKeyHasher {
_FORCE_INLINE_ static uint32_t hash(const SurfaceKey &p_a) {
return hash_murmur3_buffer(&p_a, sizeof(SurfaceKey));
}
};
HashMap<SurfaceKey, SurfaceData, SurfaceKeyHasher> surfaces;
HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER;
VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_CENTER;
String text;
String xl_text;
bool uppercase = false;
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF;
BitField<TextServer::LineBreakFlag> autowrap_flags_trim = TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES;
BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
float width = 500.0;
int font_size = 32;
Ref<Font> font_override;
mutable Ref<Font> theme_font;
Color modulate = Color(1, 1, 1, 1);
Point2 lbl_offset;
int outline_render_priority = -1;
int render_priority = 0;
int outline_size = 12;
Color outline_modulate = Color(0, 0, 0, 1);
float line_spacing = 0.f;
String language;
TextServer::Direction text_direction = TextServer::DIRECTION_AUTO;
TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
RID text_rid;
Vector<RID> lines_rid;
RID base_material;
StandardMaterial3D::BillboardMode billboard_mode = StandardMaterial3D::BILLBOARD_DISABLED;
StandardMaterial3D::TextureFilter texture_filter = StandardMaterial3D::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS;
bool pending_update = false;
bool dirty_lines = true;
bool dirty_font = true;
bool dirty_text = true;
void _generate_glyph_surfaces(const Glyph &p_glyph, Vector2 &r_offset, const Color &p_modulate, int p_priority = 0, int p_outline_size = 0);
protected:
GDVIRTUAL2RC(TypedArray<Vector3i>, _structured_text_parser, Array, String)
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
void _im_update();
void _font_changed();
void _queue_update();
void _shape();
public:
void set_horizontal_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_horizontal_alignment() const;
void set_vertical_alignment(VerticalAlignment p_alignment);
VerticalAlignment get_vertical_alignment() const;
void set_render_priority(int p_priority);
int get_render_priority() const;
void set_outline_render_priority(int p_priority);
int get_outline_render_priority() const;
void set_text(const String &p_string);
String get_text() const;
void set_text_direction(TextServer::Direction p_text_direction);
TextServer::Direction get_text_direction() const;
void set_language(const String &p_language);
String get_language() const;
void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
void set_uppercase(bool p_uppercase);
bool is_uppercase() const;
void set_font(const Ref<Font> &p_font);
Ref<Font> get_font() const;
Ref<Font> _get_font_or_default() const;
void set_font_size(int p_size);
int get_font_size() const;
void set_outline_size(int p_size);
int get_outline_size() const;
void set_line_spacing(float p_size);
float get_line_spacing() const;
void set_modulate(const Color &p_color);
Color get_modulate() const;
void set_outline_modulate(const Color &p_color);
Color get_outline_modulate() const;
void set_autowrap_mode(TextServer::AutowrapMode p_mode);
TextServer::AutowrapMode get_autowrap_mode() const;
void set_autowrap_trim_flags(BitField<TextServer::LineBreakFlag> p_flags);
BitField<TextServer::LineBreakFlag> get_autowrap_trim_flags() const;
void set_justification_flags(BitField<TextServer::JustificationFlag> p_flags);
BitField<TextServer::JustificationFlag> get_justification_flags() const;
void set_width(float p_width);
float get_width() const;
void set_pixel_size(real_t p_amount);
real_t get_pixel_size() const;
void set_offset(const Point2 &p_offset);
Point2 get_offset() const;
void set_draw_flag(DrawFlags p_flag, bool p_enable);
bool get_draw_flag(DrawFlags p_flag) const;
void set_alpha_cut_mode(AlphaCutMode p_mode);
AlphaCutMode get_alpha_cut_mode() const;
void set_alpha_scissor_threshold(float p_threshold);
float get_alpha_scissor_threshold() const;
void set_alpha_hash_scale(float p_hash_scale);
float get_alpha_hash_scale() const;
void set_alpha_antialiasing(BaseMaterial3D::AlphaAntiAliasing p_alpha_aa);
BaseMaterial3D::AlphaAntiAliasing get_alpha_antialiasing() const;
void set_alpha_antialiasing_edge(float p_edge);
float get_alpha_antialiasing_edge() const;
void set_billboard_mode(StandardMaterial3D::BillboardMode p_mode);
StandardMaterial3D::BillboardMode get_billboard_mode() const;
void set_texture_filter(StandardMaterial3D::TextureFilter p_filter);
StandardMaterial3D::TextureFilter get_texture_filter() const;
virtual AABB get_aabb() const override;
virtual Ref<TriangleMesh> generate_triangle_mesh() const override;
Label3D();
~Label3D();
};
VARIANT_ENUM_CAST(Label3D::DrawFlags);
VARIANT_ENUM_CAST(Label3D::AlphaCutMode);

683
scene/3d/light_3d.cpp Normal file
View File

@@ -0,0 +1,683 @@
/**************************************************************************/
/* light_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 "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;
RS::get_singleton()->light_set_param(light, RS::LightParam(p_param), p_value);
if (p_param == PARAM_SPOT_ANGLE || p_param == PARAM_RANGE) {
update_gizmos();
if (p_param == PARAM_SPOT_ANGLE) {
update_configuration_warnings();
}
}
}
real_t Light3D::get_param(Param p_param) const {
ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0);
return param[p_param];
}
void Light3D::set_shadow(bool p_enable) {
shadow = p_enable;
RS::get_singleton()->light_set_shadow(light, p_enable);
update_configuration_warnings();
}
bool Light3D::has_shadow() const {
return shadow;
}
void Light3D::set_negative(bool p_enable) {
negative = p_enable;
RS::get_singleton()->light_set_negative(light, p_enable);
}
bool Light3D::is_negative() const {
return negative;
}
void Light3D::set_enable_distance_fade(bool p_enable) {
distance_fade_enabled = p_enable;
RS::get_singleton()->light_set_distance_fade(light, distance_fade_enabled, distance_fade_begin, distance_fade_shadow, distance_fade_length);
notify_property_list_changed();
}
bool Light3D::is_distance_fade_enabled() const {
return distance_fade_enabled;
}
void Light3D::set_distance_fade_begin(real_t p_distance) {
distance_fade_begin = p_distance;
RS::get_singleton()->light_set_distance_fade(light, distance_fade_enabled, distance_fade_begin, distance_fade_shadow, distance_fade_length);
}
real_t Light3D::get_distance_fade_begin() const {
return distance_fade_begin;
}
void Light3D::set_distance_fade_shadow(real_t p_distance) {
distance_fade_shadow = p_distance;
RS::get_singleton()->light_set_distance_fade(light, distance_fade_enabled, distance_fade_begin, distance_fade_shadow, distance_fade_length);
}
real_t Light3D::get_distance_fade_shadow() const {
return distance_fade_shadow;
}
void Light3D::set_distance_fade_length(real_t p_length) {
distance_fade_length = p_length;
RS::get_singleton()->light_set_distance_fade(light, distance_fade_enabled, distance_fade_begin, distance_fade_shadow, distance_fade_length);
}
real_t Light3D::get_distance_fade_length() const {
return distance_fade_length;
}
void Light3D::set_cull_mask(uint32_t p_cull_mask) {
cull_mask = p_cull_mask;
RS::get_singleton()->light_set_cull_mask(light, p_cull_mask);
}
uint32_t Light3D::get_cull_mask() const {
return cull_mask;
}
void Light3D::set_color(const Color &p_color) {
color = p_color;
if (GLOBAL_GET_CACHED(bool, "rendering/lights_and_shadows/use_physical_light_units")) {
Color combined = color.srgb_to_linear();
combined *= correlated_color.srgb_to_linear();
RS::get_singleton()->light_set_color(light, combined.linear_to_srgb());
} else {
RS::get_singleton()->light_set_color(light, color);
}
// The gizmo color depends on the light color, so update it.
update_gizmos();
}
Color Light3D::get_color() const {
return color;
}
void Light3D::set_shadow_reverse_cull_face(bool p_enable) {
reverse_cull = p_enable;
RS::get_singleton()->light_set_reverse_cull_face_mode(light, reverse_cull);
}
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));
} else if (type == RenderingServer::LIGHT_OMNI) {
return AABB(Vector3(-1, -1, -1) * param[PARAM_RANGE], Vector3(2, 2, 2) * param[PARAM_RANGE]);
} else if (type == RenderingServer::LIGHT_SPOT) {
real_t cone_slant_height = param[PARAM_RANGE];
real_t cone_angle_rad = Math::deg_to_rad(param[PARAM_SPOT_ANGLE]);
if (cone_angle_rad > Math::PI / 2.0) {
// Just return the AABB of an omni light if the spot angle is above 90 degrees.
return AABB(Vector3(-1, -1, -1) * cone_slant_height, Vector3(2, 2, 2) * cone_slant_height);
}
real_t size = Math::sin(cone_angle_rad) * cone_slant_height;
return AABB(Vector3(-size, -size, -cone_slant_height), Vector3(2 * size, 2 * size, cone_slant_height));
}
return AABB();
}
PackedStringArray Light3D::get_configuration_warnings() const {
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (!get_scale().is_equal_approx(Vector3(1, 1, 1))) {
warnings.push_back(RTR("A light's scale does not affect the visual size of the light."));
}
return warnings;
}
void Light3D::set_bake_mode(BakeMode p_mode) {
bake_mode = p_mode;
RS::get_singleton()->light_set_bake_mode(light, RS::LightBakeMode(p_mode));
}
Light3D::BakeMode Light3D::get_bake_mode() const {
return bake_mode;
}
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();
}
Ref<Texture2D> Light3D::get_projector() const {
return projector;
}
void Light3D::owner_changed_notify() {
// For cases where owner changes _after_ entering tree (as example, editor editing).
_update_visibility();
}
// Temperature expressed in Kelvins. Valid range 1000 - 15000
// First converts to CIE 1960 then to sRGB
// As explained in the Filament documentation: https://google.github.io/filament/Filament.md.html#lighting/directlighting/lightsparameterization
Color _color_from_temperature(float p_temperature) {
float T2 = p_temperature * p_temperature;
float u = (0.860117757f + 1.54118254e-4f * p_temperature + 1.28641212e-7f * T2) /
(1.0f + 8.42420235e-4f * p_temperature + 7.08145163e-7f * T2);
float v = (0.317398726f + 4.22806245e-5f * p_temperature + 4.20481691e-8f * T2) /
(1.0f - 2.89741816e-5f * p_temperature + 1.61456053e-7f * T2);
// Convert to xyY space.
float d = 1.0f / (2.0f * u - 8.0f * v + 4.0f);
float x = 3.0f * u * d;
float y = 2.0f * v * d;
// Convert to XYZ space
const float a = 1.0 / MAX(y, 1e-5f);
Vector3 xyz = Vector3(x * a, 1.0, (1.0f - x - y) * a);
// Convert from XYZ to sRGB(linear)
Vector3 linear = Vector3(3.2404542f * xyz.x - 1.5371385f * xyz.y - 0.4985314f * xyz.z,
-0.9692660f * xyz.x + 1.8760108f * xyz.y + 0.0415560f * xyz.z,
0.0556434f * xyz.x - 0.2040259f * xyz.y + 1.0572252f * xyz.z);
linear /= MAX(1e-5f, linear[linear.max_axis_index()]);
// Normalize, clamp, and convert to sRGB.
return Color(linear.x, linear.y, linear.z).clamp().linear_to_srgb();
}
void Light3D::set_temperature(const float p_temperature) {
temperature = p_temperature;
if (!GLOBAL_GET_CACHED(bool, "rendering/lights_and_shadows/use_physical_light_units")) {
return;
}
correlated_color = _color_from_temperature(temperature);
Color combined = color.srgb_to_linear() * correlated_color.srgb_to_linear();
RS::get_singleton()->light_set_color(light, combined.linear_to_srgb());
// The gizmo color depends on the light color, so update it.
update_gizmos();
}
Color Light3D::get_correlated_color() const {
return correlated_color;
}
float Light3D::get_temperature() const {
return temperature;
}
void Light3D::_update_visibility() {
if (!is_inside_tree()) {
return;
}
bool editor_ok = true;
#ifdef TOOLS_ENABLED
if (editor_only) {
if (!Engine::get_singleton()->is_editor_hint()) {
editor_ok = false;
} else {
editor_ok = (get_tree()->get_edited_scene_root() && (this == get_tree()->get_edited_scene_root() || get_owner() == get_tree()->get_edited_scene_root()));
}
}
#else
if (editor_only) {
editor_ok = false;
}
#endif
RS::get_singleton()->instance_set_visible(get_instance(), is_visible_in_tree() && editor_ok);
}
void Light3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_TRANSFORM_CHANGED: {
update_configuration_warnings();
} break;
case NOTIFICATION_VISIBILITY_CHANGED:
case NOTIFICATION_ENTER_TREE: {
_update_visibility();
} break;
}
}
void Light3D::set_editor_only(bool p_editor_only) {
editor_only = p_editor_only;
_update_visibility();
}
bool Light3D::is_editor_only() const {
return editor_only;
}
void Light3D::_validate_property(PropertyInfo &p_property) const {
if (get_light_type() != RS::LIGHT_DIRECTIONAL && (p_property.name == "light_angular_distance" || p_property.name == "light_intensity_lux")) {
// Angular distance and Light Intensity Lux are only used in DirectionalLight3D.
p_property.usage = PROPERTY_USAGE_NONE;
} else if (get_light_type() == RS::LIGHT_DIRECTIONAL && p_property.name == "light_intensity_lumens") {
p_property.usage = PROPERTY_USAGE_NONE;
}
if (!GLOBAL_GET_CACHED(bool, "rendering/lights_and_shadows/use_physical_light_units") && (p_property.name == "light_intensity_lumens" || p_property.name == "light_intensity_lux" || p_property.name == "light_temperature")) {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
void Light3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_editor_only", "editor_only"), &Light3D::set_editor_only);
ClassDB::bind_method(D_METHOD("is_editor_only"), &Light3D::is_editor_only);
ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &Light3D::set_param);
ClassDB::bind_method(D_METHOD("get_param", "param"), &Light3D::get_param);
ClassDB::bind_method(D_METHOD("set_shadow", "enabled"), &Light3D::set_shadow);
ClassDB::bind_method(D_METHOD("has_shadow"), &Light3D::has_shadow);
ClassDB::bind_method(D_METHOD("set_negative", "enabled"), &Light3D::set_negative);
ClassDB::bind_method(D_METHOD("is_negative"), &Light3D::is_negative);
ClassDB::bind_method(D_METHOD("set_cull_mask", "cull_mask"), &Light3D::set_cull_mask);
ClassDB::bind_method(D_METHOD("get_cull_mask"), &Light3D::get_cull_mask);
ClassDB::bind_method(D_METHOD("set_enable_distance_fade", "enable"), &Light3D::set_enable_distance_fade);
ClassDB::bind_method(D_METHOD("is_distance_fade_enabled"), &Light3D::is_distance_fade_enabled);
ClassDB::bind_method(D_METHOD("set_distance_fade_begin", "distance"), &Light3D::set_distance_fade_begin);
ClassDB::bind_method(D_METHOD("get_distance_fade_begin"), &Light3D::get_distance_fade_begin);
ClassDB::bind_method(D_METHOD("set_distance_fade_shadow", "distance"), &Light3D::set_distance_fade_shadow);
ClassDB::bind_method(D_METHOD("get_distance_fade_shadow"), &Light3D::get_distance_fade_shadow);
ClassDB::bind_method(D_METHOD("set_distance_fade_length", "distance"), &Light3D::set_distance_fade_length);
ClassDB::bind_method(D_METHOD("get_distance_fade_length"), &Light3D::get_distance_fade_length);
ClassDB::bind_method(D_METHOD("set_color", "color"), &Light3D::set_color);
ClassDB::bind_method(D_METHOD("get_color"), &Light3D::get_color);
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);
ClassDB::bind_method(D_METHOD("set_projector", "projector"), &Light3D::set_projector);
ClassDB::bind_method(D_METHOD("get_projector"), &Light3D::get_projector);
ClassDB::bind_method(D_METHOD("set_temperature", "temperature"), &Light3D::set_temperature);
ClassDB::bind_method(D_METHOD("get_temperature"), &Light3D::get_temperature);
ClassDB::bind_method(D_METHOD("get_correlated_color"), &Light3D::get_correlated_color);
ADD_GROUP("Light", "light_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_intensity_lumens", PROPERTY_HINT_RANGE, "0,100000.0,0.01,or_greater,suffix:lm"), "set_param", "get_param", PARAM_INTENSITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_intensity_lux", PROPERTY_HINT_RANGE, "0,150000.0,0.01,or_greater,suffix:lx"), "set_param", "get_param", PARAM_INTENSITY);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "light_temperature", PROPERTY_HINT_RANGE, "1000,15000.0,1.0,suffix:k"), "set_temperature", "get_temperature");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "light_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_color", "get_color");
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);
// 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");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_specular", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_param", "get_param", PARAM_SPECULAR);
ADD_PROPERTY(PropertyInfo(Variant::INT, "light_bake_mode", PROPERTY_HINT_ENUM, "Disabled,Static,Dynamic"), "set_bake_mode", "get_bake_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "light_cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask");
ADD_GROUP("Shadow", "shadow_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_enabled", PROPERTY_HINT_GROUP_ENABLE), "set_shadow", "has_shadow");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_bias", PROPERTY_HINT_RANGE, "0,10,0.001"), "set_param", "get_param", PARAM_SHADOW_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_normal_bias", PROPERTY_HINT_RANGE, "0,10,0.001"), "set_param", "get_param", PARAM_SHADOW_NORMAL_BIAS);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_reverse_cull_face"), "set_shadow_reverse_cull_face", "get_shadow_reverse_cull_face");
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", PROPERTY_HINT_GROUP_ENABLE), "set_enable_distance_fade", "is_distance_fade_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_begin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_distance_fade_begin", "get_distance_fade_begin");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_shadow", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_distance_fade_shadow", "get_distance_fade_shadow");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_length", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_distance_fade_length", "get_distance_fade_length");
ADD_GROUP("Editor", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "is_editor_only");
ADD_GROUP("", "");
BIND_ENUM_CONSTANT(PARAM_ENERGY);
BIND_ENUM_CONSTANT(PARAM_INDIRECT_ENERGY);
BIND_ENUM_CONSTANT(PARAM_VOLUMETRIC_FOG_ENERGY);
BIND_ENUM_CONSTANT(PARAM_SPECULAR);
BIND_ENUM_CONSTANT(PARAM_RANGE);
BIND_ENUM_CONSTANT(PARAM_SIZE);
BIND_ENUM_CONSTANT(PARAM_ATTENUATION);
BIND_ENUM_CONSTANT(PARAM_SPOT_ANGLE);
BIND_ENUM_CONSTANT(PARAM_SPOT_ATTENUATION);
BIND_ENUM_CONSTANT(PARAM_SHADOW_MAX_DISTANCE);
BIND_ENUM_CONSTANT(PARAM_SHADOW_SPLIT_1_OFFSET);
BIND_ENUM_CONSTANT(PARAM_SHADOW_SPLIT_2_OFFSET);
BIND_ENUM_CONSTANT(PARAM_SHADOW_SPLIT_3_OFFSET);
BIND_ENUM_CONSTANT(PARAM_SHADOW_FADE_START);
BIND_ENUM_CONSTANT(PARAM_SHADOW_NORMAL_BIAS);
BIND_ENUM_CONSTANT(PARAM_SHADOW_BIAS);
BIND_ENUM_CONSTANT(PARAM_SHADOW_PANCAKE_SIZE);
BIND_ENUM_CONSTANT(PARAM_SHADOW_OPACITY);
BIND_ENUM_CONSTANT(PARAM_SHADOW_BLUR);
BIND_ENUM_CONSTANT(PARAM_TRANSMITTANCE_BIAS);
BIND_ENUM_CONSTANT(PARAM_INTENSITY);
BIND_ENUM_CONSTANT(PARAM_MAX);
BIND_ENUM_CONSTANT(BAKE_DISABLED);
BIND_ENUM_CONSTANT(BAKE_STATIC);
BIND_ENUM_CONSTANT(BAKE_DYNAMIC);
}
Light3D::Light3D(RenderingServer::LightType p_type) {
type = p_type;
switch (p_type) {
case RS::LIGHT_DIRECTIONAL:
light = RenderingServer::get_singleton()->directional_light_create();
break;
case RS::LIGHT_OMNI:
light = RenderingServer::get_singleton()->omni_light_create();
break;
case RS::LIGHT_SPOT:
light = RenderingServer::get_singleton()->spot_light_create();
break;
default: {
};
}
RS::get_singleton()->instance_set_base(get_instance(), light);
set_color(Color(1, 1, 1, 1));
set_shadow(false);
set_negative(false);
set_cull_mask(0xFFFFFFFF);
set_param(PARAM_ENERGY, 1);
set_param(PARAM_INDIRECT_ENERGY, 1);
set_param(PARAM_VOLUMETRIC_FOG_ENERGY, 1);
set_param(PARAM_SPECULAR, 0.5);
set_param(PARAM_RANGE, 5);
set_param(PARAM_SIZE, 0);
set_param(PARAM_ATTENUATION, 1);
set_param(PARAM_SPOT_ANGLE, 45);
set_param(PARAM_SPOT_ATTENUATION, 1);
set_param(PARAM_SHADOW_MAX_DISTANCE, 0);
set_param(PARAM_SHADOW_SPLIT_1_OFFSET, 0.1);
set_param(PARAM_SHADOW_SPLIT_2_OFFSET, 0.2);
set_param(PARAM_SHADOW_SPLIT_3_OFFSET, 0.5);
set_param(PARAM_SHADOW_FADE_START, 0.8);
set_param(PARAM_SHADOW_PANCAKE_SIZE, 20.0);
set_param(PARAM_SHADOW_OPACITY, 1.0);
set_param(PARAM_SHADOW_BLUR, 1.0);
set_param(PARAM_SHADOW_BIAS, 0.1);
set_param(PARAM_SHADOW_NORMAL_BIAS, 1.0);
set_param(PARAM_TRANSMITTANCE_BIAS, 0.05);
set_param(PARAM_SHADOW_FADE_START, 1);
// For OmniLight3D and SpotLight3D, specified in Lumens.
set_param(PARAM_INTENSITY, 1000.0);
set_temperature(6500.0); // Nearly white.
set_disable_scale(true);
}
Light3D::Light3D() {
ERR_PRINT("Light3D should not be instantiated directly; use the DirectionalLight3D, OmniLight3D or SpotLight3D subtypes instead.");
}
Light3D::~Light3D() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->instance_set_base(get_instance(), RID());
if (light.is_valid()) {
RenderingServer::get_singleton()->free(light);
}
}
/////////////////////////////////////////
void DirectionalLight3D::set_shadow_mode(ShadowMode p_mode) {
shadow_mode = p_mode;
RS::get_singleton()->light_directional_set_shadow_mode(light, RS::LightDirectionalShadowMode(p_mode));
notify_property_list_changed();
}
DirectionalLight3D::ShadowMode DirectionalLight3D::get_shadow_mode() const {
return shadow_mode;
}
void DirectionalLight3D::set_blend_splits(bool p_enable) {
blend_splits = p_enable;
RS::get_singleton()->light_directional_set_blend_splits(light, p_enable);
}
bool DirectionalLight3D::is_blend_splits_enabled() const {
return blend_splits;
}
void DirectionalLight3D::set_sky_mode(SkyMode p_mode) {
sky_mode = p_mode;
RS::get_singleton()->light_directional_set_sky_mode(light, RS::LightDirectionalSkyMode(p_mode));
}
DirectionalLight3D::SkyMode DirectionalLight3D::get_sky_mode() const {
return sky_mode;
}
void DirectionalLight3D::_validate_property(PropertyInfo &p_property) const {
if (Engine::get_singleton()->is_editor_hint()) {
if (shadow_mode == SHADOW_ORTHOGONAL && (p_property.name == "directional_shadow_split_1" || p_property.name == "directional_shadow_blend_splits")) {
// Split 2 and split blending are only used with the PSSM 2 Splits and PSSM 4 Splits shadow modes.
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
if ((shadow_mode == SHADOW_ORTHOGONAL || shadow_mode == SHADOW_PARALLEL_2_SPLITS) && (p_property.name == "directional_shadow_split_2" || p_property.name == "directional_shadow_split_3")) {
// Splits 3 and 4 are only used with the PSSM 4 Splits shadow mode.
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
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;
}
if (p_property.name == "distance_fade_enabled" || p_property.name == "distance_fade_begin" || p_property.name == "distance_fade_shadow" || p_property.name == "distance_fade_length") {
// Not relevant for DirectionalLight3D, as the light LOD system only pertains to point lights.
// For DirectionalLight3D, `directional_shadow_max_distance` can be used instead.
p_property.usage = PROPERTY_USAGE_NONE;
}
}
void DirectionalLight3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_shadow_mode", "mode"), &DirectionalLight3D::set_shadow_mode);
ClassDB::bind_method(D_METHOD("get_shadow_mode"), &DirectionalLight3D::get_shadow_mode);
ClassDB::bind_method(D_METHOD("set_blend_splits", "enabled"), &DirectionalLight3D::set_blend_splits);
ClassDB::bind_method(D_METHOD("is_blend_splits_enabled"), &DirectionalLight3D::is_blend_splits_enabled);
ClassDB::bind_method(D_METHOD("set_sky_mode", "mode"), &DirectionalLight3D::set_sky_mode);
ClassDB::bind_method(D_METHOD("get_sky_mode"), &DirectionalLight3D::get_sky_mode);
ADD_GROUP("Directional Shadow", "directional_shadow_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "directional_shadow_mode", PROPERTY_HINT_ENUM, "Orthogonal (Fast),PSSM 2 Splits (Average),PSSM 4 Splits (Slow)"), "set_shadow_mode", "get_shadow_mode");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_1", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_1_OFFSET);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_2", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_2_OFFSET);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_3", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_3_OFFSET);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional_shadow_blend_splits"), "set_blend_splits", "is_blend_splits_enabled");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_fade_start", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_FADE_START);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_max_distance", PROPERTY_HINT_RANGE, "0,8192,0.1,or_greater,exp"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_pancake_size", PROPERTY_HINT_RANGE, "0,1024,0.1,or_greater,exp"), "set_param", "get_param", PARAM_SHADOW_PANCAKE_SIZE);
ADD_PROPERTY(PropertyInfo(Variant::INT, "sky_mode", PROPERTY_HINT_ENUM, "Light and Sky,Light Only,Sky Only"), "set_sky_mode", "get_sky_mode");
BIND_ENUM_CONSTANT(SHADOW_ORTHOGONAL);
BIND_ENUM_CONSTANT(SHADOW_PARALLEL_2_SPLITS);
BIND_ENUM_CONSTANT(SHADOW_PARALLEL_4_SPLITS);
BIND_ENUM_CONSTANT(SKY_MODE_LIGHT_AND_SKY);
BIND_ENUM_CONSTANT(SKY_MODE_LIGHT_ONLY);
BIND_ENUM_CONSTANT(SKY_MODE_SKY_ONLY);
}
DirectionalLight3D::DirectionalLight3D() :
Light3D(RenderingServer::LIGHT_DIRECTIONAL) {
set_param(PARAM_SHADOW_MAX_DISTANCE, 100);
set_param(PARAM_SHADOW_FADE_START, 0.8);
// 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);
}
void OmniLight3D::set_shadow_mode(ShadowMode p_mode) {
shadow_mode = p_mode;
RS::get_singleton()->light_omni_set_shadow_mode(light, RS::LightOmniShadowMode(p_mode));
}
OmniLight3D::ShadowMode OmniLight3D::get_shadow_mode() const {
return shadow_mode;
}
PackedStringArray OmniLight3D::get_configuration_warnings() const {
PackedStringArray warnings = Light3D::get_configuration_warnings();
if (!has_shadow() && get_projector().is_valid()) {
warnings.push_back(RTR("Projector texture only works with shadows active."));
}
if (get_projector().is_valid() && (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "dummy")) {
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;
}
void OmniLight3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_shadow_mode", "mode"), &OmniLight3D::set_shadow_mode);
ClassDB::bind_method(D_METHOD("get_shadow_mode"), &OmniLight3D::get_shadow_mode);
ADD_GROUP("Omni", "omni_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "omni_range", PROPERTY_HINT_RANGE, "0,4096,0.001,or_greater,exp"), "set_param", "get_param", PARAM_RANGE);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "omni_attenuation", PROPERTY_HINT_RANGE, "-10,10,0.001,or_greater,or_less"), "set_param", "get_param", PARAM_ATTENUATION);
ADD_PROPERTY(PropertyInfo(Variant::INT, "omni_shadow_mode", PROPERTY_HINT_ENUM, "Dual Paraboloid,Cube"), "set_shadow_mode", "get_shadow_mode");
BIND_ENUM_CONSTANT(SHADOW_DUAL_PARABOLOID);
BIND_ENUM_CONSTANT(SHADOW_CUBE);
}
OmniLight3D::OmniLight3D() :
Light3D(RenderingServer::LIGHT_OMNI) {
set_shadow_mode(SHADOW_CUBE);
}
PackedStringArray SpotLight3D::get_configuration_warnings() const {
PackedStringArray warnings = Light3D::get_configuration_warnings();
if (has_shadow() && get_param(PARAM_SPOT_ANGLE) >= 90.0) {
warnings.push_back(RTR("A SpotLight3D with an angle wider than 90 degrees cannot cast shadows."));
}
if (!has_shadow() && get_projector().is_valid()) {
warnings.push_back(RTR("Projector texture only works with shadows active."));
}
if (get_projector().is_valid() && (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "dummy")) {
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;
}
void SpotLight3D::_bind_methods() {
ADD_GROUP("Spot", "spot_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_range", PROPERTY_HINT_RANGE, "0,4096,0.001,or_greater,exp,suffix:m"), "set_param", "get_param", PARAM_RANGE);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_attenuation", PROPERTY_HINT_RANGE, "-10,10,0.01,or_greater,or_less"), "set_param", "get_param", PARAM_ATTENUATION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle", PROPERTY_HINT_RANGE, "0,180,0.01,degrees"), "set_param", "get_param", PARAM_SPOT_ANGLE);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_SPOT_ATTENUATION);
}
SpotLight3D::SpotLight3D() :
Light3D(RenderingServer::LIGHT_SPOT) {
// Decrease the default shadow bias to better suit most scenes.
set_param(PARAM_SHADOW_BIAS, 0.03);
}

240
scene/3d/light_3d.h Normal file
View File

@@ -0,0 +1,240 @@
/**************************************************************************/
/* light_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/visual_instance_3d.h"
class Light3D : public VisualInstance3D {
GDCLASS(Light3D, VisualInstance3D);
public:
enum Param {
PARAM_ENERGY = RS::LIGHT_PARAM_ENERGY,
PARAM_INDIRECT_ENERGY = RS::LIGHT_PARAM_INDIRECT_ENERGY,
PARAM_VOLUMETRIC_FOG_ENERGY = RS::LIGHT_PARAM_VOLUMETRIC_FOG_ENERGY,
PARAM_SPECULAR = RS::LIGHT_PARAM_SPECULAR,
PARAM_RANGE = RS::LIGHT_PARAM_RANGE,
PARAM_SIZE = RS::LIGHT_PARAM_SIZE,
PARAM_ATTENUATION = RS::LIGHT_PARAM_ATTENUATION,
PARAM_SPOT_ANGLE = RS::LIGHT_PARAM_SPOT_ANGLE,
PARAM_SPOT_ATTENUATION = RS::LIGHT_PARAM_SPOT_ATTENUATION,
PARAM_SHADOW_MAX_DISTANCE = RS::LIGHT_PARAM_SHADOW_MAX_DISTANCE,
PARAM_SHADOW_SPLIT_1_OFFSET = RS::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET,
PARAM_SHADOW_SPLIT_2_OFFSET = RS::LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET,
PARAM_SHADOW_SPLIT_3_OFFSET = RS::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET,
PARAM_SHADOW_FADE_START = RS::LIGHT_PARAM_SHADOW_FADE_START,
PARAM_SHADOW_NORMAL_BIAS = RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS,
PARAM_SHADOW_BIAS = RS::LIGHT_PARAM_SHADOW_BIAS,
PARAM_SHADOW_PANCAKE_SIZE = RS::LIGHT_PARAM_SHADOW_PANCAKE_SIZE,
PARAM_SHADOW_OPACITY = RS::LIGHT_PARAM_SHADOW_OPACITY,
PARAM_SHADOW_BLUR = RS::LIGHT_PARAM_SHADOW_BLUR,
PARAM_TRANSMITTANCE_BIAS = RS::LIGHT_PARAM_TRANSMITTANCE_BIAS,
PARAM_INTENSITY = RS::LIGHT_PARAM_INTENSITY,
PARAM_MAX = RS::LIGHT_PARAM_MAX
};
enum BakeMode {
BAKE_DISABLED,
BAKE_STATIC,
BAKE_DYNAMIC,
};
private:
Color color;
real_t param[PARAM_MAX] = {};
bool shadow = false;
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;
real_t distance_fade_length = 10.0;
RS::LightType type = RenderingServer::LIGHT_DIRECTIONAL;
bool editor_only = false;
void _update_visibility();
BakeMode bake_mode = BAKE_DYNAMIC;
Ref<Texture2D> projector;
Color correlated_color = Color(1.0, 1.0, 1.0);
float temperature = 6500.0;
// bind helpers
virtual void owner_changed_notify() override;
protected:
RID light;
static void _bind_methods();
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
Light3D(RenderingServer::LightType p_type);
public:
RS::LightType get_light_type() const { return type; }
void set_editor_only(bool p_editor_only);
bool is_editor_only() const;
void set_param(Param p_param, real_t p_value);
real_t get_param(Param p_param) const;
void set_shadow(bool p_enable);
bool has_shadow() const;
void set_negative(bool p_enable);
bool is_negative() const;
void set_enable_distance_fade(bool p_enable);
bool is_distance_fade_enabled() const;
void set_distance_fade_begin(real_t p_distance);
real_t get_distance_fade_begin() const;
void set_distance_fade_shadow(real_t p_distance);
real_t get_distance_fade_shadow() const;
void set_distance_fade_length(real_t p_length);
real_t get_distance_fade_length() const;
void set_cull_mask(uint32_t p_cull_mask);
uint32_t get_cull_mask() const;
void set_color(const Color &p_color);
Color get_color() const;
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;
void set_projector(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_projector() const;
void set_temperature(const float p_temperature);
float get_temperature() const;
Color get_correlated_color() const;
virtual AABB get_aabb() const override;
virtual PackedStringArray get_configuration_warnings() const override;
Light3D();
~Light3D();
};
VARIANT_ENUM_CAST(Light3D::Param);
VARIANT_ENUM_CAST(Light3D::BakeMode);
class DirectionalLight3D : public Light3D {
GDCLASS(DirectionalLight3D, Light3D);
public:
enum ShadowMode {
SHADOW_ORTHOGONAL,
SHADOW_PARALLEL_2_SPLITS,
SHADOW_PARALLEL_4_SPLITS,
};
enum SkyMode {
SKY_MODE_LIGHT_AND_SKY,
SKY_MODE_LIGHT_ONLY,
SKY_MODE_SKY_ONLY,
};
private:
bool blend_splits;
ShadowMode shadow_mode;
SkyMode sky_mode = SKY_MODE_LIGHT_AND_SKY;
protected:
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
public:
void set_shadow_mode(ShadowMode p_mode);
ShadowMode get_shadow_mode() const;
void set_blend_splits(bool p_enable);
bool is_blend_splits_enabled() const;
void set_sky_mode(SkyMode p_mode);
SkyMode get_sky_mode() const;
DirectionalLight3D();
};
VARIANT_ENUM_CAST(DirectionalLight3D::ShadowMode)
VARIANT_ENUM_CAST(DirectionalLight3D::SkyMode)
class OmniLight3D : public Light3D {
GDCLASS(OmniLight3D, Light3D);
public:
// omni light
enum ShadowMode {
SHADOW_DUAL_PARABOLOID,
SHADOW_CUBE,
};
private:
ShadowMode shadow_mode;
protected:
static void _bind_methods();
public:
void set_shadow_mode(ShadowMode p_mode);
ShadowMode get_shadow_mode() const;
PackedStringArray get_configuration_warnings() const override;
OmniLight3D();
};
VARIANT_ENUM_CAST(OmniLight3D::ShadowMode)
class SpotLight3D : public Light3D {
GDCLASS(SpotLight3D, Light3D);
protected:
static void _bind_methods();
public:
PackedStringArray get_configuration_warnings() const override;
SpotLight3D();
};

1953
scene/3d/lightmap_gi.cpp Normal file

File diff suppressed because it is too large Load Diff

362
scene/3d/lightmap_gi.h Normal file
View File

@@ -0,0 +1,362 @@
/**************************************************************************/
/* lightmap_gi.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. */
/**************************************************************************/
#pragma once
#include "core/templates/local_vector.h"
#include "scene/3d/light_3d.h"
#include "scene/3d/lightmapper.h"
#include "scene/3d/visual_instance_3d.h"
class Sky;
class CameraAttributes;
class LightmapGIData : public Resource {
GDCLASS(LightmapGIData, Resource);
RES_BASE_EXTENSION("lmbake")
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;
struct User {
NodePath path;
int32_t sub_instance = 0;
Rect2 uv_scale;
int slice_index = 0;
};
Vector<User> users;
void _set_user_data(const Array &p_data);
Array _get_user_data() const;
void _set_probe_data(const Dictionary &p_data);
Dictionary _get_probe_data() const;
void _reset_lightmap_textures();
void _reset_shadowmask_textures();
protected:
static void _bind_methods();
public:
void add_user(const NodePath &p_path, const Rect2 &p_uv_scale, int p_slice_index, int32_t p_sub_instance = -1);
int get_user_count() const;
NodePath get_user_path(int p_user) const;
int32_t get_user_sub_instance(int p_user) const;
Rect2 get_user_lightmap_uv_scale(int p_user) const;
int get_user_lightmap_slice_index(int p_user) const;
void clear_users();
#ifndef DISABLE_DEPRECATED
void set_light_texture(const Ref<TextureLayered> &p_light_texture);
Ref<TextureLayered> get_light_texture() const;
void _set_light_textures_data(const Array &p_data);
Array _get_light_textures_data() const;
#endif
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;
void 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);
PackedVector3Array get_capture_points() const;
PackedColorArray get_capture_sh() const;
PackedInt32Array get_capture_tetrahedra() const;
PackedInt32Array get_capture_bsp_tree() const;
AABB get_capture_bounds() const;
void clear();
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();
};
class LightmapGI : public VisualInstance3D {
GDCLASS(LightmapGI, VisualInstance3D);
public:
enum BakeQuality {
BAKE_QUALITY_LOW,
BAKE_QUALITY_MEDIUM,
BAKE_QUALITY_HIGH,
BAKE_QUALITY_ULTRA,
};
enum GenerateProbes {
GENERATE_PROBES_DISABLED,
GENERATE_PROBES_SUBDIV_4,
GENERATE_PROBES_SUBDIV_8,
GENERATE_PROBES_SUBDIV_16,
GENERATE_PROBES_SUBDIV_32,
};
enum BakeError {
BAKE_ERROR_OK,
BAKE_ERROR_NO_SCENE_ROOT,
BAKE_ERROR_FOREIGN_DATA,
BAKE_ERROR_NO_LIGHTMAPPER,
BAKE_ERROR_NO_SAVE_PATH,
BAKE_ERROR_NO_MESHES,
BAKE_ERROR_MESHES_INVALID,
BAKE_ERROR_CANT_CREATE_IMAGE,
BAKE_ERROR_USER_ABORTED,
BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL,
BAKE_ERROR_LIGHTMAP_TOO_SMALL,
BAKE_ERROR_ATLAS_TOO_SMALL,
};
enum EnvironmentMode {
ENVIRONMENT_MODE_DISABLED,
ENVIRONMENT_MODE_SCENE,
ENVIRONMENT_MODE_CUSTOM_SKY,
ENVIRONMENT_MODE_CUSTOM_COLOR,
};
private:
BakeQuality bake_quality = BAKE_QUALITY_MEDIUM;
bool use_denoiser = true;
float denoiser_strength = 0.1f;
int denoiser_range = 10;
int bounces = 3;
float bounce_indirect_energy = 1.0;
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;
Color environment_custom_color = Color(1, 1, 1);
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;
Light3D *light = nullptr;
};
struct MeshesFound {
Transform3D xform;
NodePath node_path;
int32_t subindex = 0;
Ref<Mesh> mesh;
float lightmap_scale = 0.0;
Vector<Ref<Material>> overrides;
};
void _find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &meshes, Vector<LightsFound> &lights, Vector<Vector3> &probes);
void _assign_lightmaps();
void _clear_lightmaps();
struct BakeTimeData {
String text;
int pass = 0;
uint64_t last_step = 0;
};
struct BSPSimplex {
int vertices[4] = {};
int planes[4] = {};
};
struct BSPNode {
static const int32_t EMPTY_LEAF = INT32_MIN;
Plane plane;
int32_t over = EMPTY_LEAF;
int32_t under = EMPTY_LEAF;
};
int _bsp_get_simplex_side(const Vector<Vector3> &p_points, const LocalVector<BSPSimplex> &p_simplices, const Plane &p_plane, uint32_t p_simplex) const;
int32_t _compute_bsp_tree(const Vector<Vector3> &p_points, const LocalVector<Plane> &p_planes, LocalVector<int32_t> &planes_tested, const LocalVector<BSPSimplex> &p_simplices, const LocalVector<int32_t> &p_simplex_indices, LocalVector<BSPNode> &bsp_nodes);
struct BakeStepUD {
Lightmapper::BakeStepFunc func;
void *ud = nullptr;
float from_percent = 0.0;
float to_percent = 0.0;
};
static bool _lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh);
struct GenProbesOctree {
Vector3i offset;
uint32_t size = 0;
GenProbesOctree *children[8] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
~GenProbesOctree() {
for (int i = 0; i < 8; i++) {
if (children[i] != nullptr) {
memdelete(children[i]);
}
}
}
};
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();
void _notification(int p_what);
public:
void set_light_data(const Ref<LightmapGIData> &p_data);
Ref<LightmapGIData> get_light_data() const;
void set_bake_quality(BakeQuality p_quality);
BakeQuality get_bake_quality() const;
void set_use_denoiser(bool p_enable);
bool is_using_denoiser() const;
void set_denoiser_strength(float p_denoiser_strength);
float get_denoiser_strength() const;
void set_denoiser_range(int p_denoiser_range);
int get_denoiser_range() const;
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;
void set_interior(bool p_interior);
bool is_interior() const;
void set_environment_mode(EnvironmentMode p_mode);
EnvironmentMode get_environment_mode() const;
void set_environment_custom_sky(const Ref<Sky> &p_sky);
Ref<Sky> get_environment_custom_sky() const;
void set_environment_custom_color(const Color &p_color);
Color get_environment_custom_color() const;
void set_environment_custom_energy(float p_energy);
float get_environment_custom_energy() const;
void set_bounces(int p_bounces);
int get_bounces() const;
void set_bounce_indirect_energy(float p_indirect_energy);
float get_bounce_indirect_energy() const;
void set_bias(float p_bias);
float get_bias() const;
void set_texel_scale(float p_multiplier);
float get_texel_scale() const;
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;
void set_camera_attributes(const Ref<CameraAttributes> &p_camera_attributes);
Ref<CameraAttributes> get_camera_attributes() const;
AABB get_aabb() const override;
BakeError bake(Node *p_from_node, String p_image_data_path = "", Lightmapper::BakeStepFunc p_bake_step = nullptr, void *p_bake_userdata = nullptr);
virtual PackedStringArray get_configuration_warnings() const override;
LightmapGI();
};
VARIANT_ENUM_CAST(LightmapGIData::ShadowmaskMode);
VARIANT_ENUM_CAST(LightmapGI::BakeQuality);
VARIANT_ENUM_CAST(LightmapGI::GenerateProbes);
VARIANT_ENUM_CAST(LightmapGI::BakeError);
VARIANT_ENUM_CAST(LightmapGI::EnvironmentMode);

View File

@@ -0,0 +1,34 @@
/**************************************************************************/
/* lightmap_probe.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 "lightmap_probe.h"
LightmapProbe::LightmapProbe() {
}

39
scene/3d/lightmap_probe.h Normal file
View File

@@ -0,0 +1,39 @@
/**************************************************************************/
/* lightmap_probe.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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
class LightmapProbe : public Node3D {
GDCLASS(LightmapProbe, Node3D)
public:
LightmapProbe();
};

76
scene/3d/lightmapper.cpp Normal file
View File

@@ -0,0 +1,76 @@
/**************************************************************************/
/* lightmapper.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 "lightmapper.h"
LightmapDenoiser *(*LightmapDenoiser::create_function)() = nullptr;
Ref<LightmapDenoiser> LightmapDenoiser::create() {
if (create_function) {
return Ref<LightmapDenoiser>(create_function());
}
return Ref<LightmapDenoiser>();
}
LightmapRaycaster *(*LightmapRaycaster::create_function)() = nullptr;
Ref<LightmapRaycaster> LightmapRaycaster::create() {
if (create_function) {
return Ref<LightmapRaycaster>(create_function());
}
return Ref<LightmapRaycaster>();
}
Lightmapper::CreateFunc Lightmapper::create_custom = nullptr;
Lightmapper::CreateFunc Lightmapper::create_gpu = nullptr;
Lightmapper::CreateFunc Lightmapper::create_cpu = nullptr;
Ref<Lightmapper> Lightmapper::create() {
Lightmapper *lm = nullptr;
if (create_custom) {
lm = create_custom();
}
if (!lm && create_gpu) {
lm = create_gpu();
}
if (!lm && create_cpu) {
lm = create_cpu();
}
if (!lm) {
return Ref<Lightmapper>();
} else {
return Ref<Lightmapper>(lm);
}
}
Lightmapper::Lightmapper() {
}

191
scene/3d/lightmapper.h Normal file
View File

@@ -0,0 +1,191 @@
/**************************************************************************/
/* lightmapper.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. */
/**************************************************************************/
#pragma once
#include "core/object/ref_counted.h"
class Image;
class LightmapDenoiser : public RefCounted {
GDCLASS(LightmapDenoiser, RefCounted)
protected:
static LightmapDenoiser *(*create_function)();
public:
virtual Ref<Image> denoise_image(const Ref<Image> &p_image) = 0;
static Ref<LightmapDenoiser> create();
};
class LightmapRaycaster : public RefCounted {
GDCLASS(LightmapRaycaster, RefCounted)
protected:
static LightmapRaycaster *(*create_function)();
public:
// Compatible with embree4 rays.
struct alignas(16) Ray {
const static unsigned int INVALID_GEOMETRY_ID = ((unsigned int)-1); // from rtcore_common.h
/*! Default construction does nothing. */
_FORCE_INLINE_ Ray() :
geomID(INVALID_GEOMETRY_ID) {}
/*! Constructs a ray from origin, direction, and ray segment. Near
* has to be smaller than far. */
_FORCE_INLINE_ Ray(const Vector3 &p_org,
const Vector3 &p_dir,
float p_tnear = 0.0f,
float p_tfar = Math::INF) :
org(p_org),
tnear(p_tnear),
dir(p_dir),
time(0.0f),
tfar(p_tfar),
mask(-1),
u(0.0),
v(0.0),
primID(INVALID_GEOMETRY_ID),
geomID(INVALID_GEOMETRY_ID),
instID(INVALID_GEOMETRY_ID) {}
/*! Tests if we hit something. */
_FORCE_INLINE_ explicit operator bool() const {
return geomID != INVALID_GEOMETRY_ID;
}
public:
Vector3 org; //!< Ray origin + tnear
float tnear; //!< Start of ray segment
Vector3 dir; //!< Ray direction + tfar
float time; //!< Time of this ray for motion blur.
float tfar; //!< End of ray segment
unsigned int mask; //!< used to mask out objects during traversal
unsigned int id; //!< ray ID
unsigned int flags; //!< ray flags
Vector3 normal; //!< Not normalized geometry normal
float u; //!< Barycentric u coordinate of hit
float v; //!< Barycentric v coordinate of hit
unsigned int primID; //!< primitive ID
unsigned int geomID; //!< geometry ID
unsigned int instID; //!< instance ID
};
virtual bool intersect(Ray &p_ray) = 0;
virtual void intersect(Vector<Ray> &r_rays) = 0;
virtual void add_mesh(const Vector<Vector3> &p_vertices, const Vector<Vector3> &p_normals, const Vector<Vector2> &p_uv2s, unsigned int p_id) = 0;
virtual void set_mesh_alpha_texture(Ref<Image> p_alpha_texture, unsigned int p_id) = 0;
virtual void commit() = 0;
virtual void set_mesh_filter(const HashSet<int> &p_mesh_ids) = 0;
virtual void clear_mesh_filter() = 0;
static Ref<LightmapRaycaster> create();
};
class Lightmapper : public RefCounted {
GDCLASS(Lightmapper, RefCounted)
public:
enum GenerateProbes {
GENERATE_PROBES_DISABLED,
GENERATE_PROBES_SUBDIV_4,
GENERATE_PROBES_SUBDIV_8,
GENERATE_PROBES_SUBDIV_16,
GENERATE_PROBES_SUBDIV_32,
};
enum LightType {
LIGHT_TYPE_DIRECTIONAL,
LIGHT_TYPE_OMNI,
LIGHT_TYPE_SPOT
};
enum BakeError {
BAKE_OK,
BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE,
BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES,
BAKE_ERROR_ATLAS_TOO_SMALL,
BAKE_ERROR_USER_ABORTED,
};
enum BakeQuality {
BAKE_QUALITY_LOW,
BAKE_QUALITY_MEDIUM,
BAKE_QUALITY_HIGH,
BAKE_QUALITY_ULTRA,
};
typedef Lightmapper *(*CreateFunc)();
static CreateFunc create_custom;
static CreateFunc create_gpu;
static CreateFunc create_cpu;
protected:
public:
typedef bool (*BakeStepFunc)(float, const String &, void *, bool); //step index, step total, step description, userdata
struct MeshData {
//triangle data
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(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_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;
virtual int get_bake_mesh_texture_slice(int p_index) const = 0;
virtual int get_bake_probe_count() const = 0;
virtual Vector3 get_bake_probe_point(int p_probe) const = 0;
virtual Vector<Color> get_bake_probe_sh(int p_probe) const = 0;
static Ref<Lightmapper> create();
Lightmapper();
};

View File

@@ -0,0 +1,780 @@
/**************************************************************************/
/* 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 {
if (Engine::get_singleton()->is_editor_hint() && (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::_validate_bone_names() {
// Prior bone name.
if (!bone_name.is_empty()) {
set_bone_name(bone_name);
} else if (bone != -1) {
set_bone(bone);
}
if (!origin_bone_name.is_empty()) {
set_origin_bone_name(origin_bone_name);
} else if (origin_bone != -1) {
set_origin_bone(origin_bone);
}
}
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(double p_delta) {
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.translate_local(skeleton->get_bone_rest(bone).origin);
} else {
bone_rest_space = skeleton->get_global_transform() * skeleton->get_bone_global_pose(parent_bone);
bone_rest_space.translate_local(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 && std::signbit(prev_forward_vector[secondary_rotation_axis]) != std::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) {
remaining = MAX(0, remaining - time_step * p_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 std::signbit(p_prev[p_flipping_axis]) != std::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 = std::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 (std::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 (std::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;
}

View File

@@ -0,0 +1,193 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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;
virtual void _validate_bone_names() override;
static void _bind_methods();
virtual void _process_modification(double p_delta) 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);

53
scene/3d/marker_3d.cpp Normal file
View File

@@ -0,0 +1,53 @@
/**************************************************************************/
/* marker_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 "marker_3d.h"
void Marker3D::set_gizmo_extents(real_t p_extents) {
if (Math::is_equal_approx(gizmo_extents, p_extents)) {
return;
}
gizmo_extents = p_extents;
update_gizmos();
}
real_t Marker3D::get_gizmo_extents() const {
return gizmo_extents;
}
void Marker3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_gizmo_extents", "extents"), &Marker3D::set_gizmo_extents);
ClassDB::bind_method(D_METHOD("get_gizmo_extents"), &Marker3D::get_gizmo_extents);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gizmo_extents", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater,suffix:m"), "set_gizmo_extents", "get_gizmo_extents");
}
Marker3D::Marker3D() {
}

48
scene/3d/marker_3d.h Normal file
View File

@@ -0,0 +1,48 @@
/**************************************************************************/
/* marker_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
class Marker3D : public Node3D {
GDCLASS(Marker3D, Node3D);
real_t gizmo_extents = 0.25;
protected:
static void _bind_methods();
public:
void set_gizmo_extents(real_t p_extents);
real_t get_gizmo_extents() const;
Marker3D();
};

View File

@@ -0,0 +1,931 @@
/**************************************************************************/
/* mesh_instance_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 "mesh_instance_3d.h"
#include "scene/3d/skeleton_3d.h"
#ifndef PHYSICS_3D_DISABLED
#include "scene/3d/physics/collision_shape_3d.h"
#include "scene/3d/physics/static_body_3d.h"
#include "scene/resources/3d/concave_polygon_shape_3d.h"
#include "scene/resources/3d/convex_polygon_shape_3d.h"
#endif // PHYSICS_3D_DISABLED
#ifndef NAVIGATION_3D_DISABLED
#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;
#endif // NAVIGATION_3D_DISABLED
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.
if (!get_instance().is_valid()) {
return false;
}
HashMap<StringName, int>::Iterator E = blend_shape_properties.find(p_name);
if (E) {
set_blend_shape_value(E->value, p_value);
return true;
}
if (p_name.operator String().begins_with("surface_material_override/")) {
int idx = p_name.operator String().get_slicec('/', 1).to_int();
if (idx >= surface_override_materials.size() || idx < 0) {
return false;
}
set_surface_override_material(idx, p_value);
return true;
}
return false;
}
bool MeshInstance3D::_get(const StringName &p_name, Variant &r_ret) const {
if (!get_instance().is_valid()) {
return false;
}
HashMap<StringName, int>::ConstIterator E = blend_shape_properties.find(p_name);
if (E) {
r_ret = get_blend_shape_value(E->value);
return true;
}
if (p_name.operator String().begins_with("surface_material_override/")) {
int idx = p_name.operator String().get_slicec('/', 1).to_int();
if (idx >= surface_override_materials.size() || idx < 0) {
return false;
}
r_ret = surface_override_materials[idx];
return true;
}
return false;
}
void MeshInstance3D::_get_property_list(List<PropertyInfo> *p_list) const {
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"));
}
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));
}
}
}
void MeshInstance3D::set_mesh(const Ref<Mesh> &p_mesh) {
if (mesh == p_mesh) {
return;
}
if (mesh.is_valid()) {
mesh->disconnect_changed(callable_mp(this, &MeshInstance3D::_mesh_changed));
}
mesh = p_mesh;
if (mesh.is_valid()) {
// If mesh is a PrimitiveMesh, calling get_rid on it can trigger a changed callback
// so do this before connecting _mesh_changed.
set_base(mesh->get_rid());
mesh->connect_changed(callable_mp(this, &MeshInstance3D::_mesh_changed));
_mesh_changed();
} else {
blend_shape_tracks.clear();
blend_shape_properties.clear();
set_base(RID());
update_gizmos();
}
notify_property_list_changed();
}
Ref<Mesh> MeshInstance3D::get_mesh() const {
return mesh;
}
int MeshInstance3D::get_blend_shape_count() const {
if (mesh.is_null()) {
return 0;
}
return mesh->get_blend_shape_count();
}
int MeshInstance3D::find_blend_shape_by_name(const StringName &p_name) {
if (mesh.is_null()) {
return -1;
}
for (int i = 0; i < mesh->get_blend_shape_count(); i++) {
if (mesh->get_blend_shape_name(i) == p_name) {
return i;
}
}
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());
blend_shape_tracks[p_blend_shape] = p_value;
RenderingServer::get_singleton()->instance_set_blend_shape_weight(get_instance(), p_blend_shape, p_value);
}
void MeshInstance3D::_resolve_skeleton_path() {
Ref<SkinReference> new_skin_reference;
if (!skeleton_path.is_empty()) {
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(get_node_or_null(skeleton_path)); // skeleton_path may be outdated when reparenting.
if (skeleton) {
if (skin_internal.is_null()) {
new_skin_reference = skeleton->register_skin(skeleton->create_skin_from_rest_transforms());
//a skin was created for us
skin_internal = new_skin_reference->get_skin();
notify_property_list_changed();
} else {
new_skin_reference = skeleton->register_skin(skin_internal);
}
}
}
skin_ref = new_skin_reference;
if (skin_ref.is_valid()) {
RenderingServer::get_singleton()->instance_attach_skeleton(get_instance(), skin_ref->get_skeleton());
} else {
RenderingServer::get_singleton()->instance_attach_skeleton(get_instance(), RID());
}
}
void MeshInstance3D::set_skin(const Ref<Skin> &p_skin) {
skin_internal = p_skin;
skin = p_skin;
if (!is_inside_tree()) {
return;
}
_resolve_skeleton_path();
}
Ref<Skin> MeshInstance3D::get_skin() const {
return skin;
}
Ref<SkinReference> MeshInstance3D::get_skin_reference() const {
return skin_ref;
}
void MeshInstance3D::set_skeleton_path(const NodePath &p_skeleton) {
skeleton_path = p_skeleton;
if (!is_inside_tree()) {
return;
}
_resolve_skeleton_path();
}
NodePath MeshInstance3D::get_skeleton_path() {
return skeleton_path;
}
AABB MeshInstance3D::get_aabb() const {
if (mesh.is_valid()) {
return mesh->get_aabb();
}
return AABB();
}
#ifndef PHYSICS_3D_DISABLED
Node *MeshInstance3D::create_trimesh_collision_node() {
if (mesh.is_null()) {
return nullptr;
}
Ref<ConcavePolygonShape3D> shape = mesh->create_trimesh_shape();
if (shape.is_null()) {
return nullptr;
}
StaticBody3D *static_body = memnew(StaticBody3D);
CollisionShape3D *cshape = memnew(CollisionShape3D);
cshape->set_shape(shape);
static_body->add_child(cshape, true);
return static_body;
}
void MeshInstance3D::create_trimesh_collision() {
StaticBody3D *static_body = Object::cast_to<StaticBody3D>(create_trimesh_collision_node());
ERR_FAIL_NULL(static_body);
static_body->set_name(String(get_name()) + "_col");
add_child(static_body, true);
if (get_owner()) {
CollisionShape3D *cshape = Object::cast_to<CollisionShape3D>(static_body->get_child(0));
static_body->set_owner(get_owner());
cshape->set_owner(get_owner());
}
}
Node *MeshInstance3D::create_convex_collision_node(bool p_clean, bool p_simplify) {
if (mesh.is_null()) {
return nullptr;
}
Ref<ConvexPolygonShape3D> shape = mesh->create_convex_shape(p_clean, p_simplify);
if (shape.is_null()) {
return nullptr;
}
StaticBody3D *static_body = memnew(StaticBody3D);
CollisionShape3D *cshape = memnew(CollisionShape3D);
cshape->set_shape(shape);
static_body->add_child(cshape, true);
return static_body;
}
void MeshInstance3D::create_convex_collision(bool p_clean, bool p_simplify) {
StaticBody3D *static_body = Object::cast_to<StaticBody3D>(create_convex_collision_node(p_clean, p_simplify));
ERR_FAIL_NULL(static_body);
static_body->set_name(String(get_name()) + "_col");
add_child(static_body, true);
if (get_owner()) {
CollisionShape3D *cshape = Object::cast_to<CollisionShape3D>(static_body->get_child(0));
static_body->set_owner(get_owner());
cshape->set_owner(get_owner());
}
}
Node *MeshInstance3D::create_multiple_convex_collisions_node(const Ref<MeshConvexDecompositionSettings> &p_settings) {
if (mesh.is_null()) {
return nullptr;
}
Ref<MeshConvexDecompositionSettings> settings;
if (p_settings.is_valid()) {
settings = p_settings;
} else {
settings.instantiate();
}
Vector<Ref<Shape3D>> shapes = mesh->convex_decompose(settings);
if (!shapes.size()) {
return nullptr;
}
StaticBody3D *static_body = memnew(StaticBody3D);
for (int i = 0; i < shapes.size(); i++) {
CollisionShape3D *cshape = memnew(CollisionShape3D);
cshape->set_shape(shapes[i]);
static_body->add_child(cshape, true);
}
return static_body;
}
void MeshInstance3D::create_multiple_convex_collisions(const Ref<MeshConvexDecompositionSettings> &p_settings) {
StaticBody3D *static_body = Object::cast_to<StaticBody3D>(create_multiple_convex_collisions_node(p_settings));
ERR_FAIL_NULL(static_body);
static_body->set_name(String(get_name()) + "_col");
add_child(static_body, true);
if (get_owner()) {
static_body->set_owner(get_owner());
int count = static_body->get_child_count();
for (int i = 0; i < count; i++) {
CollisionShape3D *cshape = Object::cast_to<CollisionShape3D>(static_body->get_child(i));
cshape->set_owner(get_owner());
}
}
}
#endif // PHYSICS_3D_DISABLED
void MeshInstance3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
_resolve_skeleton_path();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
if (mesh.is_valid()) {
mesh->notification(NOTIFICATION_TRANSLATION_CHANGED);
}
} break;
}
}
int MeshInstance3D::get_surface_override_material_count() const {
return surface_override_materials.size();
}
void MeshInstance3D::set_surface_override_material(int p_surface, const Ref<Material> &p_material) {
ERR_FAIL_INDEX(p_surface, surface_override_materials.size());
surface_override_materials.write[p_surface] = p_material;
if (surface_override_materials[p_surface].is_valid()) {
RS::get_singleton()->instance_set_surface_override_material(get_instance(), p_surface, surface_override_materials[p_surface]->get_rid());
} else {
RS::get_singleton()->instance_set_surface_override_material(get_instance(), p_surface, RID());
}
}
Ref<Material> MeshInstance3D::get_surface_override_material(int p_surface) const {
ERR_FAIL_INDEX_V(p_surface, surface_override_materials.size(), Ref<Material>());
return surface_override_materials[p_surface];
}
Ref<Material> MeshInstance3D::get_active_material(int p_surface) const {
Ref<Material> mat_override = get_material_override();
if (mat_override.is_valid()) {
return mat_override;
}
Ref<Mesh> m = get_mesh();
if (m.is_null() || m->get_surface_count() == 0) {
return Ref<Material>();
}
Ref<Material> surface_material = get_surface_override_material(p_surface);
if (surface_material.is_valid()) {
return surface_material;
}
return m->surface_get_material(p_surface);
}
void MeshInstance3D::_mesh_changed() {
ERR_FAIL_COND(mesh.is_null());
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());
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);
}
}
}
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());
}
}
update_gizmos();
}
MeshInstance3D *MeshInstance3D::create_debug_tangents_node() {
Vector<Vector3> lines;
Vector<Color> colors;
Ref<Mesh> m = get_mesh();
if (m.is_null()) {
return nullptr;
}
for (int i = 0; i < m->get_surface_count(); i++) {
Array arrays = m->surface_get_arrays(i);
ERR_CONTINUE(arrays.size() != Mesh::ARRAY_MAX);
Vector<Vector3> verts = arrays[Mesh::ARRAY_VERTEX];
Vector<Vector3> norms = arrays[Mesh::ARRAY_NORMAL];
if (norms.is_empty()) {
continue;
}
Vector<float> tangents = arrays[Mesh::ARRAY_TANGENT];
if (tangents.is_empty()) {
continue;
}
for (int j = 0; j < verts.size(); j++) {
Vector3 v = verts[j];
Vector3 n = norms[j];
Vector3 t = Vector3(tangents[j * 4 + 0], tangents[j * 4 + 1], tangents[j * 4 + 2]);
Vector3 b = (n.cross(t)).normalized() * tangents[j * 4 + 3];
lines.push_back(v); //normal
colors.push_back(Color(0, 0, 1)); //color
lines.push_back(v + n * 0.04); //normal
colors.push_back(Color(0, 0, 1)); //color
lines.push_back(v); //tangent
colors.push_back(Color(1, 0, 0)); //color
lines.push_back(v + t * 0.04); //tangent
colors.push_back(Color(1, 0, 0)); //color
lines.push_back(v); //binormal
colors.push_back(Color(0, 1, 0)); //color
lines.push_back(v + b * 0.04); //binormal
colors.push_back(Color(0, 1, 0)); //color
}
}
if (lines.size()) {
Ref<StandardMaterial3D> sm;
sm.instantiate();
sm->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
sm->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
sm->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
sm->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
Ref<ArrayMesh> am;
am.instantiate();
Array a;
a.resize(Mesh::ARRAY_MAX);
a[Mesh::ARRAY_VERTEX] = lines;
a[Mesh::ARRAY_COLOR] = colors;
am->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, a);
am->surface_set_material(0, sm);
MeshInstance3D *mi = memnew(MeshInstance3D);
mi->set_mesh(am);
mi->set_name("DebugTangents");
return mi;
}
return nullptr;
}
void MeshInstance3D::create_debug_tangents() {
MeshInstance3D *mi = create_debug_tangents_node();
if (!mi) {
return;
}
add_child(mi, true);
if (is_inside_tree() && this == get_tree()->get_edited_scene_root()) {
mi->set_owner(this);
} else {
mi->set_owner(get_owner());
}
}
bool MeshInstance3D::_property_can_revert(const StringName &p_name) const {
HashMap<StringName, int>::ConstIterator E = blend_shape_properties.find(p_name);
if (E) {
return true;
}
return false;
}
bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_property) const {
HashMap<StringName, int>::ConstIterator E = blend_shape_properties.find(p_name);
if (E) {
r_property = 0.0f;
return true;
}
return false;
}
Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(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(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;
} else {
bake_mesh.instantiate();
}
Mesh::BlendShapeMode blend_shape_mode = source_mesh->get_blend_shape_mode();
int mesh_surface_count = source_mesh->get_surface_count();
bake_mesh->clear_surfaces();
bake_mesh->set_blend_shape_mode(blend_shape_mode);
for (int surface_index = 0; surface_index < mesh_surface_count; surface_index++) {
uint32_t surface_format = source_mesh->surface_get_format(surface_index);
ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_VERTEX));
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];
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) {
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();
Vector3 *lerped_vertices_ptrw = lerped_vertex_array.ptrw();
Vector3 *lerped_normals_ptrw = lerped_normal_array.ptrw();
float *lerped_tangents_ptrw = lerped_tangent_array.ptrw();
const Array &blendshapes_mesh_arrays = source_mesh->surface_get_blend_shape_arrays(surface_index);
int blend_shape_count = source_mesh->get_blend_shape_count();
ERR_FAIL_COND_V(blendshapes_mesh_arrays.size() != blend_shape_count, Ref<ArrayMesh>());
for (int blendshape_index = 0; blendshape_index < blend_shape_count; blendshape_index++) {
float blend_weight = get_blend_shape_value(blendshape_index);
if (std::abs(blend_weight) <= 0.0001) {
continue;
}
const Array &blendshape_mesh_arrays = blendshapes_mesh_arrays[blendshape_index];
const Vector<Vector3> &blendshape_vertex_array = blendshape_mesh_arrays[Mesh::ARRAY_VERTEX];
const Vector<Vector3> &blendshape_normal_array = blendshape_mesh_arrays[Mesh::ARRAY_NORMAL];
const Vector<float> &blendshape_tangent_array = blendshape_mesh_arrays[Mesh::ARRAY_TANGENT];
ERR_FAIL_COND_V(source_mesh_vertex_array.size() != blendshape_vertex_array.size(), Ref<ArrayMesh>());
ERR_FAIL_COND_V(source_mesh_normal_array.size() != blendshape_normal_array.size(), Ref<ArrayMesh>());
ERR_FAIL_COND_V(source_mesh_tangent_array.size() != blendshape_tangent_array.size(), Ref<ArrayMesh>());
const Vector3 *blendshape_vertices_ptr = blendshape_vertex_array.ptr();
const Vector3 *blendshape_normals_ptr = blendshape_normal_array.ptr();
const float *blendshape_tangents_ptr = blendshape_tangent_array.ptr();
if (blend_shape_mode == Mesh::BLEND_SHAPE_MODE_NORMALIZED) {
for (int i = 0; i < source_mesh_vertex_array.size(); i++) {
const Vector3 &source_vertex = source_vertices_ptr[i];
const Vector3 &blendshape_vertex = blendshape_vertices_ptr[i];
Vector3 lerped_vertex = source_vertex.lerp(blendshape_vertex, blend_weight) - source_vertex;
lerped_vertices_ptrw[i] += lerped_vertex;
if (use_normal_array) {
const Vector3 &source_normal = source_normals_ptr[i];
const Vector3 &blendshape_normal = blendshape_normals_ptr[i];
Vector3 lerped_normal = source_normal.lerp(blendshape_normal, blend_weight) - source_normal;
lerped_normals_ptrw[i] += lerped_normal;
}
if (use_tangent_array) {
int tangent_index = i * 4;
const Vector4 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]);
const Vector4 blendshape_tangent = Vector4(
blendshape_tangents_ptr[tangent_index],
blendshape_tangents_ptr[tangent_index + 1],
blendshape_tangents_ptr[tangent_index + 2],
blendshape_tangents_ptr[tangent_index + 3]);
Vector4 lerped_tangent = source_tangent.lerp(blendshape_tangent, blend_weight);
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;
lerped_tangents_ptrw[tangent_index + 3] += lerped_tangent.w;
}
}
} else if (blend_shape_mode == Mesh::BLEND_SHAPE_MODE_RELATIVE) {
for (int i = 0; i < source_mesh_vertex_array.size(); i++) {
const Vector3 &blendshape_vertex = blendshape_vertices_ptr[i];
lerped_vertices_ptrw[i] += blendshape_vertex * blend_weight;
if (use_normal_array) {
const Vector3 &blendshape_normal = blendshape_normals_ptr[i];
lerped_normals_ptrw[i] += blendshape_normal * blend_weight;
}
if (use_tangent_array) {
int tangent_index = i * 4;
const Vector4 blendshape_tangent = Vector4(
blendshape_tangents_ptr[tangent_index],
blendshape_tangents_ptr[tangent_index + 1],
blendshape_tangents_ptr[tangent_index + 2],
blendshape_tangents_ptr[tangent_index + 3]);
Vector4 lerped_tangent = blendshape_tangent * blend_weight;
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;
lerped_tangents_ptrw[tangent_index + 3] += lerped_tangent.w;
}
}
}
}
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<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>();
}
#ifndef NAVIGATION_3D_DISABLED
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());
}
}
}
#endif // NAVIGATION_3D_DISABLED
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);
ClassDB::bind_method(D_METHOD("set_skeleton_path", "skeleton_path"), &MeshInstance3D::set_skeleton_path);
ClassDB::bind_method(D_METHOD("get_skeleton_path"), &MeshInstance3D::get_skeleton_path);
ClassDB::bind_method(D_METHOD("set_skin", "skin"), &MeshInstance3D::set_skin);
ClassDB::bind_method(D_METHOD("get_skin"), &MeshInstance3D::get_skin);
ClassDB::bind_method(D_METHOD("get_skin_reference"), &MeshInstance3D::get_skin_reference);
ClassDB::bind_method(D_METHOD("get_surface_override_material_count"), &MeshInstance3D::get_surface_override_material_count);
ClassDB::bind_method(D_METHOD("set_surface_override_material", "surface", "material"), &MeshInstance3D::set_surface_override_material);
ClassDB::bind_method(D_METHOD("get_surface_override_material", "surface"), &MeshInstance3D::get_surface_override_material);
ClassDB::bind_method(D_METHOD("get_active_material", "surface"), &MeshInstance3D::get_active_material);
#ifndef PHYSICS_3D_DISABLED
ClassDB::bind_method(D_METHOD("create_trimesh_collision"), &MeshInstance3D::create_trimesh_collision);
ClassDB::set_method_flags("MeshInstance3D", "create_trimesh_collision", METHOD_FLAGS_DEFAULT);
ClassDB::bind_method(D_METHOD("create_convex_collision", "clean", "simplify"), &MeshInstance3D::create_convex_collision, DEFVAL(true), DEFVAL(false));
ClassDB::set_method_flags("MeshInstance3D", "create_convex_collision", METHOD_FLAGS_DEFAULT);
ClassDB::bind_method(D_METHOD("create_multiple_convex_collisions", "settings"), &MeshInstance3D::create_multiple_convex_collisions, DEFVAL(Ref<MeshConvexDecompositionSettings>()));
ClassDB::set_method_flags("MeshInstance3D", "create_multiple_convex_collisions", METHOD_FLAGS_DEFAULT);
#endif // PHYSICS_3D_DISABLED
ClassDB::bind_method(D_METHOD("get_blend_shape_count"), &MeshInstance3D::get_blend_shape_count);
ClassDB::bind_method(D_METHOD("find_blend_shape_by_name", "name"), &MeshInstance3D::find_blend_shape_by_name);
ClassDB::bind_method(D_METHOD("get_blend_shape_value", "blend_shape_idx"), &MeshInstance3D::get_blend_shape_value);
ClassDB::bind_method(D_METHOD("set_blend_shape_value", "blend_shape_idx", "value"), &MeshInstance3D::set_blend_shape_value);
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", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skin", PROPERTY_HINT_RESOURCE_TYPE, "Skin"), "set_skin", "get_skin");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_skeleton_path", "get_skeleton_path");
ADD_GROUP("", "");
}
MeshInstance3D::MeshInstance3D() {
}
MeshInstance3D::~MeshInstance3D() {
}

129
scene/3d/mesh_instance_3d.h Normal file
View File

@@ -0,0 +1,129 @@
/**************************************************************************/
/* mesh_instance_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. */
/**************************************************************************/
#pragma once
#include "core/templates/local_vector.h"
#include "scene/3d/visual_instance_3d.h"
#ifndef NAVIGATION_3D_DISABLED
class NavigationMesh;
class NavigationMeshSourceGeometryData3D;
#endif // NAVIGATION_3D_DISABLED
class Skin;
class SkinReference;
class MeshInstance3D : public GeometryInstance3D {
GDCLASS(MeshInstance3D, GeometryInstance3D);
protected:
Ref<Mesh> mesh;
Ref<Skin> skin;
Ref<Skin> skin_internal;
Ref<SkinReference> skin_ref;
NodePath skeleton_path = NodePath("..");
LocalVector<float> blend_shape_tracks;
HashMap<StringName, int> blend_shape_properties;
Vector<Ref<Material>> surface_override_materials;
void _mesh_changed();
void _resolve_skeleton_path();
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
bool surface_index_0 = false;
void _notification(int p_what);
static void _bind_methods();
bool _property_can_revert(const StringName &p_name) const;
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
public:
void set_mesh(const Ref<Mesh> &p_mesh);
Ref<Mesh> get_mesh() const;
void set_skin(const Ref<Skin> &p_skin);
Ref<Skin> get_skin() const;
void set_skeleton_path(const NodePath &p_skeleton);
NodePath get_skeleton_path();
Ref<SkinReference> get_skin_reference() const;
int get_blend_shape_count() const;
int find_blend_shape_by_name(const StringName &p_name);
float get_blend_shape_value(int p_blend_shape) const;
void set_blend_shape_value(int p_blend_shape, float p_value);
int get_surface_override_material_count() const;
void set_surface_override_material(int p_surface, const Ref<Material> &p_material);
Ref<Material> get_surface_override_material(int p_surface) const;
Ref<Material> get_active_material(int p_surface) const;
#ifndef PHYSICS_3D_DISABLED
Node *create_trimesh_collision_node();
void create_trimesh_collision();
Node *create_convex_collision_node(bool p_clean = true, bool p_simplify = false);
void create_convex_collision(bool p_clean = true, bool p_simplify = false);
Node *create_multiple_convex_collisions_node(const Ref<MeshConvexDecompositionSettings> &p_settings = Ref<MeshConvexDecompositionSettings>());
void create_multiple_convex_collisions(const Ref<MeshConvexDecompositionSettings> &p_settings = Ref<MeshConvexDecompositionSettings>());
#endif // PHYSICS_3D_DISABLED
MeshInstance3D *create_debug_tangents_node();
void create_debug_tangents();
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;
#ifndef NAVIGATION_3D_DISABLED
private:
static Callable _navmesh_source_geometry_parsing_callback;
static RID _navmesh_source_geometry_parser;
#endif // NAVIGATION_3D_DISABLED
public:
#ifndef NAVIGATION_3D_DISABLED
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 // NAVIGATION_3D_DISABLED
MeshInstance3D();
~MeshInstance3D();
};

View File

@@ -0,0 +1,111 @@
/**************************************************************************/
/* modifier_bone_target_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 "modifier_bone_target_3d.h"
void ModifierBoneTarget3D::_validate_bone_names() {
// Prior bone name.
if (!bone_name.is_empty()) {
set_bone_name(bone_name);
} else if (bone != -1) {
set_bone(bone);
}
}
void ModifierBoneTarget3D::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 ModifierBoneTarget3D::get_bone_name() const {
return bone_name;
}
void ModifierBoneTarget3D::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 ModifierBoneTarget3D::get_bone() const {
return bone;
}
void ModifierBoneTarget3D::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "influence") {
p_property.usage = PROPERTY_USAGE_READ_ONLY;
}
if (!Engine::get_singleton()->is_editor_hint()) {
return;
}
if (p_property.name == "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 = "";
}
}
}
void ModifierBoneTarget3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &ModifierBoneTarget3D::set_bone_name);
ClassDB::bind_method(D_METHOD("get_bone_name"), &ModifierBoneTarget3D::get_bone_name);
ClassDB::bind_method(D_METHOD("set_bone", "bone"), &ModifierBoneTarget3D::set_bone);
ClassDB::bind_method(D_METHOD("get_bone"), &ModifierBoneTarget3D::get_bone);
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");
}
void ModifierBoneTarget3D::_process_modification(double p_delta) {
if (!is_inside_tree()) {
return;
}
Skeleton3D *skeleton = get_skeleton();
if (!skeleton || bone < 0 || bone >= skeleton->get_bone_count()) {
return;
}
set_transform(skeleton->get_bone_global_pose(bone));
}

View File

@@ -0,0 +1,52 @@
/**************************************************************************/
/* modifier_bone_target_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/skeleton_modifier_3d.h"
class ModifierBoneTarget3D : public SkeletonModifier3D {
GDCLASS(ModifierBoneTarget3D, SkeletonModifier3D);
String bone_name;
int bone = -1;
protected:
void _validate_property(PropertyInfo &p_property) const;
virtual void _validate_bone_names() override;
static void _bind_methods();
virtual void _process_modification(double p_delta) 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;
};

View File

@@ -0,0 +1,149 @@
/**************************************************************************/
/* multimesh_instance_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 "multimesh_instance_3d.h"
#ifndef NAVIGATION_3D_DISABLED
#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;
#endif // NAVIGATION_3D_DISABLED
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());
}
}
Ref<MultiMesh> MultiMeshInstance3D::get_multimesh() const {
return multimesh;
}
Array MultiMeshInstance3D::get_meshes() const {
if (multimesh.is_null() || multimesh->get_mesh().is_null() || multimesh->get_transform_format() != MultiMesh::TransformFormat::TRANSFORM_3D) {
return Array();
}
int count = multimesh->get_visible_instance_count();
if (count == -1) {
count = multimesh->get_instance_count();
}
Ref<Mesh> mesh = multimesh->get_mesh();
Array results;
for (int i = 0; i < count; i++) {
results.push_back(multimesh->get_instance_transform(i));
results.push_back(mesh);
}
return results;
}
AABB MultiMeshInstance3D::get_aabb() const {
if (multimesh.is_null()) {
return AABB();
} else {
return multimesh->get_aabb();
}
}
#ifndef NAVIGATION_3D_DISABLED
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));
}
}
}
}
}
#endif // NAVIGATION_3D_DISABLED
MultiMeshInstance3D::MultiMeshInstance3D() {
}
MultiMeshInstance3D::~MultiMeshInstance3D() {
}

View File

@@ -0,0 +1,75 @@
/**************************************************************************/
/* multimesh_instance_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/visual_instance_3d.h"
#include "scene/resources/multimesh.h"
#ifndef NAVIGATION_3D_DISABLED
class NavigationMesh;
class NavigationMeshSourceGeometryData3D;
#endif // NAVIGATION_3D_DISABLED
class MultiMeshInstance3D : public GeometryInstance3D {
GDCLASS(MultiMeshInstance3D, GeometryInstance3D);
Ref<MultiMesh> multimesh;
void _refresh_interpolated();
protected:
virtual void _physics_interpolated_changed() override;
static void _bind_methods();
void _notification(int p_what);
public:
void set_multimesh(const Ref<MultiMesh> &p_multimesh);
Ref<MultiMesh> get_multimesh() const;
Array get_meshes() const;
virtual AABB get_aabb() const override;
private:
#ifndef NAVIGATION_3D_DISABLED
static Callable _navmesh_source_geometry_parsing_callback;
static RID _navmesh_source_geometry_parser;
#endif // NAVIGATION_3D_DISABLED
public:
#ifndef NAVIGATION_3D_DISABLED
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 // NAVIGATION_3D_DISABLED
MultiMeshInstance3D();
~MultiMeshInstance3D();
};

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.scene_sources, "*.cpp")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,298 @@
/**************************************************************************/
/* navigation_agent_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. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
#include "servers/navigation/navigation_globals.h"
#include "servers/navigation/navigation_path_query_parameters_3d.h"
#include "servers/navigation/navigation_path_query_result_3d.h"
class Node3D;
class NavigationAgent3D : public Node {
GDCLASS(NavigationAgent3D, Node);
Node3D *agent_parent = nullptr;
RID agent;
RID map_override;
bool avoidance_enabled = false;
bool use_3d_avoidance = false;
uint32_t avoidance_layers = 1;
uint32_t avoidance_mask = 1;
real_t avoidance_priority = 1.0;
uint32_t navigation_layers = 1;
NavigationPathQueryParameters3D::PathfindingAlgorithm pathfinding_algorithm = NavigationPathQueryParameters3D::PathfindingAlgorithm::PATHFINDING_ALGORITHM_ASTAR;
NavigationPathQueryParameters3D::PathPostProcessing path_postprocessing = NavigationPathQueryParameters3D::PathPostProcessing::PATH_POSTPROCESSING_CORRIDORFUNNEL;
BitField<NavigationPathQueryParameters3D::PathMetadataFlags> path_metadata_flags = NavigationPathQueryParameters3D::PathMetadataFlags::PATH_METADATA_INCLUDE_ALL;
real_t path_desired_distance = 1.0;
real_t target_desired_distance = 1.0;
real_t height = NavigationDefaults3D::AVOIDANCE_AGENT_HEIGHT;
real_t radius = NavigationDefaults3D::AVOIDANCE_AGENT_RADIUS;
real_t path_height_offset = 0.0;
real_t neighbor_distance = NavigationDefaults3D::AVOIDANCE_AGENT_NEIGHBOR_DISTANCE;
int max_neighbors = NavigationDefaults3D::AVOIDANCE_AGENT_MAX_NEIGHBORS;
real_t time_horizon_agents = NavigationDefaults3D::AVOIDANCE_AGENT_TIME_HORIZON_AGENTS;
real_t time_horizon_obstacles = NavigationDefaults3D::AVOIDANCE_AGENT_TIME_HORIZON_OBSTACLES;
real_t max_speed = NavigationDefaults3D::AVOIDANCE_AGENT_MAX_SPEED;
real_t path_max_distance = 5.0;
bool simplify_path = false;
real_t simplify_epsilon = 0.0;
float path_return_max_length = 0.0;
float path_return_max_radius = 0.0;
int path_search_max_polygons = NavigationDefaults3D::path_search_max_polygons;
float path_search_max_distance = 0.0;
Vector3 target_position;
Ref<NavigationPathQueryParameters3D> navigation_query;
Ref<NavigationPathQueryResult3D> navigation_result;
int navigation_path_index = 0;
// the velocity result of the avoidance simulation step
Vector3 safe_velocity;
/// The submitted target velocity, sets the "wanted" rvo agent velocity on the next update
// this velocity is not guaranteed, the simulation will try to fulfill it if possible
// if other agents or obstacles interfere it will be changed accordingly
Vector3 velocity;
bool velocity_submitted = false;
/// The submitted forced velocity, overrides the rvo agent velocity on the next update
// should only be used very intentionally and not every frame as it interferes with the simulation stability
Vector3 velocity_forced;
bool velocity_forced_submitted = false;
// 2D avoidance has no y-axis. This stores and reapplies the y-axis velocity to the agent before and after the avoidance step.
// While not perfect it at least looks way better than agent's that clip through everything that is not a flat surface
bool keep_y_velocity = true;
float stored_y_velocity = 0.0;
bool target_position_submitted = false;
bool target_reached = false;
bool navigation_finished = true;
bool last_waypoint_reached = false;
// Debug properties for exposed bindings
bool debug_enabled = false;
float debug_path_custom_point_size = 4.0;
bool debug_use_custom = false;
Color debug_path_custom_color = Color(1.0, 1.0, 1.0, 1.0);
#ifdef DEBUG_ENABLED
// Debug properties internal only
bool debug_path_dirty = true;
RID debug_path_instance;
Ref<ArrayMesh> debug_path_mesh;
Ref<StandardMaterial3D> debug_agent_path_line_custom_material;
Ref<StandardMaterial3D> debug_agent_path_point_custom_material;
#endif // DEBUG_ENABLED
protected:
static void _bind_methods();
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
#endif // DISABLE_DEPRECATED
public:
NavigationAgent3D();
virtual ~NavigationAgent3D();
RID get_rid() const { return agent; }
void set_avoidance_enabled(bool p_enabled);
bool get_avoidance_enabled() const;
void set_agent_parent(Node *p_agent_parent);
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers() const;
void set_navigation_layer_value(int p_layer_number, bool p_value);
bool get_navigation_layer_value(int p_layer_number) const;
void set_pathfinding_algorithm(const NavigationPathQueryParameters3D::PathfindingAlgorithm p_pathfinding_algorithm);
NavigationPathQueryParameters3D::PathfindingAlgorithm get_pathfinding_algorithm() const {
return pathfinding_algorithm;
}
void set_path_postprocessing(const NavigationPathQueryParameters3D::PathPostProcessing p_path_postprocessing);
NavigationPathQueryParameters3D::PathPostProcessing get_path_postprocessing() const {
return path_postprocessing;
}
void set_path_metadata_flags(BitField<NavigationPathQueryParameters3D::PathMetadataFlags> p_flags);
BitField<NavigationPathQueryParameters3D::PathMetadataFlags> get_path_metadata_flags() const {
return path_metadata_flags;
}
void set_navigation_map(RID p_navigation_map);
RID get_navigation_map() const;
void set_path_desired_distance(real_t p_dd);
real_t get_path_desired_distance() const { return path_desired_distance; }
void set_target_desired_distance(real_t p_dd);
real_t get_target_desired_distance() const { return target_desired_distance; }
void set_radius(real_t p_radius);
real_t get_radius() const { return radius; }
void set_height(real_t p_height);
real_t get_height() const { return height; }
void set_path_height_offset(real_t p_path_height_offset);
real_t get_path_height_offset() const { return path_height_offset; }
void set_use_3d_avoidance(bool p_use_3d_avoidance);
bool get_use_3d_avoidance() const { return use_3d_avoidance; }
void set_keep_y_velocity(bool p_enabled);
bool get_keep_y_velocity() const;
void set_neighbor_distance(real_t p_distance);
real_t get_neighbor_distance() const { return neighbor_distance; }
void set_max_neighbors(int p_count);
int get_max_neighbors() const { return max_neighbors; }
void set_time_horizon_agents(real_t p_time_horizon);
real_t get_time_horizon_agents() const { return time_horizon_agents; }
void set_time_horizon_obstacles(real_t p_time_horizon);
real_t get_time_horizon_obstacles() const { return time_horizon_obstacles; }
void set_max_speed(real_t p_max_speed);
real_t get_max_speed() const { return max_speed; }
void set_path_max_distance(real_t p_pmd);
real_t get_path_max_distance();
void set_target_position(Vector3 p_position);
Vector3 get_target_position() const;
void set_simplify_path(bool p_enabled);
bool get_simplify_path() const;
void set_simplify_epsilon(real_t p_epsilon);
real_t get_simplify_epsilon() const;
void set_path_return_max_length(float p_length);
float get_path_return_max_length() const;
void set_path_return_max_radius(float p_radius);
float get_path_return_max_radius() const;
void set_path_search_max_polygons(int p_max_polygons);
int get_path_search_max_polygons() const;
void set_path_search_max_distance(float p_distance);
float get_path_search_max_distance() const;
float get_path_length() const;
Vector3 get_next_path_position();
Ref<NavigationPathQueryResult3D> get_current_navigation_result() const { return navigation_result; }
const Vector<Vector3> &get_current_navigation_path() const { return navigation_result->get_path(); }
int get_current_navigation_path_index() const { return navigation_path_index; }
real_t distance_to_target() const;
bool is_target_reached() const;
bool is_target_reachable();
bool is_navigation_finished();
Vector3 get_final_position();
void set_velocity(const Vector3 p_velocity);
Vector3 get_velocity() { return velocity; }
void set_velocity_forced(const Vector3 p_velocity);
void _avoidance_done(Vector3 p_new_velocity);
PackedStringArray get_configuration_warnings() const override;
void set_avoidance_layers(uint32_t p_layers);
uint32_t get_avoidance_layers() const;
void set_avoidance_mask(uint32_t p_mask);
uint32_t get_avoidance_mask() const;
void set_avoidance_layer_value(int p_layer_number, bool p_value);
bool get_avoidance_layer_value(int p_layer_number) const;
void set_avoidance_mask_value(int p_mask_number, bool p_value);
bool get_avoidance_mask_value(int p_mask_number) const;
void set_avoidance_priority(real_t p_priority);
real_t get_avoidance_priority() const;
void set_debug_enabled(bool p_enabled);
bool get_debug_enabled() const;
void set_debug_use_custom(bool p_enabled);
bool get_debug_use_custom() const;
void set_debug_path_custom_color(Color p_color);
Color get_debug_path_custom_color() const;
void set_debug_path_custom_point_size(float p_point_size);
float get_debug_path_custom_point_size() const;
private:
bool _is_target_reachable() const;
Vector3 _get_final_position() const;
void _update_navigation();
void _advance_waypoints(const Vector3 &p_origin);
void _request_repath();
bool _is_last_waypoint() const;
void _move_to_next_waypoint();
bool _is_within_waypoint_distance(const Vector3 &p_origin) const;
bool _is_within_target_distance(const Vector3 &p_origin) const;
void _trigger_waypoint_reached();
void _transition_to_navigation_finished();
void _transition_to_target_reached();
#ifdef DEBUG_ENABLED
void _navigation_debug_changed();
void _update_debug_path();
#endif // DEBUG_ENABLED
};

View File

@@ -0,0 +1,552 @@
/**************************************************************************/
/* navigation_link_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 "navigation_link_3d.h"
#include "servers/navigation_server_3d.h"
#ifdef DEBUG_ENABLED
void NavigationLink3D::_update_debug_mesh() {
if (!is_inside_tree()) {
return;
}
if (Engine::get_singleton()->is_editor_hint()) {
// don't update inside Editor as node 3d gizmo takes care of this
// as collisions and selections for Editor Viewport need to be updated
return;
}
if (!NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
if (debug_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_instance, false);
}
return;
}
if (!debug_instance.is_valid()) {
debug_instance = RenderingServer::get_singleton()->instance_create();
}
if (debug_mesh.is_null()) {
debug_mesh.instantiate();
}
RID nav_map = get_world_3d()->get_navigation_map();
real_t search_radius = NavigationServer3D::get_singleton()->map_get_link_connection_radius(nav_map);
Vector3 up_vector = NavigationServer3D::get_singleton()->map_get_up(nav_map);
Vector3::Axis up_axis = up_vector.max_axis_index();
debug_mesh->clear_surfaces();
Vector<Vector3> lines;
// Draw line between the points.
lines.push_back(start_position);
lines.push_back(end_position);
// Draw start position search radius
for (int i = 0; i < 30; i++) {
// Create a circle
const float ra = Math::deg_to_rad((float)(i * 12));
const float rb = Math::deg_to_rad((float)((i + 1) * 12));
const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius;
const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius;
// Draw axis-aligned circle
switch (up_axis) {
case Vector3::AXIS_X:
lines.append(start_position + Vector3(0, a.x, a.y));
lines.append(start_position + Vector3(0, b.x, b.y));
break;
case Vector3::AXIS_Y:
lines.append(start_position + Vector3(a.x, 0, a.y));
lines.append(start_position + Vector3(b.x, 0, b.y));
break;
case Vector3::AXIS_Z:
lines.append(start_position + Vector3(a.x, a.y, 0));
lines.append(start_position + Vector3(b.x, b.y, 0));
break;
}
}
// Draw end position search radius
for (int i = 0; i < 30; i++) {
// Create a circle
const float ra = Math::deg_to_rad((float)(i * 12));
const float rb = Math::deg_to_rad((float)((i + 1) * 12));
const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius;
const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius;
// Draw axis-aligned circle
switch (up_axis) {
case Vector3::AXIS_X:
lines.append(end_position + Vector3(0, a.x, a.y));
lines.append(end_position + Vector3(0, b.x, b.y));
break;
case Vector3::AXIS_Y:
lines.append(end_position + Vector3(a.x, 0, a.y));
lines.append(end_position + Vector3(b.x, 0, b.y));
break;
case Vector3::AXIS_Z:
lines.append(end_position + Vector3(a.x, a.y, 0));
lines.append(end_position + Vector3(b.x, b.y, 0));
break;
}
}
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;
debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, mesh_array);
RS::get_singleton()->instance_set_base(debug_instance, debug_mesh->get_rid());
RS::get_singleton()->instance_set_scenario(debug_instance, get_world_3d()->get_scenario());
RS::get_singleton()->instance_set_visible(debug_instance, is_visible_in_tree());
Ref<StandardMaterial3D> link_material = NavigationServer3D::get_singleton()->get_debug_navigation_link_connections_material();
Ref<StandardMaterial3D> disabled_link_material = NavigationServer3D::get_singleton()->get_debug_navigation_link_connections_disabled_material();
if (enabled) {
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, link_material->get_rid());
} else {
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, disabled_link_material->get_rid());
}
RS::get_singleton()->instance_set_transform(debug_instance, current_global_transform);
}
#endif // DEBUG_ENABLED
void NavigationLink3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rid"), &NavigationLink3D::get_rid);
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);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationLink3D::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationLink3D::get_navigation_layers);
ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationLink3D::set_navigation_layer_value);
ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationLink3D::get_navigation_layer_value);
ClassDB::bind_method(D_METHOD("set_start_position", "position"), &NavigationLink3D::set_start_position);
ClassDB::bind_method(D_METHOD("get_start_position"), &NavigationLink3D::get_start_position);
ClassDB::bind_method(D_METHOD("set_end_position", "position"), &NavigationLink3D::set_end_position);
ClassDB::bind_method(D_METHOD("get_end_position"), &NavigationLink3D::get_end_position);
ClassDB::bind_method(D_METHOD("set_global_start_position", "position"), &NavigationLink3D::set_global_start_position);
ClassDB::bind_method(D_METHOD("get_global_start_position"), &NavigationLink3D::get_global_start_position);
ClassDB::bind_method(D_METHOD("set_global_end_position", "position"), &NavigationLink3D::set_global_end_position);
ClassDB::bind_method(D_METHOD("get_global_end_position"), &NavigationLink3D::get_global_end_position);
ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationLink3D::set_enter_cost);
ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationLink3D::get_enter_cost);
ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationLink3D::set_travel_cost);
ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationLink3D::get_travel_cost);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bidirectional"), "set_bidirectional", "is_bidirectional");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "start_position"), "set_start_position", "get_start_position");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "end_position"), "set_end_position", "get_end_position");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost");
}
#ifndef DISABLE_DEPRECATED
bool NavigationLink3D::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "start_location") {
set_start_position(p_value);
return true;
}
if (p_name == "end_location") {
set_end_position(p_value);
return true;
}
return false;
}
bool NavigationLink3D::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "start_location") {
r_ret = get_start_position();
return true;
}
if (p_name == "end_location") {
r_ret = get_end_position();
return true;
}
return false;
}
#endif // DISABLE_DEPRECATED
void NavigationLink3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
_link_enter_navigation_map();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
set_physics_process_internal(true);
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
set_physics_process_internal(false);
_link_update_transform();
} break;
case NOTIFICATION_EXIT_TREE: {
_link_exit_navigation_map();
} break;
#ifdef DEBUG_ENABLED
case NOTIFICATION_VISIBILITY_CHANGED: {
_update_debug_mesh();
} break;
#endif // DEBUG_ENABLED
}
}
NavigationLink3D::NavigationLink3D() {
link = NavigationServer3D::get_singleton()->link_create();
NavigationServer3D::get_singleton()->link_set_owner_id(link, get_instance_id());
NavigationServer3D::get_singleton()->link_set_enter_cost(link, enter_cost);
NavigationServer3D::get_singleton()->link_set_travel_cost(link, travel_cost);
NavigationServer3D::get_singleton()->link_set_navigation_layers(link, navigation_layers);
NavigationServer3D::get_singleton()->link_set_bidirectional(link, bidirectional);
NavigationServer3D::get_singleton()->link_set_enabled(link, enabled);
set_notify_transform(true);
}
NavigationLink3D::~NavigationLink3D() {
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
NavigationServer3D::get_singleton()->free(link);
link = RID();
#ifdef DEBUG_ENABLED
ERR_FAIL_NULL(RenderingServer::get_singleton());
if (debug_instance.is_valid()) {
RenderingServer::get_singleton()->free(debug_instance);
}
if (debug_mesh.is_valid()) {
RenderingServer::get_singleton()->free(debug_mesh->get_rid());
}
#endif // DEBUG_ENABLED
}
RID NavigationLink3D::get_rid() const {
return link;
}
void NavigationLink3D::set_enabled(bool p_enabled) {
if (enabled == p_enabled) {
return;
}
enabled = p_enabled;
NavigationServer3D::get_singleton()->link_set_enabled(link, enabled);
#ifdef DEBUG_ENABLED
if (debug_instance.is_valid() && debug_mesh.is_valid()) {
if (enabled) {
Ref<StandardMaterial3D> link_material = NavigationServer3D::get_singleton()->get_debug_navigation_link_connections_material();
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, link_material->get_rid());
} else {
Ref<StandardMaterial3D> disabled_link_material = NavigationServer3D::get_singleton()->get_debug_navigation_link_connections_disabled_material();
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, disabled_link_material->get_rid());
}
}
#endif // DEBUG_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;
}
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) {
if (navigation_layers == p_navigation_layers) {
return;
}
navigation_layers = p_navigation_layers;
NavigationServer3D::get_singleton()->link_set_navigation_layers(link, navigation_layers);
}
void NavigationLink3D::set_navigation_layer_value(int p_layer_number, bool p_value) {
ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive.");
uint32_t _navigation_layers = get_navigation_layers();
if (p_value) {
_navigation_layers |= 1 << (p_layer_number - 1);
} else {
_navigation_layers &= ~(1 << (p_layer_number - 1));
}
set_navigation_layers(_navigation_layers);
}
bool NavigationLink3D::get_navigation_layer_value(int p_layer_number) const {
ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive.");
return get_navigation_layers() & (1 << (p_layer_number - 1));
}
void NavigationLink3D::set_start_position(Vector3 p_position) {
if (start_position.is_equal_approx(p_position)) {
return;
}
start_position = p_position;
if (!is_inside_tree()) {
return;
}
NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
#ifdef DEBUG_ENABLED
_update_debug_mesh();
#endif // DEBUG_ENABLED
update_gizmos();
update_configuration_warnings();
}
void NavigationLink3D::set_end_position(Vector3 p_position) {
if (end_position.is_equal_approx(p_position)) {
return;
}
end_position = p_position;
if (!is_inside_tree()) {
return;
}
NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
#ifdef DEBUG_ENABLED
_update_debug_mesh();
#endif // DEBUG_ENABLED
update_gizmos();
update_configuration_warnings();
}
void NavigationLink3D::set_global_start_position(Vector3 p_position) {
if (is_inside_tree()) {
set_start_position(to_local(p_position));
} else {
set_start_position(p_position);
}
}
Vector3 NavigationLink3D::get_global_start_position() const {
if (is_inside_tree()) {
return to_global(start_position);
} else {
return start_position;
}
}
void NavigationLink3D::set_global_end_position(Vector3 p_position) {
if (is_inside_tree()) {
set_end_position(to_local(p_position));
} else {
set_end_position(p_position);
}
}
Vector3 NavigationLink3D::get_global_end_position() const {
if (is_inside_tree()) {
return to_global(end_position);
} else {
return end_position;
}
}
void NavigationLink3D::set_enter_cost(real_t p_enter_cost) {
ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive.");
if (Math::is_equal_approx(enter_cost, p_enter_cost)) {
return;
}
enter_cost = p_enter_cost;
NavigationServer3D::get_singleton()->link_set_enter_cost(link, enter_cost);
}
void NavigationLink3D::set_travel_cost(real_t p_travel_cost) {
ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive.");
if (Math::is_equal_approx(travel_cost, p_travel_cost)) {
return;
}
travel_cost = p_travel_cost;
NavigationServer3D::get_singleton()->link_set_travel_cost(link, travel_cost);
}
PackedStringArray NavigationLink3D::get_configuration_warnings() const {
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."));
}
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
}
}

View File

@@ -0,0 +1,111 @@
/**************************************************************************/
/* navigation_link_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
class NavigationLink3D : public Node3D {
GDCLASS(NavigationLink3D, Node3D);
bool enabled = true;
RID link;
RID map_override;
bool bidirectional = true;
uint32_t navigation_layers = 1;
Vector3 end_position;
Vector3 start_position;
real_t enter_cost = 0.0;
real_t travel_cost = 1.0;
Transform3D current_global_transform;
#ifdef DEBUG_ENABLED
RID debug_instance;
Ref<ArrayMesh> debug_mesh;
void _update_debug_mesh();
#endif // DEBUG_ENABLED
protected:
static void _bind_methods();
void _notification(int p_what);
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
#endif // DISABLE_DEPRECATED
public:
NavigationLink3D();
~NavigationLink3D();
RID get_rid() const;
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; }
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers() const { return navigation_layers; }
void set_navigation_layer_value(int p_layer_number, bool p_value);
bool get_navigation_layer_value(int p_layer_number) const;
void set_start_position(Vector3 p_position);
Vector3 get_start_position() const { return start_position; }
void set_end_position(Vector3 p_position);
Vector3 get_end_position() const { return end_position; }
void set_global_start_position(Vector3 p_position);
Vector3 get_global_start_position() const;
void set_global_end_position(Vector3 p_position);
Vector3 get_global_end_position() const;
void set_enter_cost(real_t p_enter_cost);
real_t get_enter_cost() const { return enter_cost; }
void set_travel_cost(real_t p_travel_cost);
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();
};

View File

@@ -0,0 +1,723 @@
/**************************************************************************/
/* navigation_obstacle_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 "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);
ClassDB::bind_method(D_METHOD("set_avoidance_enabled", "enabled"), &NavigationObstacle3D::set_avoidance_enabled);
ClassDB::bind_method(D_METHOD("get_avoidance_enabled"), &NavigationObstacle3D::get_avoidance_enabled);
ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationObstacle3D::set_navigation_map);
ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationObstacle3D::get_navigation_map);
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationObstacle3D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &NavigationObstacle3D::get_radius);
ClassDB::bind_method(D_METHOD("set_height", "height"), &NavigationObstacle3D::set_height);
ClassDB::bind_method(D_METHOD("get_height"), &NavigationObstacle3D::get_height);
ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &NavigationObstacle3D::set_velocity);
ClassDB::bind_method(D_METHOD("get_velocity"), &NavigationObstacle3D::get_velocity);
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationObstacle3D::set_vertices);
ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationObstacle3D::get_vertices);
ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationObstacle3D::set_avoidance_layers);
ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationObstacle3D::get_avoidance_layers);
ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationObstacle3D::set_avoidance_layer_value);
ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationObstacle3D::get_avoidance_layer_value);
ClassDB::bind_method(D_METHOD("set_use_3d_avoidance", "enabled"), &NavigationObstacle3D::set_use_3d_avoidance);
ClassDB::bind_method(D_METHOD("get_use_3d_avoidance"), &NavigationObstacle3D::get_use_3d_avoidance);
ClassDB::bind_method(D_METHOD("set_affect_navigation_mesh", "enabled"), &NavigationObstacle3D::set_affect_navigation_mesh);
ClassDB::bind_method(D_METHOD("get_affect_navigation_mesh"), &NavigationObstacle3D::get_affect_navigation_mesh);
ClassDB::bind_method(D_METHOD("set_carve_navigation_mesh", "enabled"), &NavigationObstacle3D::set_carve_navigation_mesh);
ClassDB::bind_method(D_METHOD("get_carve_navigation_mesh"), &NavigationObstacle3D::get_carve_navigation_mesh);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_radius", "get_radius");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_height", "get_height");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices"), "set_vertices", "get_vertices");
ADD_GROUP("NavigationMesh", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "affect_navigation_mesh"), "set_affect_navigation_mesh", "get_affect_navigation_mesh");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "carve_navigation_mesh"), "set_carve_navigation_mesh", "get_carve_navigation_mesh");
ADD_GROUP("Avoidance", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity");
ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_3d_avoidance"), "set_use_3d_avoidance", "get_use_3d_avoidance");
}
void NavigationObstacle3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POST_ENTER_TREE: {
if (map_override.is_valid()) {
_update_map(map_override);
} else if (is_inside_tree()) {
_update_map(get_world_3d()->get_navigation_map());
} else {
_update_map(RID());
}
// 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_transform();
set_physics_process_internal(true);
#ifdef DEBUG_ENABLED
_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
_clear_debug();
#endif // DEBUG_ENABLED
} break;
case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (!can_process()) {
map_before_pause = map_current;
_update_map(RID());
} else if (can_process() && !(map_before_pause == RID())) {
_update_map(map_before_pause);
map_before_pause = RID();
}
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;
_update_map(RID());
} else if (can_process() && !(map_before_pause == RID())) {
_update_map(map_before_pause);
map_before_pause = RID();
}
NavigationServer3D::get_singleton()->obstacle_set_paused(obstacle, !can_process());
} break;
#ifdef DEBUG_ENABLED
case NOTIFICATION_VISIBILITY_CHANGED: {
_update_debug();
} break;
#endif // DEBUG_ENABLED
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (is_inside_tree()) {
_update_transform();
if (velocity_submitted) {
velocity_submitted = false;
// only update if there is a noticeable change, else the rvo agent preferred velocity stays the same
if (!previous_velocity.is_equal_approx(velocity)) {
NavigationServer3D::get_singleton()->obstacle_set_velocity(obstacle, velocity);
}
previous_velocity = velocity;
}
#ifdef DEBUG_ENABLED
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_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
}
} break;
}
}
NavigationObstacle3D::NavigationObstacle3D() {
NavigationServer3D *ns3d = NavigationServer3D::get_singleton();
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
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() {
NavigationServer3D *ns3d = NavigationServer3D::get_singleton();
ERR_FAIL_NULL(ns3d);
ns3d->free(obstacle);
obstacle = RID();
#ifdef DEBUG_ENABLED
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));
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 (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;
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
}
void NavigationObstacle3D::set_navigation_map(RID p_navigation_map) {
if (map_override == p_navigation_map) {
return;
}
map_override = p_navigation_map;
_update_map(map_override);
}
RID NavigationObstacle3D::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 NavigationObstacle3D::set_radius(real_t p_radius) {
ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive.");
if (Math::is_equal_approx(radius, p_radius)) {
return;
}
radius = p_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
}
void NavigationObstacle3D::set_height(real_t p_height) {
ERR_FAIL_COND_MSG(p_height < 0.0, "Height must be positive.");
if (Math::is_equal_approx(height, p_height)) {
return;
}
height = p_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
}
void NavigationObstacle3D::set_avoidance_layers(uint32_t p_layers) {
avoidance_layers = p_layers;
NavigationServer3D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
}
uint32_t NavigationObstacle3D::get_avoidance_layers() const {
return avoidance_layers;
}
void NavigationObstacle3D::set_avoidance_layer_value(int p_layer_number, bool p_value) {
ERR_FAIL_COND_MSG(p_layer_number < 1, "Avoidance layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_MSG(p_layer_number > 32, "Avoidance layer number must be between 1 and 32 inclusive.");
uint32_t avoidance_layers_new = get_avoidance_layers();
if (p_value) {
avoidance_layers_new |= 1 << (p_layer_number - 1);
} else {
avoidance_layers_new &= ~(1 << (p_layer_number - 1));
}
set_avoidance_layers(avoidance_layers_new);
}
bool NavigationObstacle3D::get_avoidance_layer_value(int p_layer_number) const {
ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Avoidance layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive.");
return get_avoidance_layers() & (1 << (p_layer_number - 1));
}
void NavigationObstacle3D::set_avoidance_enabled(bool p_enabled) {
if (avoidance_enabled == p_enabled) {
return;
}
avoidance_enabled = p_enabled;
NavigationServer3D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
}
bool NavigationObstacle3D::get_avoidance_enabled() const {
return avoidance_enabled;
}
void NavigationObstacle3D::set_use_3d_avoidance(bool p_use_3d_avoidance) {
use_3d_avoidance = p_use_3d_avoidance;
_update_use_3d_avoidance(use_3d_avoidance);
notify_property_list_changed();
}
void NavigationObstacle3D::set_velocity(const Vector3 p_velocity) {
velocity = p_velocity;
velocity_submitted = true;
}
void NavigationObstacle3D::set_affect_navigation_mesh(bool p_enabled) {
affect_navigation_mesh = p_enabled;
}
bool NavigationObstacle3D::get_affect_navigation_mesh() const {
return affect_navigation_mesh;
}
void NavigationObstacle3D::set_carve_navigation_mesh(bool p_enabled) {
carve_navigation_mesh = p_enabled;
}
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;
}
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 (ns3d->get_debug_enabled() &&
ns3d->get_debug_avoidance_enabled() &&
ns3d->get_debug_navigation_avoidance_enable_obstacles_radius()) {
is_debug_enabled = true;
}
rs->mesh_clear(fake_agent_radius_debug_mesh_rid);
if (!is_debug_enabled) {
return;
}
Vector<Vector3> face_vertex_array;
Vector<int> face_indices_array;
int i, j, prevrow, thisrow, point;
float x, y, z;
int rings = 16;
int radial_segments = 32;
point = 0;
thisrow = 0;
prevrow = 0;
for (j = 0; j <= (rings + 1); j++) {
float v = j;
float w;
v /= (rings + 1);
w = std::sin(Math::PI * v);
y = (radius)*std::cos(Math::PI * v);
for (i = 0; i <= radial_segments; i++) {
float u = i;
u /= radial_segments;
x = std::sin(u * Math::TAU);
z = std::cos(u * Math::TAU);
Vector3 p = Vector3(x * radius * w, y, z * radius * w);
face_vertex_array.push_back(p);
point++;
if (i > 0 && j > 0) {
face_indices_array.push_back(prevrow + i - 1);
face_indices_array.push_back(prevrow + i);
face_indices_array.push_back(thisrow + i - 1);
face_indices_array.push_back(prevrow + i);
face_indices_array.push_back(thisrow + i);
face_indices_array.push_back(thisrow + i - 1);
};
};
prevrow = thisrow;
thisrow = point;
};
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;
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());
if (is_inside_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() {
if (Engine::get_singleton()->is_editor_hint()) {
// 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;
}
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;
}
Vector<Vector3> edge_vertex_array;
edge_vertex_array.resize(vertex_count * 8);
Vector3 *edge_vertex_array_ptrw = edge_vertex_array.ptrw();
int vertex_index = 0;
for (int i = 0; i < vertex_count; i++) {
Vector3 point = vertices[i];
Vector3 next_point = vertices[(i + 1) % vertex_count];
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_ptrw[vertex_index++] = edge_middle;
edge_vertex_array_ptrw[vertex_index++] = edge_middle + (arrow_dir * 0.5);
edge_vertex_array_ptrw[vertex_index++] = point;
edge_vertex_array_ptrw[vertex_index++] = next_point;
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_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;
rs->mesh_add_surface_from_arrays(static_obstacle_debug_mesh_rid, RS::PRIMITIVE_LINES, edge_mesh_array);
Ref<StandardMaterial3D> edge_material;
if (are_vertices_valid()) {
edge_material = ns3d->get_debug_navigation_avoidance_static_obstacle_pushout_edge_material();
} else {
edge_material = ns3d->get_debug_navigation_avoidance_static_obstacle_pushin_edge_material();
}
rs->instance_set_surface_override_material(static_obstacle_debug_instance_rid, 0, edge_material->get_rid());
if (is_inside_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

View File

@@ -0,0 +1,142 @@
/**************************************************************************/
/* navigation_obstacle_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
class NavigationMesh;
class NavigationMeshSourceGeometryData3D;
class NavigationObstacle3D : public Node3D {
GDCLASS(NavigationObstacle3D, Node3D);
RID obstacle;
RID map_before_pause;
RID map_override;
RID map_current;
real_t height = 1.0;
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;
Vector3 velocity;
Vector3 previous_velocity;
bool velocity_submitted = false;
bool affect_navigation_mesh = false;
bool carve_navigation_mesh = false;
#ifdef DEBUG_ENABLED
RID fake_agent_radius_debug_instance_rid;
RID fake_agent_radius_debug_mesh_rid;
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:
static void _bind_methods();
void _notification(int p_what);
public:
NavigationObstacle3D();
virtual ~NavigationObstacle3D();
RID get_rid() const { return obstacle; }
void set_avoidance_enabled(bool p_enabled);
bool get_avoidance_enabled() const;
void set_navigation_map(RID p_navigation_map);
RID get_navigation_map() const;
void set_radius(real_t p_radius);
real_t get_radius() const { return radius; }
void set_height(real_t p_height);
real_t get_height() const { return height; }
void set_vertices(const Vector<Vector3> &p_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;
void set_avoidance_layer_value(int p_layer_number, bool p_value);
bool get_avoidance_layer_value(int p_layer_number) const;
void set_use_3d_avoidance(bool p_use_3d_avoidance);
bool get_use_3d_avoidance() const { return use_3d_avoidance; }
void set_velocity(const Vector3 p_velocity);
Vector3 get_velocity() const { return velocity; }
void _avoidance_done(Vector3 p_new_velocity); // Dummy
void set_affect_navigation_mesh(bool p_enabled);
bool get_affect_navigation_mesh() const;
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);
};

View File

@@ -0,0 +1,766 @@
/**************************************************************************/
/* navigation_region_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 "navigation_region_3d.h"
#include "core/math/random_pcg.h"
#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h"
#include "servers/navigation_server_3d.h"
RID NavigationRegion3D::get_rid() const {
return region;
}
void NavigationRegion3D::set_enabled(bool p_enabled) {
if (enabled == p_enabled) {
return;
}
enabled = p_enabled;
NavigationServer3D::get_singleton()->region_set_enabled(region, enabled);
#ifdef DEBUG_ENABLED
if (debug_instance.is_valid()) {
if (!is_enabled()) {
if (debug_mesh.is_valid()) {
if (debug_mesh->get_surface_count() > 0) {
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_disabled_material()->get_rid());
}
if (debug_mesh->get_surface_count() > 1) {
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 1, NavigationServer3D::get_singleton()->get_debug_navigation_geometry_edge_disabled_material()->get_rid());
}
}
} else {
if (debug_mesh.is_valid()) {
if (debug_mesh->get_surface_count() > 0) {
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, RID());
}
if (debug_mesh->get_surface_count() > 1) {
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 1, RID());
}
}
}
}
#endif // DEBUG_ENABLED
update_gizmos();
}
bool NavigationRegion3D::is_enabled() const {
return enabled;
}
void NavigationRegion3D::set_use_edge_connections(bool p_enabled) {
if (use_edge_connections == p_enabled) {
return;
}
use_edge_connections = p_enabled;
NavigationServer3D::get_singleton()->region_set_use_edge_connections(region, use_edge_connections);
}
bool NavigationRegion3D::get_use_edge_connections() const {
return use_edge_connections;
}
void NavigationRegion3D::set_navigation_layers(uint32_t p_navigation_layers) {
if (navigation_layers == p_navigation_layers) {
return;
}
navigation_layers = p_navigation_layers;
NavigationServer3D::get_singleton()->region_set_navigation_layers(region, navigation_layers);
}
uint32_t NavigationRegion3D::get_navigation_layers() const {
return navigation_layers;
}
void NavigationRegion3D::set_navigation_layer_value(int p_layer_number, bool p_value) {
ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive.");
uint32_t _navigation_layers = get_navigation_layers();
if (p_value) {
_navigation_layers |= 1 << (p_layer_number - 1);
} else {
_navigation_layers &= ~(1 << (p_layer_number - 1));
}
set_navigation_layers(_navigation_layers);
}
bool NavigationRegion3D::get_navigation_layer_value(int p_layer_number) const {
ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive.");
return get_navigation_layers() & (1 << (p_layer_number - 1));
}
void NavigationRegion3D::set_enter_cost(real_t p_enter_cost) {
ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive.");
if (Math::is_equal_approx(enter_cost, p_enter_cost)) {
return;
}
enter_cost = p_enter_cost;
NavigationServer3D::get_singleton()->region_set_enter_cost(region, enter_cost);
}
real_t NavigationRegion3D::get_enter_cost() const {
return enter_cost;
}
void NavigationRegion3D::set_travel_cost(real_t p_travel_cost) {
ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive.");
if (Math::is_equal_approx(travel_cost, p_travel_cost)) {
return;
}
travel_cost = p_travel_cost;
NavigationServer3D::get_singleton()->region_set_travel_cost(region, travel_cost);
}
real_t NavigationRegion3D::get_travel_cost() const {
return travel_cost;
}
RID NavigationRegion3D::get_region_rid() const {
return get_rid();
}
void NavigationRegion3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
_region_enter_navigation_map();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
set_physics_process_internal(true);
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
set_physics_process_internal(false);
_region_update_transform();
} break;
case NOTIFICATION_EXIT_TREE: {
_region_exit_navigation_map();
} break;
}
}
void NavigationRegion3D::set_navigation_mesh(const Ref<NavigationMesh> &p_navigation_mesh) {
if (navigation_mesh.is_valid()) {
navigation_mesh->disconnect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed));
}
navigation_mesh = p_navigation_mesh;
if (navigation_mesh.is_valid()) {
navigation_mesh->connect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed));
}
_navigation_mesh_changed();
}
Ref<NavigationMesh> NavigationRegion3D::get_navigation_mesh() const {
return navigation_mesh;
}
void NavigationRegion3D::set_navigation_map(RID p_navigation_map) {
if (map_override == p_navigation_map) {
return;
}
map_override = p_navigation_map;
NavigationServer3D::get_singleton()->region_set_map(region, map_override);
}
RID NavigationRegion3D::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 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_null(), "Baking the navigation mesh requires a valid `NavigationMesh` resource.");
Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
source_geometry_data.instantiate();
NavigationServer3D::get_singleton()->parse_source_geometry_data(navigation_mesh, source_geometry_data, this);
if (p_on_thread) {
NavigationServer3D::get_singleton()->bake_from_source_geometry_data_async(navigation_mesh, source_geometry_data, callable_mp(this, &NavigationRegion3D::_bake_finished));
} else {
NavigationServer3D::get_singleton()->bake_from_source_geometry_data(navigation_mesh, source_geometry_data, callable_mp(this, &NavigationRegion3D::_bake_finished));
}
}
void NavigationRegion3D::_bake_finished() {
if (!Thread::is_main_thread()) {
callable_mp(this, &NavigationRegion3D::_bake_finished).call_deferred();
return;
}
emit_signal(SNAME("bake_finished"));
}
bool NavigationRegion3D::is_baking() const {
return NavigationServer3D::get_singleton()->is_baking_navigation_mesh(navigation_mesh);
}
PackedStringArray NavigationRegion3D::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (navigation_mesh.is_null()) {
warnings.push_back(RTR("A NavigationMesh resource must be set or created for this node to work."));
}
}
return warnings;
}
void NavigationRegion3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rid"), &NavigationRegion3D::get_rid);
ClassDB::bind_method(D_METHOD("set_navigation_mesh", "navigation_mesh"), &NavigationRegion3D::set_navigation_mesh);
ClassDB::bind_method(D_METHOD("get_navigation_mesh"), &NavigationRegion3D::get_navigation_mesh);
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationRegion3D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationRegion3D::is_enabled);
ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationRegion3D::set_navigation_map);
ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationRegion3D::get_navigation_map);
ClassDB::bind_method(D_METHOD("set_use_edge_connections", "enabled"), &NavigationRegion3D::set_use_edge_connections);
ClassDB::bind_method(D_METHOD("get_use_edge_connections"), &NavigationRegion3D::get_use_edge_connections);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationRegion3D::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationRegion3D::get_navigation_layers);
ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationRegion3D::set_navigation_layer_value);
ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationRegion3D::get_navigation_layer_value);
ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationRegion3D::get_region_rid);
ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationRegion3D::set_enter_cost);
ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationRegion3D::get_enter_cost);
ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationRegion3D::set_travel_cost);
ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationRegion3D::get_travel_cost);
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");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost");
ADD_SIGNAL(MethodInfo("navigation_mesh_changed"));
ADD_SIGNAL(MethodInfo("bake_finished"));
}
#ifndef DISABLE_DEPRECATED
// Compatibility with earlier 4.0 betas.
bool NavigationRegion3D::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "navmesh") {
set_navigation_mesh(p_value);
return true;
}
return false;
}
bool NavigationRegion3D::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "navmesh") {
r_ret = get_navigation_mesh();
return true;
}
return false;
}
#endif // DISABLE_DEPRECATED
void NavigationRegion3D::_navigation_mesh_changed() {
_update_bounds();
NavigationServer3D::get_singleton()->region_set_navigation_mesh(region, navigation_mesh);
#ifdef DEBUG_ENABLED
if (is_inside_tree() && NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
if (navigation_mesh.is_valid()) {
_update_debug_mesh();
_update_debug_edge_connections_mesh();
} else {
if (debug_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_instance, false);
}
if (debug_edge_connections_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false);
}
}
}
_update_debug_edge_connections_mesh();
#endif // DEBUG_ENABLED
emit_signal(SNAME("navigation_mesh_changed"));
update_gizmos();
update_configuration_warnings();
}
#ifdef DEBUG_ENABLED
void NavigationRegion3D::_navigation_map_changed(RID p_map) {
if (is_inside_tree() && p_map == get_world_3d()->get_navigation_map()) {
_update_debug_edge_connections_mesh();
}
}
#endif // DEBUG_ENABLED
#ifdef DEBUG_ENABLED
void NavigationRegion3D::_navigation_debug_changed() {
if (is_inside_tree()) {
_update_debug_mesh();
_update_debug_edge_connections_mesh();
}
}
#endif // DEBUG_ENABLED
void NavigationRegion3D::_region_enter_navigation_map() {
if (!is_inside_tree()) {
return;
}
if (map_override.is_valid()) {
NavigationServer3D::get_singleton()->region_set_map(region, map_override);
} else {
NavigationServer3D::get_singleton()->region_set_map(region, get_world_3d()->get_navigation_map());
}
current_global_transform = get_global_transform();
NavigationServer3D::get_singleton()->region_set_transform(region, current_global_transform);
NavigationServer3D::get_singleton()->region_set_enabled(region, enabled);
#ifdef DEBUG_ENABLED
if (NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
_update_debug_mesh();
}
#endif // DEBUG_ENABLED
}
void NavigationRegion3D::_region_exit_navigation_map() {
NavigationServer3D::get_singleton()->region_set_map(region, RID());
#ifdef DEBUG_ENABLED
if (debug_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_instance, false);
}
if (debug_edge_connections_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false);
}
#endif // DEBUG_ENABLED
}
void NavigationRegion3D::_region_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()->region_set_transform(region, current_global_transform);
#ifdef DEBUG_ENABLED
if (debug_instance.is_valid()) {
RS::get_singleton()->instance_set_transform(debug_instance, current_global_transform);
}
#endif // DEBUG_ENABLED
}
}
NavigationRegion3D::NavigationRegion3D() {
set_notify_transform(true);
region = NavigationServer3D::get_singleton()->region_create();
NavigationServer3D::get_singleton()->region_set_owner_id(region, get_instance_id());
NavigationServer3D::get_singleton()->region_set_enter_cost(region, get_enter_cost());
NavigationServer3D::get_singleton()->region_set_travel_cost(region, get_travel_cost());
NavigationServer3D::get_singleton()->region_set_navigation_layers(region, navigation_layers);
NavigationServer3D::get_singleton()->region_set_use_edge_connections(region, use_edge_connections);
NavigationServer3D::get_singleton()->region_set_enabled(region, enabled);
#ifdef DEBUG_ENABLED
NavigationServer3D::get_singleton()->connect(SNAME("map_changed"), callable_mp(this, &NavigationRegion3D::_navigation_map_changed));
NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion3D::_navigation_debug_changed));
#endif // DEBUG_ENABLED
}
NavigationRegion3D::~NavigationRegion3D() {
if (navigation_mesh.is_valid()) {
navigation_mesh->disconnect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed));
}
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
NavigationServer3D::get_singleton()->free(region);
#ifdef DEBUG_ENABLED
NavigationServer3D::get_singleton()->disconnect(SNAME("map_changed"), callable_mp(this, &NavigationRegion3D::_navigation_map_changed));
NavigationServer3D::get_singleton()->disconnect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion3D::_navigation_debug_changed));
ERR_FAIL_NULL(RenderingServer::get_singleton());
if (debug_instance.is_valid()) {
RenderingServer::get_singleton()->free(debug_instance);
}
if (debug_mesh.is_valid()) {
RenderingServer::get_singleton()->free(debug_mesh->get_rid());
}
if (debug_edge_connections_instance.is_valid()) {
RenderingServer::get_singleton()->free(debug_edge_connections_instance);
}
if (debug_edge_connections_mesh.is_valid()) {
RenderingServer::get_singleton()->free(debug_edge_connections_mesh->get_rid());
}
#endif // DEBUG_ENABLED
}
#ifdef DEBUG_ENABLED
void NavigationRegion3D::_update_debug_mesh() {
if (Engine::get_singleton()->is_editor_hint()) {
// don't update inside Editor as node 3d gizmo takes care of this
// as collisions and selections for Editor Viewport need to be updated
return;
}
if (!NavigationServer3D::get_singleton()->get_debug_enabled() || !NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
if (debug_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_instance, false);
}
return;
}
if (navigation_mesh.is_null()) {
if (debug_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_instance, false);
}
return;
}
if (!debug_instance.is_valid()) {
debug_instance = RenderingServer::get_singleton()->instance_create();
}
if (debug_mesh.is_null()) {
debug_mesh.instantiate();
}
debug_mesh->clear_surfaces();
Vector<Vector3> vertices = navigation_mesh->get_vertices();
if (vertices.is_empty()) {
return;
}
int polygon_count = navigation_mesh->get_polygon_count();
if (polygon_count == 0) {
return;
}
bool enabled_geometry_face_random_color = NavigationServer3D::get_singleton()->get_debug_navigation_enable_geometry_face_random_color();
bool enabled_edge_lines = NavigationServer3D::get_singleton()->get_debug_navigation_enable_edge_lines();
int vertex_count = 0;
int line_count = 0;
for (int i = 0; i < polygon_count; i++) {
const Vector<int> &polygon = navigation_mesh->get_polygon(i);
int polygon_size = polygon.size();
if (polygon_size < 3) {
continue;
}
line_count += polygon_size * 2;
vertex_count += (polygon_size - 2) * 3;
}
Vector<Vector3> face_vertex_array;
face_vertex_array.resize(vertex_count);
Vector<Color> face_color_array;
if (enabled_geometry_face_random_color) {
face_color_array.resize(vertex_count);
}
Vector<Vector3> line_vertex_array;
if (enabled_edge_lines) {
line_vertex_array.resize(line_count);
}
Color debug_navigation_geometry_face_color = NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_color();
RandomPCG rand;
Color polygon_color = debug_navigation_geometry_face_color;
int face_vertex_index = 0;
int line_vertex_index = 0;
Vector3 *face_vertex_array_ptrw = face_vertex_array.ptrw();
Color *face_color_array_ptrw = face_color_array.ptrw();
Vector3 *line_vertex_array_ptrw = line_vertex_array.ptrw();
for (int polygon_index = 0; polygon_index < polygon_count; polygon_index++) {
const Vector<int> &polygon_indices = navigation_mesh->get_polygon(polygon_index);
int polygon_indices_size = polygon_indices.size();
if (polygon_indices_size < 3) {
continue;
}
if (enabled_geometry_face_random_color) {
// Generate the polygon color, slightly randomly modified from the settings one.
polygon_color.set_hsv(debug_navigation_geometry_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, debug_navigation_geometry_face_color.get_s(), debug_navigation_geometry_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2);
polygon_color.a = debug_navigation_geometry_face_color.a;
}
for (int polygon_indices_index = 0; polygon_indices_index < polygon_indices_size - 2; polygon_indices_index++) {
face_vertex_array_ptrw[face_vertex_index] = vertices[polygon_indices[0]];
face_vertex_array_ptrw[face_vertex_index + 1] = vertices[polygon_indices[polygon_indices_index + 1]];
face_vertex_array_ptrw[face_vertex_index + 2] = vertices[polygon_indices[polygon_indices_index + 2]];
if (enabled_geometry_face_random_color) {
face_color_array_ptrw[face_vertex_index] = polygon_color;
face_color_array_ptrw[face_vertex_index + 1] = polygon_color;
face_color_array_ptrw[face_vertex_index + 2] = polygon_color;
}
face_vertex_index += 3;
}
if (enabled_edge_lines) {
for (int polygon_indices_index = 0; polygon_indices_index < polygon_indices_size; polygon_indices_index++) {
line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[polygon_indices_index]];
line_vertex_index += 1;
if (polygon_indices_index + 1 == polygon_indices_size) {
line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[0]];
line_vertex_index += 1;
} else {
line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[polygon_indices_index + 1]];
line_vertex_index += 1;
}
}
}
}
Ref<StandardMaterial3D> face_material = NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_material();
Array face_mesh_array;
face_mesh_array.resize(Mesh::ARRAY_MAX);
face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array;
if (enabled_geometry_face_random_color) {
face_mesh_array[Mesh::ARRAY_COLOR] = face_color_array;
}
debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, face_mesh_array);
debug_mesh->surface_set_material(0, face_material);
if (enabled_edge_lines) {
Ref<StandardMaterial3D> line_material = NavigationServer3D::get_singleton()->get_debug_navigation_geometry_edge_material();
Array line_mesh_array;
line_mesh_array.resize(Mesh::ARRAY_MAX);
line_mesh_array[Mesh::ARRAY_VERTEX] = line_vertex_array;
debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, line_mesh_array);
debug_mesh->surface_set_material(1, line_material);
}
RS::get_singleton()->instance_set_base(debug_instance, debug_mesh->get_rid());
if (is_inside_tree()) {
RS::get_singleton()->instance_set_scenario(debug_instance, get_world_3d()->get_scenario());
RS::get_singleton()->instance_set_transform(debug_instance, current_global_transform);
RS::get_singleton()->instance_set_visible(debug_instance, is_visible_in_tree());
}
if (!is_enabled()) {
if (debug_mesh.is_valid()) {
if (debug_mesh->get_surface_count() > 0) {
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_disabled_material()->get_rid());
}
if (debug_mesh->get_surface_count() > 1) {
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 1, NavigationServer3D::get_singleton()->get_debug_navigation_geometry_edge_disabled_material()->get_rid());
}
}
} else {
if (debug_mesh.is_valid()) {
if (debug_mesh->get_surface_count() > 0) {
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, RID());
}
if (debug_mesh->get_surface_count() > 1) {
RS::get_singleton()->instance_set_surface_override_material(debug_instance, 1, RID());
}
}
}
}
#endif // DEBUG_ENABLED
#ifdef DEBUG_ENABLED
void NavigationRegion3D::_update_debug_edge_connections_mesh() {
if (!NavigationServer3D::get_singleton()->get_debug_enabled() || !NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
if (debug_edge_connections_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false);
}
return;
}
if (!is_inside_tree()) {
return;
}
if (!use_edge_connections || !NavigationServer3D::get_singleton()->map_get_use_edge_connections(get_world_3d()->get_navigation_map())) {
if (debug_edge_connections_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false);
}
return;
}
if (navigation_mesh.is_null()) {
if (debug_edge_connections_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false);
}
return;
}
if (!debug_edge_connections_instance.is_valid()) {
debug_edge_connections_instance = RenderingServer::get_singleton()->instance_create();
}
if (debug_edge_connections_mesh.is_null()) {
debug_edge_connections_mesh.instantiate();
}
debug_edge_connections_mesh->clear_surfaces();
float edge_connection_margin = NavigationServer3D::get_singleton()->map_get_edge_connection_margin(get_world_3d()->get_navigation_map());
float half_edge_connection_margin = edge_connection_margin * 0.5;
int connections_count = NavigationServer3D::get_singleton()->region_get_connections_count(region);
if (connections_count == 0) {
RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false);
return;
}
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);
Vector3 connection_pathway_end = NavigationServer3D::get_singleton()->region_get_connection_pathway_end(region, i);
Vector3 direction_start_end = connection_pathway_start.direction_to(connection_pathway_end);
Vector3 direction_end_start = connection_pathway_end.direction_to(connection_pathway_start);
Vector3 start_right_dir = direction_start_end.cross(Vector3(0, 1, 0));
Vector3 start_left_dir = -start_right_dir;
Vector3 end_right_dir = direction_end_start.cross(Vector3(0, 1, 0));
Vector3 end_left_dir = -end_right_dir;
Vector3 left_start_pos = connection_pathway_start + (start_left_dir * half_edge_connection_margin);
Vector3 right_start_pos = connection_pathway_start + (start_right_dir * half_edge_connection_margin);
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_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.is_empty()) {
return;
}
Ref<StandardMaterial3D> edge_connections_material = NavigationServer3D::get_singleton()->get_debug_navigation_edge_connections_material();
Array mesh_array;
mesh_array.resize(Mesh::ARRAY_MAX);
mesh_array[Mesh::ARRAY_VERTEX] = vertex_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());
RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, is_visible_in_tree());
if (is_inside_tree()) {
RS::get_singleton()->instance_set_scenario(debug_edge_connections_instance, get_world_3d()->get_scenario());
}
bool enable_edge_connections = NavigationServer3D::get_singleton()->get_debug_navigation_enable_edge_connections();
if (!enable_edge_connections) {
RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false);
}
}
#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;
}

View File

@@ -0,0 +1,124 @@
/**************************************************************************/
/* navigation_region_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
#include "scene/resources/navigation_mesh.h"
class NavigationRegion3D : public Node3D {
GDCLASS(NavigationRegion3D, Node3D);
bool enabled = true;
bool use_edge_connections = true;
RID region;
RID map_override;
uint32_t navigation_layers = 1;
real_t enter_cost = 0.0;
real_t travel_cost = 1.0;
Ref<NavigationMesh> navigation_mesh;
Transform3D current_global_transform;
void _navigation_mesh_changed();
AABB bounds;
#ifdef DEBUG_ENABLED
RID debug_instance;
RID debug_edge_connections_instance;
Ref<ArrayMesh> debug_mesh;
Ref<ArrayMesh> debug_edge_connections_mesh;
private:
void _update_debug_mesh();
void _update_debug_edge_connections_mesh();
void _navigation_map_changed(RID p_map);
void _navigation_debug_changed();
#endif // DEBUG_ENABLED
protected:
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
#endif // DISABLE_DEPRECATED
public:
RID get_rid() const;
void set_enabled(bool p_enabled);
bool is_enabled() const;
void set_navigation_map(RID p_navigation_map);
RID get_navigation_map() const;
void set_use_edge_connections(bool p_enabled);
bool get_use_edge_connections() const;
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers() const;
void set_navigation_layer_value(int p_layer_number, bool p_value);
bool get_navigation_layer_value(int p_layer_number) const;
RID get_region_rid() const;
void set_enter_cost(real_t p_enter_cost);
real_t get_enter_cost() const;
void set_travel_cost(real_t p_travel_cost);
real_t get_travel_cost() const;
void set_navigation_mesh(const Ref<NavigationMesh> &p_navigation_mesh);
Ref<NavigationMesh> get_navigation_mesh() const;
/// Bakes the navigation mesh; once done, automatically
/// sets the new navigation mesh and emits a signal
void bake_navigation_mesh(bool p_on_thread);
void _bake_finished();
bool is_baking() const;
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();
};

1563
scene/3d/node_3d.cpp Normal file

File diff suppressed because it is too large Load Diff

354
scene/3d/node_3d.h Normal file
View File

@@ -0,0 +1,354 @@
/**************************************************************************/
/* node_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. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
#include "scene/resources/3d/world_3d.h"
class Node3DGizmo : public RefCounted {
GDCLASS(Node3DGizmo, RefCounted);
public:
virtual void create() = 0;
virtual void transform() = 0;
virtual void clear() = 0;
virtual void redraw() = 0;
virtual void free() = 0;
Node3DGizmo();
virtual ~Node3DGizmo() {}
};
class Node3D : public Node {
GDCLASS(Node3D, Node);
friend class SceneTreeFTI;
friend class SceneTreeFTITests;
public:
// Edit mode for the rotation.
// THIS MODE ONLY AFFECTS HOW DATA IS EDITED AND SAVED
// IT DOES _NOT_ AFFECT THE TRANSFORM LOGIC (see comment in TransformDirty).
enum RotationEditMode {
ROTATION_EDIT_MODE_EULER,
ROTATION_EDIT_MODE_QUATERNION,
ROTATION_EDIT_MODE_BASIS,
};
private:
// For the sake of ease of use, Node3D can operate with Transforms (Basis+Origin), Quaternion/Scale and Euler Rotation/Scale.
// Transform and Quaternion are stored in data.local_transform Basis (so quaternion is not really stored, but converted back/forth from 3x3 matrix on demand).
// Euler needs to be kept separate because converting to Basis and back may result in a different vector (which is troublesome for users
// editing in the inspector, not only because of the numerical precision loss but because they expect these rotations to be consistent, or support
// "redundant" rotations for animation interpolation, like going from 0 to 720 degrees).
//
// As such, the system works in a way where if the local transform is set (via transform/basis/quaternion), the EULER rotation and scale becomes dirty.
// It will remain dirty until reading back is attempted (for performance reasons). Likewise, if the Euler rotation scale are set, the local transform
// will become dirty (and again, will not become valid again until read).
//
// All this is transparent from outside the Node3D API, which allows everything to works by calling these functions in exchange.
//
// Additionally, setting either transform, quaternion, Euler rotation or scale makes the global transform dirty, which will be updated when read again.
//
// NOTE: Again, RotationEditMode is _independent_ of this mechanism, it is only meant to expose the right set of properties for editing (editor) and saving
// (to scene, in order to keep the same values and avoid data loss on conversions). It has zero influence in the logic described above.
enum TransformDirty {
DIRTY_NONE = 0,
DIRTY_EULER_ROTATION_AND_SCALE = 1,
DIRTY_LOCAL_TRANSFORM = 2,
DIRTY_GLOBAL_TRANSFORM = 4,
DIRTY_GLOBAL_INTERPOLATED_TRANSFORM = 8,
};
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.
struct Data {
// Interpolated global transform - correct on the frame only.
// Only used with FTI.
Transform3D global_transform_interpolated;
// Current xforms are either
// * Used for everything (when not using FTI)
// * Correct on the physics tick (when using FTI)
mutable Transform3D global_transform;
mutable Transform3D local_transform;
// Only used with FTI.
Transform3D local_transform_prev;
mutable EulerOrder euler_rotation_order = EulerOrder::YXZ;
mutable Vector3 euler_rotation;
mutable Vector3 scale = Vector3(1, 1, 1);
mutable RotationEditMode rotation_edit_mode = ROTATION_EDIT_MODE_EULER;
mutable MTNumeric<uint32_t> dirty;
Viewport *viewport = nullptr;
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;
// Scene tree interpolation.
bool fti_on_frame_xform_list : 1;
bool fti_on_frame_property_list : 1;
bool fti_on_tick_xform_list : 1;
bool fti_on_tick_property_list : 1;
bool fti_global_xform_interp_set : 1;
bool fti_frame_xform_force_update : 1;
bool fti_is_identity_xform : 1;
bool fti_processed : 1;
RID visibility_parent;
Node3D *parent = nullptr;
List<Node3D *> children;
List<Node3D *>::Element *C = nullptr;
ClientPhysicsInterpolationData *client_physics_interpolation_data = nullptr;
#ifdef TOOLS_ENABLED
Vector<Ref<Node3DGizmo>> gizmos;
bool gizmos_requested : 1;
bool gizmos_disabled : 1;
bool gizmos_dirty : 1;
bool transform_gizmo_visible : 1;
#endif
} data;
NodePath visibility_parent_path;
_FORCE_INLINE_ uint32_t _read_dirty_mask() const { return is_group_processing() ? data.dirty.mt.get() : data.dirty.st; }
_FORCE_INLINE_ bool _test_dirty_bits(uint32_t p_bits) const { return is_group_processing() ? data.dirty.mt.bit_and(p_bits) : (data.dirty.st & p_bits); }
void _replace_dirty_mask(uint32_t p_mask) const;
void _set_dirty_bits(uint32_t p_bits) const;
void _clear_dirty_bits(uint32_t p_bits) const;
void _update_gizmos();
void _notify_dirty();
void _propagate_transform_changed(Node3D *p_origin);
void _propagate_visibility_changed();
void _propagate_visibility_parent();
void _update_visibility_parent(bool p_update_root);
void _propagate_transform_changed_deferred();
protected:
_FORCE_INLINE_ void set_ignore_transform_notification(bool p_ignore) { data.ignore_notification = p_ignore; }
_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);
const Transform3D &_get_cached_global_transform_interpolated() const { return data.global_transform_interpolated; }
void _disable_client_physics_interpolation();
// Calling this announces to the FTI system that a node has been moved,
// or requires an update in terms of interpolation
// (e.g. changing Camera zoom even if position hasn't changed).
void fti_notify_node_changed(bool p_transform_changed = true);
// Opportunity after FTI to update the servers
// with global_transform_interpolated,
// and any custom interpolated data in derived classes.
// Make sure to call the parent class fti_update_servers(),
// so all data is updated to the servers.
virtual void fti_update_servers_xform() {}
virtual void fti_update_servers_property() {}
// Pump the FTI data, also gives a chance for inherited classes
// to pump custom data, but they *must* call the base class here too.
// This is the opportunity for classes to move current values for
// transforms and properties to stored previous values,
// and this should take place both on ticks, and during resets.
virtual void fti_pump_xform();
virtual void fti_pump_property() {}
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
bool _property_can_revert(const StringName &p_name) const;
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
public:
enum {
NOTIFICATION_TRANSFORM_CHANGED = SceneTree::NOTIFICATION_TRANSFORM_CHANGED,
NOTIFICATION_ENTER_WORLD = 41,
NOTIFICATION_EXIT_WORLD = 42,
NOTIFICATION_VISIBILITY_CHANGED = 43,
NOTIFICATION_LOCAL_TRANSFORM_CHANGED = 44,
};
Node3D *get_parent_node_3d() const;
Ref<World3D> get_world_3d() const;
void set_position(const Vector3 &p_position);
void set_rotation_edit_mode(RotationEditMode p_mode);
RotationEditMode get_rotation_edit_mode() const;
void set_rotation_order(EulerOrder p_order);
void set_rotation(const Vector3 &p_euler_rad);
void set_rotation_degrees(const Vector3 &p_euler_degrees);
void set_scale(const Vector3 &p_scale);
void set_global_position(const Vector3 &p_position);
void set_global_basis(const Basis &p_basis);
void set_global_rotation(const Vector3 &p_euler_rad);
void set_global_rotation_degrees(const Vector3 &p_euler_degrees);
Vector3 get_position() const;
EulerOrder get_rotation_order() const;
Vector3 get_rotation() const;
Vector3 get_rotation_degrees() const;
Vector3 get_scale() const;
Vector3 get_global_position() const;
Basis get_global_basis() const;
Vector3 get_global_rotation() const;
Vector3 get_global_rotation_degrees() const;
void set_transform(const Transform3D &p_transform);
void set_basis(const Basis &p_basis);
void set_quaternion(const Quaternion &p_quaternion);
void set_global_transform(const Transform3D &p_transform);
Transform3D get_transform() const;
Basis get_basis() const;
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; }
#endif
virtual void reparent(Node *p_parent, bool p_keep_global_transform = true) override;
void set_disable_gizmos(bool p_enabled);
void update_gizmos();
void set_subgizmo_selection(Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform = Transform3D());
void clear_subgizmo_selection();
Vector<Ref<Node3DGizmo>> get_gizmos() const;
TypedArray<Node3DGizmo> get_gizmos_bind() const;
void add_gizmo(Ref<Node3DGizmo> p_gizmo);
void remove_gizmo(Ref<Node3DGizmo> p_gizmo);
void clear_gizmos();
void set_as_top_level(bool p_enabled);
void set_as_top_level_keep_local(bool p_enabled);
bool is_set_as_top_level() const;
void set_disable_scale(bool p_enabled);
bool is_scale_disabled() const;
_FORCE_INLINE_ bool is_inside_world() const { return data.inside_world; }
Transform3D get_relative_transform(const Node *p_parent) const;
void rotate(const Vector3 &p_axis, real_t p_angle);
void rotate_x(real_t p_angle);
void rotate_y(real_t p_angle);
void rotate_z(real_t p_angle);
void translate(const Vector3 &p_offset);
void scale(const Vector3 &p_ratio);
void rotate_object_local(const Vector3 &p_axis, real_t p_angle);
void scale_object_local(const Vector3 &p_scale);
void translate_object_local(const Vector3 &p_offset);
void global_rotate(const Vector3 &p_axis, real_t p_angle);
void global_scale(const Vector3 &p_scale);
void global_translate(const Vector3 &p_offset);
void look_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false);
void look_at_from_position(const Vector3 &p_pos, const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false);
Vector3 to_local(Vector3 p_global) const;
Vector3 to_global(Vector3 p_local) const;
void set_notify_transform(bool p_enabled);
bool is_transform_notification_enabled() const;
void set_notify_local_transform(bool p_enabled);
bool is_local_transform_notification_enabled() const;
void orthonormalize();
void set_identity();
void set_visible(bool p_visible);
void show();
void hide();
bool is_visible() const;
bool is_visible_in_tree() const;
void force_update_transform();
void set_visibility_parent(const NodePath &p_path);
NodePath get_visibility_parent() const;
Node3D();
~Node3D();
};
VARIANT_ENUM_CAST(Node3D::RotationEditMode)

View File

@@ -0,0 +1,754 @@
/**************************************************************************/
/* occluder_instance_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 "occluder_instance_3d.h"
#include "core/config/project_settings.h"
#include "core/io/marshalls.h"
#include "core/math/geometry_2d.h"
#include "core/math/triangulate.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/resources/3d/importer_mesh.h"
#include "scene/resources/surface_tool.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_node.h"
#endif
RID Occluder3D::get_rid() const {
return occluder;
}
void Occluder3D::_update() {
_update_arrays(vertices, indices);
aabb = AABB();
const Vector3 *ptr = vertices.ptr();
for (int i = 0; i < vertices.size(); i++) {
aabb.expand_to(ptr[i]);
}
debug_lines.clear();
debug_mesh.unref();
RS::get_singleton()->occluder_set_mesh(occluder, vertices, indices);
emit_changed();
}
PackedVector3Array Occluder3D::get_vertices() const {
return vertices;
}
PackedInt32Array Occluder3D::get_indices() const {
return indices;
}
Vector<Vector3> Occluder3D::get_debug_lines() const {
if (!debug_lines.is_empty()) {
return debug_lines;
}
if (indices.size() % 3 != 0) {
return Vector<Vector3>();
}
const Vector3 *vertices_ptr = vertices.ptr();
debug_lines.resize(indices.size() / 3 * 6);
Vector3 *line_ptr = debug_lines.ptrw();
int line_i = 0;
for (int i = 0; i < indices.size() / 3; i++) {
for (int j = 0; j < 3; j++) {
int a = indices[i * 3 + j];
int b = indices[i * 3 + (j + 1) % 3];
ERR_FAIL_INDEX_V_MSG(a, vertices.size(), Vector<Vector3>(), "Occluder indices are out of range.");
ERR_FAIL_INDEX_V_MSG(b, vertices.size(), Vector<Vector3>(), "Occluder indices are out of range.");
line_ptr[line_i++] = vertices_ptr[a];
line_ptr[line_i++] = vertices_ptr[b];
}
}
return debug_lines;
}
Ref<ArrayMesh> Occluder3D::get_debug_mesh() const {
if (debug_mesh.is_valid()) {
return debug_mesh;
}
if (vertices.is_empty() || indices.is_empty() || indices.size() % 3 != 0) {
return debug_mesh;
}
Array arrays;
arrays.resize(Mesh::ARRAY_MAX);
arrays[Mesh::ARRAY_VERTEX] = vertices;
arrays[Mesh::ARRAY_INDEX] = indices;
debug_mesh.instantiate();
debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
return debug_mesh;
}
AABB Occluder3D::get_aabb() const {
return aabb;
}
void Occluder3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
_update();
} break;
}
}
Occluder3D::Occluder3D() {
occluder = RS::get_singleton()->occluder_create();
}
Occluder3D::~Occluder3D() {
if (occluder.is_valid()) {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(occluder);
}
}
/////////////////////////////////////////////////
void ArrayOccluder3D::set_arrays(PackedVector3Array p_vertices, PackedInt32Array p_indices) {
vertices = p_vertices;
indices = p_indices;
_update();
}
void ArrayOccluder3D::set_vertices(PackedVector3Array p_vertices) {
vertices = p_vertices;
_update();
}
void ArrayOccluder3D::set_indices(PackedInt32Array p_indices) {
indices = p_indices;
_update();
}
void ArrayOccluder3D::_update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) {
r_vertices = vertices;
r_indices = indices;
}
void ArrayOccluder3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_arrays", "vertices", "indices"), &ArrayOccluder3D::set_arrays);
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &ArrayOccluder3D::set_vertices);
ClassDB::bind_method(D_METHOD("get_vertices"), &ArrayOccluder3D::get_vertices);
ClassDB::bind_method(D_METHOD("set_indices", "indices"), &ArrayOccluder3D::set_indices);
ClassDB::bind_method(D_METHOD("get_indices"), &ArrayOccluder3D::get_indices);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_vertices", "get_vertices");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_indices", "get_indices");
}
ArrayOccluder3D::ArrayOccluder3D() {
}
ArrayOccluder3D::~ArrayOccluder3D() {
}
/////////////////////////////////////////////////
void QuadOccluder3D::set_size(const Size2 &p_size) {
if (size == p_size) {
return;
}
size = p_size.maxf(0);
_update();
}
Size2 QuadOccluder3D::get_size() const {
return size;
}
void QuadOccluder3D::_update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) {
Size2 _size = Size2(size.x / 2.0f, size.y / 2.0f);
r_vertices = {
Vector3(-_size.x, -_size.y, 0),
Vector3(-_size.x, _size.y, 0),
Vector3(_size.x, _size.y, 0),
Vector3(_size.x, -_size.y, 0),
};
r_indices = {
0, 1, 2,
0, 2, 3
};
}
void QuadOccluder3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_size", "size"), &QuadOccluder3D::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &QuadOccluder3D::get_size);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size");
}
QuadOccluder3D::QuadOccluder3D() {
}
QuadOccluder3D::~QuadOccluder3D() {
}
/////////////////////////////////////////////////
void BoxOccluder3D::set_size(const Vector3 &p_size) {
if (size == p_size) {
return;
}
size = p_size.maxf(0);
_update();
}
Vector3 BoxOccluder3D::get_size() const {
return size;
}
void BoxOccluder3D::_update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) {
Vector3 _size = Vector3(size.x / 2.0f, size.y / 2.0f, size.z / 2.0f);
r_vertices = {
// front
Vector3(-_size.x, -_size.y, _size.z),
Vector3(_size.x, -_size.y, _size.z),
Vector3(_size.x, _size.y, _size.z),
Vector3(-_size.x, _size.y, _size.z),
// back
Vector3(-_size.x, -_size.y, -_size.z),
Vector3(_size.x, -_size.y, -_size.z),
Vector3(_size.x, _size.y, -_size.z),
Vector3(-_size.x, _size.y, -_size.z),
};
r_indices = {
// front
0, 1, 2,
2, 3, 0,
// right
1, 5, 6,
6, 2, 1,
// back
7, 6, 5,
5, 4, 7,
// left
4, 0, 3,
3, 7, 4,
// bottom
4, 5, 1,
1, 0, 4,
// top
3, 2, 6,
6, 7, 3
};
}
void BoxOccluder3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_size", "size"), &BoxOccluder3D::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &BoxOccluder3D::get_size);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size");
}
BoxOccluder3D::BoxOccluder3D() {
}
BoxOccluder3D::~BoxOccluder3D() {
}
/////////////////////////////////////////////////
void SphereOccluder3D::set_radius(float p_radius) {
if (radius == p_radius) {
return;
}
radius = MAX(p_radius, 0.0f);
_update();
}
float SphereOccluder3D::get_radius() const {
return radius;
}
void SphereOccluder3D::_update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) {
r_vertices.resize((RINGS + 2) * (RADIAL_SEGMENTS + 1));
int vertex_i = 0;
Vector3 *vertex_ptr = r_vertices.ptrw();
r_indices.resize((RINGS + 1) * RADIAL_SEGMENTS * 6);
int idx_i = 0;
int *idx_ptr = r_indices.ptrw();
int current_row = 0;
int previous_row = 0;
int point = 0;
for (int j = 0; j <= (RINGS + 1); j++) {
float v = j / float(RINGS + 1);
float w = Math::sin(Math::PI * v);
float y = Math::cos(Math::PI * v);
for (int i = 0; i <= RADIAL_SEGMENTS; i++) {
float u = i / float(RADIAL_SEGMENTS);
float x = Math::cos(u * Math::TAU);
float z = Math::sin(u * Math::TAU);
vertex_ptr[vertex_i++] = Vector3(x * w, y, z * w) * radius;
if (i > 0 && j > 0) {
idx_ptr[idx_i++] = previous_row + i - 1;
idx_ptr[idx_i++] = previous_row + i;
idx_ptr[idx_i++] = current_row + i - 1;
idx_ptr[idx_i++] = previous_row + i;
idx_ptr[idx_i++] = current_row + i;
idx_ptr[idx_i++] = current_row + i - 1;
}
point++;
}
previous_row = current_row;
current_row = point;
}
}
void SphereOccluder3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereOccluder3D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &SphereOccluder3D::get_radius);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_NONE, "suffix:m"), "set_radius", "get_radius");
}
SphereOccluder3D::SphereOccluder3D() {
}
SphereOccluder3D::~SphereOccluder3D() {
}
/////////////////////////////////////////////////
void PolygonOccluder3D::set_polygon(const Vector<Vector2> &p_polygon) {
polygon = p_polygon;
_update();
}
Vector<Vector2> PolygonOccluder3D::get_polygon() const {
return polygon;
}
void PolygonOccluder3D::_update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) {
if (polygon.size() < 3) {
r_vertices.clear();
r_indices.clear();
return;
}
Vector<Point2> occluder_polygon = polygon;
if (Triangulate::get_area(occluder_polygon) > 0) {
occluder_polygon.reverse();
}
Vector<int> occluder_indices = Geometry2D::triangulate_polygon(occluder_polygon);
if (occluder_indices.size() < 3) {
r_vertices.clear();
r_indices.clear();
ERR_FAIL_MSG("Failed to triangulate PolygonOccluder3D. Make sure the polygon doesn't have any intersecting edges.");
}
r_vertices.resize(occluder_polygon.size());
Vector3 *vertex_ptr = r_vertices.ptrw();
const Vector2 *polygon_ptr = occluder_polygon.ptr();
for (int i = 0; i < occluder_polygon.size(); i++) {
vertex_ptr[i] = Vector3(polygon_ptr[i].x, polygon_ptr[i].y, 0.0);
}
r_indices.resize(occluder_indices.size());
memcpy(r_indices.ptrw(), occluder_indices.ptr(), occluder_indices.size() * sizeof(int));
}
bool PolygonOccluder3D::_has_editable_3d_polygon_no_depth() const {
return false;
}
void PolygonOccluder3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &PolygonOccluder3D::set_polygon);
ClassDB::bind_method(D_METHOD("get_polygon"), &PolygonOccluder3D::get_polygon);
ClassDB::bind_method(D_METHOD("_has_editable_3d_polygon_no_depth"), &PolygonOccluder3D::_has_editable_3d_polygon_no_depth);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");
}
PolygonOccluder3D::PolygonOccluder3D() {
}
PolygonOccluder3D::~PolygonOccluder3D() {
}
/////////////////////////////////////////////////
AABB OccluderInstance3D::get_aabb() const {
if (occluder.is_valid()) {
return occluder->get_aabb();
}
return AABB();
}
void OccluderInstance3D::set_occluder(const Ref<Occluder3D> &p_occluder) {
if (occluder == p_occluder) {
return;
}
if (occluder.is_valid()) {
occluder->disconnect_changed(callable_mp(this, &OccluderInstance3D::_occluder_changed));
}
occluder = p_occluder;
if (occluder.is_valid()) {
set_base(occluder->get_rid());
occluder->connect_changed(callable_mp(this, &OccluderInstance3D::_occluder_changed));
} else {
set_base(RID());
}
update_gizmos();
update_configuration_warnings();
#ifdef TOOLS_ENABLED
// PolygonOccluder3D is edited via an editor plugin, this ensures the plugin is shown/hidden when necessary
if (Engine::get_singleton()->is_editor_hint()) {
callable_mp(EditorNode::get_singleton(), &EditorNode::edit_current).call_deferred();
}
#endif
}
void OccluderInstance3D::_occluder_changed() {
update_gizmos();
update_configuration_warnings();
}
Ref<Occluder3D> OccluderInstance3D::get_occluder() const {
return occluder;
}
void OccluderInstance3D::set_bake_mask(uint32_t p_mask) {
bake_mask = p_mask;
update_configuration_warnings();
}
uint32_t OccluderInstance3D::get_bake_mask() const {
return bake_mask;
}
void OccluderInstance3D::set_bake_simplification_distance(float p_dist) {
bake_simplification_dist = MAX(p_dist, 0.0f);
}
float OccluderInstance3D::get_bake_simplification_distance() const {
return bake_simplification_dist;
}
void OccluderInstance3D::set_bake_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_bake_mask();
if (p_value) {
mask |= 1 << (p_layer_number - 1);
} else {
mask &= ~(1 << (p_layer_number - 1));
}
set_bake_mask(mask);
}
bool OccluderInstance3D::get_bake_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 bake_mask & (1 << (p_layer_number - 1));
}
bool OccluderInstance3D::_bake_material_check(Ref<Material> p_material) {
StandardMaterial3D *standard_mat = Object::cast_to<StandardMaterial3D>(p_material.ptr());
if (standard_mat && standard_mat->get_transparency() != StandardMaterial3D::TRANSPARENCY_DISABLED) {
return false;
}
return true;
}
void OccluderInstance3D::_bake_surface(const Transform3D &p_transform, Array p_surface_arrays, Ref<Material> p_material, float p_simplification_dist, PackedVector3Array &r_vertices, PackedInt32Array &r_indices) {
if (!_bake_material_check(p_material)) {
return;
}
ERR_FAIL_COND_MSG(p_surface_arrays.size() != Mesh::ARRAY_MAX, "Invalid surface array.");
PackedVector3Array vertices = p_surface_arrays[Mesh::ARRAY_VERTEX];
PackedInt32Array indices = p_surface_arrays[Mesh::ARRAY_INDEX];
if (vertices.is_empty() || indices.is_empty()) {
return;
}
Vector3 *vertices_ptr = vertices.ptrw();
for (int j = 0; j < vertices.size(); j++) {
vertices_ptr[j] = p_transform.xform(vertices_ptr[j]);
}
if (!Math::is_zero_approx(p_simplification_dist) && SurfaceTool::simplify_func) {
Vector<float> vertices_f32 = vector3_to_float32_array(vertices.ptr(), vertices.size());
float error_scale = SurfaceTool::simplify_scale_func(vertices_f32.ptr(), vertices.size(), sizeof(float) * 3);
float target_error = p_simplification_dist / error_scale;
float error = -1.0f;
int target_index_count = MIN(indices.size(), 36);
const int simplify_options = SurfaceTool::SIMPLIFY_LOCK_BORDER;
uint32_t index_count = SurfaceTool::simplify_func(
(unsigned int *)indices.ptrw(),
(unsigned int *)indices.ptr(),
indices.size(),
vertices_f32.ptr(), vertices.size(), sizeof(float) * 3,
target_index_count, target_error, simplify_options, &error);
indices.resize(index_count);
}
SurfaceTool::strip_mesh_arrays(vertices, indices);
int vertex_offset = r_vertices.size();
r_vertices.resize(vertex_offset + vertices.size());
memcpy(r_vertices.ptrw() + vertex_offset, vertices.ptr(), vertices.size() * sizeof(Vector3));
int index_offset = r_indices.size();
r_indices.resize(index_offset + indices.size());
int *idx_ptr = r_indices.ptrw();
for (int j = 0; j < indices.size(); j++) {
idx_ptr[index_offset + j] = vertex_offset + indices[j];
}
}
void OccluderInstance3D::_bake_node(Node *p_node, PackedVector3Array &r_vertices, PackedInt32Array &r_indices) {
MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_node);
if (mi && mi->is_visible_in_tree()) {
Ref<Mesh> mesh = mi->get_mesh();
bool valid = true;
if (mesh.is_null()) {
valid = false;
}
if (valid && !_bake_material_check(mi->get_material_override())) {
valid = false;
}
if ((mi->get_layer_mask() & bake_mask) == 0) {
valid = false;
}
if (valid) {
Transform3D global_to_local = get_global_transform().affine_inverse() * mi->get_global_transform();
for (int i = 0; i < mesh->get_surface_count(); i++) {
_bake_surface(global_to_local, mesh->surface_get_arrays(i), mi->get_active_material(i), bake_simplification_dist, r_vertices, r_indices);
}
}
}
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *child = p_node->get_child(i);
if (!child->get_owner()) {
continue; // may be a helper
}
_bake_node(child, r_vertices, r_indices);
}
}
void OccluderInstance3D::bake_single_node(const Node3D *p_node, float p_simplification_distance, PackedVector3Array &r_vertices, PackedInt32Array &r_indices) {
ERR_FAIL_NULL(p_node);
Transform3D xform = p_node->is_inside_tree() ? p_node->get_global_transform() : p_node->get_transform();
const MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_node);
if (mi) {
Ref<Mesh> mesh = mi->get_mesh();
bool valid = true;
if (mesh.is_null()) {
valid = false;
}
if (valid && !_bake_material_check(mi->get_material_override())) {
valid = false;
}
if (valid) {
for (int i = 0; i < mesh->get_surface_count(); i++) {
_bake_surface(xform, mesh->surface_get_arrays(i), mi->get_active_material(i), p_simplification_distance, r_vertices, r_indices);
}
}
}
const ImporterMeshInstance3D *imi = Object::cast_to<ImporterMeshInstance3D>(p_node);
if (imi) {
Ref<ImporterMesh> mesh = imi->get_mesh();
bool valid = true;
if (mesh.is_null()) {
valid = false;
}
if (valid) {
for (int i = 0; i < mesh->get_surface_count(); i++) {
Ref<Material> material = imi->get_surface_material(i);
if (material.is_null()) {
material = mesh->get_surface_material(i);
}
_bake_surface(xform, mesh->get_surface_arrays(i), material, p_simplification_distance, r_vertices, r_indices);
}
}
}
}
OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node, String p_occluder_path) {
if (p_occluder_path.is_empty()) {
if (get_occluder().is_null()) {
return BAKE_ERROR_NO_SAVE_PATH;
}
p_occluder_path = get_occluder()->get_path();
if (!p_occluder_path.is_resource_file()) {
return BAKE_ERROR_NO_SAVE_PATH;
}
}
PackedVector3Array vertices;
PackedInt32Array indices;
_bake_node(p_from_node, vertices, indices);
if (vertices.is_empty() || indices.is_empty()) {
return BAKE_ERROR_NO_MESHES;
}
Ref<ArrayOccluder3D> occ;
if (get_occluder().is_valid()) {
occ = get_occluder();
set_occluder(Ref<Occluder3D>()); // clear
}
if (occ.is_null()) {
occ.instantiate();
}
occ->set_arrays(vertices, indices);
Error err = ResourceSaver::save(occ, p_occluder_path);
if (err != OK) {
return BAKE_ERROR_CANT_SAVE;
}
occ->set_path(p_occluder_path);
set_occluder(occ);
return BAKE_ERROR_OK;
}
PackedStringArray OccluderInstance3D::get_configuration_warnings() const {
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (!bool(GLOBAL_GET_CACHED(bool, "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."));
}
if (bake_mask == 0) {
warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any occluder meshes for this OccluderInstance3D.\nTo resolve this, enable at least one bit in the Bake Mask property."));
}
if (occluder.is_null()) {
warnings.push_back(RTR("No occluder mesh is defined in the Occluder property, so no occlusion culling will be performed using this OccluderInstance3D.\nTo resolve this, set the Occluder property to one of the primitive occluder types or bake the scene meshes by selecting the OccluderInstance3D and pressing the Bake Occluders button at the top of the 3D editor viewport."));
} else {
Ref<ArrayOccluder3D> arr_occluder = occluder;
if (arr_occluder.is_valid() && arr_occluder->get_indices().size() < 3) {
// Setting a new ArrayOccluder3D from the inspector will create an empty occluder,
// so warn the user about this.
warnings.push_back(RTR("The occluder mesh has less than 3 vertices, so no occlusion culling will be performed using this OccluderInstance3D.\nTo generate a proper occluder mesh, select the OccluderInstance3D then use the Bake Occluders button at the top of the 3D editor viewport."));
}
Ref<PolygonOccluder3D> poly_occluder = occluder;
if (poly_occluder.is_valid() && poly_occluder->get_polygon().size() < 3) {
warnings.push_back(RTR("The polygon occluder has less than 3 vertices, so no occlusion culling will be performed using this OccluderInstance3D.\nVertices can be added in the inspector or using the polygon editing tools at the top of the 3D editor viewport."));
}
}
return warnings;
}
bool OccluderInstance3D::_is_editable_3d_polygon() const {
return Ref<PolygonOccluder3D>(occluder).is_valid();
}
Ref<Resource> OccluderInstance3D::_get_editable_3d_polygon_resource() const {
return occluder;
}
void OccluderInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bake_mask", "mask"), &OccluderInstance3D::set_bake_mask);
ClassDB::bind_method(D_METHOD("get_bake_mask"), &OccluderInstance3D::get_bake_mask);
ClassDB::bind_method(D_METHOD("set_bake_mask_value", "layer_number", "value"), &OccluderInstance3D::set_bake_mask_value);
ClassDB::bind_method(D_METHOD("get_bake_mask_value", "layer_number"), &OccluderInstance3D::get_bake_mask_value);
ClassDB::bind_method(D_METHOD("set_bake_simplification_distance", "simplification_distance"), &OccluderInstance3D::set_bake_simplification_distance);
ClassDB::bind_method(D_METHOD("get_bake_simplification_distance"), &OccluderInstance3D::get_bake_simplification_distance);
ClassDB::bind_method(D_METHOD("set_occluder", "occluder"), &OccluderInstance3D::set_occluder);
ClassDB::bind_method(D_METHOD("get_occluder"), &OccluderInstance3D::get_occluder);
ClassDB::bind_method(D_METHOD("_is_editable_3d_polygon"), &OccluderInstance3D::_is_editable_3d_polygon);
ClassDB::bind_method(D_METHOD("_get_editable_3d_polygon_resource"), &OccluderInstance3D::_get_editable_3d_polygon_resource);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "occluder", PROPERTY_HINT_RESOURCE_TYPE, "Occluder3D"), "set_occluder", "get_occluder");
ADD_GROUP("Bake", "bake_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_bake_mask", "get_bake_mask");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_simplification_distance", PROPERTY_HINT_RANGE, "0.0,2.0,0.01,suffix:m"), "set_bake_simplification_distance", "get_bake_simplification_distance");
}
OccluderInstance3D::OccluderInstance3D() {
}
OccluderInstance3D::~OccluderInstance3D() {
}

View File

@@ -0,0 +1,210 @@
/**************************************************************************/
/* occluder_instance_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/visual_instance_3d.h"
class Occluder3D : public Resource {
GDCLASS(Occluder3D, Resource);
RES_BASE_EXTENSION("occ");
RID occluder;
PackedVector3Array vertices;
PackedInt32Array indices;
AABB aabb;
mutable Ref<ArrayMesh> debug_mesh;
mutable Vector<Vector3> debug_lines;
protected:
void _update();
virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) = 0;
void _notification(int p_what);
public:
PackedVector3Array get_vertices() const;
PackedInt32Array get_indices() const;
Vector<Vector3> get_debug_lines() const;
Ref<ArrayMesh> get_debug_mesh() const;
AABB get_aabb() const;
virtual RID get_rid() const override;
Occluder3D();
virtual ~Occluder3D();
};
class ArrayOccluder3D : public Occluder3D {
GDCLASS(ArrayOccluder3D, Occluder3D);
PackedVector3Array vertices;
PackedInt32Array indices;
protected:
virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) override;
static void _bind_methods();
public:
void set_arrays(PackedVector3Array p_vertices, PackedInt32Array p_indices);
void set_vertices(PackedVector3Array p_vertices);
void set_indices(PackedInt32Array p_indices);
ArrayOccluder3D();
~ArrayOccluder3D();
};
class QuadOccluder3D : public Occluder3D {
GDCLASS(QuadOccluder3D, Occluder3D);
private:
Size2 size = Vector2(1.0f, 1.0f);
protected:
virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) override;
static void _bind_methods();
public:
Size2 get_size() const;
void set_size(const Size2 &p_size);
QuadOccluder3D();
~QuadOccluder3D();
};
class BoxOccluder3D : public Occluder3D {
GDCLASS(BoxOccluder3D, Occluder3D);
private:
Vector3 size = Vector3(1.0f, 1.0f, 1.0f);
protected:
virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) override;
static void _bind_methods();
public:
Vector3 get_size() const;
void set_size(const Vector3 &p_size);
BoxOccluder3D();
~BoxOccluder3D();
};
class SphereOccluder3D : public Occluder3D {
GDCLASS(SphereOccluder3D, Occluder3D);
private:
static constexpr int RINGS = 7;
static constexpr int RADIAL_SEGMENTS = 7;
float radius = 1.0f;
protected:
virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) override;
static void _bind_methods();
public:
float get_radius() const;
void set_radius(float p_radius);
SphereOccluder3D();
~SphereOccluder3D();
};
class PolygonOccluder3D : public Occluder3D {
GDCLASS(PolygonOccluder3D, Occluder3D);
private:
Vector<Vector2> polygon;
bool _has_editable_3d_polygon_no_depth() const;
protected:
virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) override;
static void _bind_methods();
public:
void set_polygon(const Vector<Vector2> &p_polygon);
Vector<Vector2> get_polygon() const;
PolygonOccluder3D();
~PolygonOccluder3D();
};
class OccluderInstance3D : public VisualInstance3D {
GDCLASS(OccluderInstance3D, VisualInstance3D);
private:
Ref<Occluder3D> occluder;
uint32_t bake_mask = 0xFFFFFFFF;
float bake_simplification_dist = 0.1f;
void _occluder_changed();
static bool _bake_material_check(Ref<Material> p_material);
static void _bake_surface(const Transform3D &p_transform, Array p_surface_arrays, Ref<Material> p_material, float p_simplification_dist, PackedVector3Array &r_vertices, PackedInt32Array &r_indices);
void _bake_node(Node *p_node, PackedVector3Array &r_vertices, PackedInt32Array &r_indices);
bool _is_editable_3d_polygon() const;
Ref<Resource> _get_editable_3d_polygon_resource() const;
protected:
static void _bind_methods();
public:
virtual PackedStringArray get_configuration_warnings() const override;
enum BakeError {
BAKE_ERROR_OK,
BAKE_ERROR_NO_SAVE_PATH,
BAKE_ERROR_NO_MESHES,
BAKE_ERROR_CANT_SAVE,
};
void set_occluder(const Ref<Occluder3D> &p_occluder);
Ref<Occluder3D> get_occluder() const;
virtual AABB get_aabb() const override;
void set_bake_mask(uint32_t p_mask);
uint32_t get_bake_mask() const;
void set_bake_simplification_distance(float p_dist);
float get_bake_simplification_distance() const;
void set_bake_mask_value(int p_layer_number, bool p_enable);
bool get_bake_mask_value(int p_layer_number) const;
BakeError bake_scene(Node *p_from_node, String p_occluder_path = "");
static void bake_single_node(const Node3D *p_node, float p_simplification_distance, PackedVector3Array &r_vertices, PackedInt32Array &r_indices);
OccluderInstance3D();
~OccluderInstance3D();
};

563
scene/3d/path_3d.cpp Normal file
View File

@@ -0,0 +1,563 @@
/**************************************************************************/
/* path_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 "path_3d.h"
Path3D::Path3D() {
SceneTree *st = SceneTree::get_singleton();
if (st && st->is_debugging_paths_hint()) {
debug_instance = RS::get_singleton()->instance_create();
set_notify_transform(true);
_update_debug_mesh();
}
}
Path3D::~Path3D() {
if (debug_instance.is_valid()) {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(debug_instance);
}
if (debug_mesh.is_valid()) {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(debug_mesh->get_rid());
}
}
void Path3D::set_update_callback(Callable p_callback) {
update_callback = p_callback;
}
void Path3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
SceneTree *st = SceneTree::get_singleton();
if (st && st->is_debugging_paths_hint()) {
_update_debug_mesh();
}
} break;
case NOTIFICATION_EXIT_TREE: {
SceneTree *st = SceneTree::get_singleton();
if (st && st->is_debugging_paths_hint()) {
RS::get_singleton()->instance_set_visible(debug_instance, false);
}
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if (is_inside_tree()) {
if (debug_instance.is_valid()) {
RS::get_singleton()->instance_set_transform(debug_instance, get_global_transform());
}
update_callback.call();
}
} break;
}
}
void Path3D::_update_debug_mesh() {
SceneTree *st = SceneTree::get_singleton();
if (!(st && st->is_debugging_paths_hint())) {
return;
}
if (debug_mesh.is_null()) {
debug_mesh.instantiate();
}
if (curve.is_null()) {
RS::get_singleton()->instance_set_visible(debug_instance, false);
return;
}
if (curve->get_point_count() < 2) {
RS::get_singleton()->instance_set_visible(debug_instance, false);
return;
}
real_t interval = 0.1;
const real_t length = curve->get_baked_length();
if (length <= CMP_EPSILON) {
RS::get_singleton()->instance_set_visible(debug_instance, false);
return;
}
const int sample_count = int(length / interval) + 2;
interval = length / (sample_count - 1);
Vector<Vector3> ribbon;
ribbon.resize(sample_count);
Vector3 *ribbon_ptr = ribbon.ptrw();
Vector<Vector3> bones;
bones.resize(sample_count * 4);
Vector3 *bones_ptr = bones.ptrw();
for (int i = 0; i < sample_count; i++) {
const Transform3D r = curve->sample_baked_with_rotation(i * interval, true, true);
const Vector3 p1 = r.origin;
const Vector3 side = r.basis.get_column(0);
const Vector3 up = r.basis.get_column(1);
const Vector3 forward = r.basis.get_column(2);
// Path3D as a ribbon.
ribbon_ptr[i] = p1;
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;
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;
ribbon_array.resize(Mesh::ARRAY_MAX);
ribbon_array[Mesh::ARRAY_VERTEX] = ribbon;
Array bone_array;
bone_array.resize(Mesh::ARRAY_MAX);
bone_array[Mesh::ARRAY_VERTEX] = bones;
_update_debug_path_material();
debug_mesh->clear_surfaces();
debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINE_STRIP, ribbon_array);
debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, bone_array);
debug_mesh->surface_set_material(0, debug_material);
debug_mesh->surface_set_material(1, debug_material);
RS::get_singleton()->instance_set_base(debug_instance, debug_mesh->get_rid());
if (is_inside_tree()) {
RS::get_singleton()->instance_set_scenario(debug_instance, get_world_3d()->get_scenario());
RS::get_singleton()->instance_set_transform(debug_instance, get_global_transform());
RS::get_singleton()->instance_set_visible(debug_instance, is_visible_in_tree());
}
}
void Path3D::set_debug_custom_color(const Color &p_color) {
debug_custom_color = p_color;
_update_debug_path_material();
}
Ref<StandardMaterial3D> Path3D::get_debug_material() {
return debug_material;
}
const Color &Path3D::get_debug_custom_color() const {
return debug_custom_color;
}
void Path3D::_update_debug_path_material() {
SceneTree *st = SceneTree::get_singleton();
if (!debug_material.is_valid()) {
Ref<StandardMaterial3D> material = memnew(StandardMaterial3D);
debug_material = material;
material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
}
Color color = debug_custom_color;
if (color == Color(0.0, 0.0, 0.0)) {
// Use the default debug path color defined in the Project Settings.
color = st->get_debug_paths_color();
}
get_debug_material()->set_albedo(color);
emit_signal(SNAME("debug_color_changed"));
}
void Path3D::_curve_changed() {
if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) {
update_gizmos();
}
if (is_inside_tree()) {
emit_signal(SNAME("curve_changed"));
}
// Update the configuration warnings of all children of type PathFollow
// previously used for PathFollowOriented (now enforced orientation is done in PathFollow). Also trigger transform update on PathFollow3Ds in deferred mode.
if (is_inside_tree()) {
for (int i = 0; i < get_child_count(); i++) {
PathFollow3D *child = Object::cast_to<PathFollow3D>(get_child(i));
if (child) {
child->update_configuration_warnings();
child->update_transform();
}
}
}
SceneTree *st = SceneTree::get_singleton();
if (st && st->is_debugging_paths_hint()) {
_update_debug_mesh();
}
}
void Path3D::set_curve(const Ref<Curve3D> &p_curve) {
if (curve.is_valid()) {
curve->disconnect_changed(callable_mp(this, &Path3D::_curve_changed));
}
curve = p_curve;
if (curve.is_valid()) {
curve->connect_changed(callable_mp(this, &Path3D::_curve_changed));
}
_curve_changed();
}
Ref<Curve3D> Path3D::get_curve() const {
return curve;
}
void Path3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_curve", "curve"), &Path3D::set_curve);
ClassDB::bind_method(D_METHOD("get_curve"), &Path3D::get_curve);
ClassDB::bind_method(D_METHOD("set_debug_custom_color", "debug_custom_color"), &Path3D::set_debug_custom_color);
ClassDB::bind_method(D_METHOD("get_debug_custom_color"), &Path3D::get_debug_custom_color);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve3D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_curve", "get_curve");
ADD_GROUP("Debug Shape", "debug_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "debug_custom_color"), "set_debug_custom_color", "get_debug_custom_color");
ADD_SIGNAL(MethodInfo("curve_changed"));
ADD_SIGNAL(MethodInfo("debug_color_changed"));
}
void PathFollow3D::update_transform() {
if (!path) {
return;
}
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
return;
}
real_t bl = c->get_baked_length();
if (bl == 0.0) {
return;
}
Transform3D t;
if (rotation_mode == ROTATION_NONE) {
Vector3 pos = c->sample_baked(progress, cubic);
t.origin = pos;
} else {
t = c->sample_baked_with_rotation(progress, cubic, false);
Vector3 tangent = -t.basis.get_column(2); // Retain tangent for applying tilt.
t = PathFollow3D::correct_posture(t, rotation_mode);
// Switch Z+ and Z- if necessary.
if (use_model_front) {
t.basis *= Basis::from_scale(Vector3(-1.0, 1.0, -1.0));
}
// Apply tilt *after* correct_posture().
if (tilt_enabled) {
const real_t tilt = c->sample_baked_tilt(progress);
const Basis twist(tangent, tilt);
t.basis = twist * t.basis;
}
}
// Apply offset and scale.
Vector3 scale = get_transform().basis.get_scale();
t.translate_local(Vector3(h_offset, v_offset, 0));
t.basis.scale_local(scale);
set_transform(t);
}
void PathFollow3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
Node *parent = get_parent();
if (parent) {
path = Object::cast_to<Path3D>(parent);
update_transform();
}
} break;
case NOTIFICATION_EXIT_TREE: {
path = nullptr;
} break;
}
}
void PathFollow3D::set_cubic_interpolation_enabled(bool p_enabled) {
cubic = p_enabled;
}
bool PathFollow3D::is_cubic_interpolation_enabled() const {
return cubic;
}
void PathFollow3D::_validate_property(PropertyInfo &p_property) const {
if (!Engine::get_singleton()->is_editor_hint()) {
return;
}
if (p_property.name == "offset") {
real_t max = 10000;
if (path && path->get_curve().is_valid()) {
max = path->get_curve()->get_baked_length();
}
p_property.hint_string = "0," + rtos(max) + ",0.01,or_less,or_greater";
}
}
PackedStringArray PathFollow3D::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!Object::cast_to<Path3D>(get_parent())) {
warnings.push_back(RTR("PathFollow3D only works when set as a child of a Path3D node."));
} else {
Path3D *p = Object::cast_to<Path3D>(get_parent());
if (p->get_curve().is_valid() && !p->get_curve()->is_up_vector_enabled() && rotation_mode == ROTATION_ORIENTED) {
warnings.push_back(RTR("PathFollow3D's ROTATION_ORIENTED requires \"Up Vector\" to be enabled in its parent Path3D's Curve resource."));
}
}
}
return warnings;
}
Transform3D PathFollow3D::correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode) {
Transform3D t = p_transform;
// Modify frame according to rotation mode.
if (p_rotation_mode == PathFollow3D::ROTATION_NONE) {
// Clear rotation.
t.basis = Basis();
} else if (p_rotation_mode == PathFollow3D::ROTATION_ORIENTED) {
Vector3 tangent = -t.basis.get_column(2);
// Y-axis points up by default.
t.basis = Basis::looking_at(tangent);
} else {
// Lock some euler axes.
Vector3 euler = t.basis.get_euler_normalized(EulerOrder::YXZ);
if (p_rotation_mode == PathFollow3D::ROTATION_Y) {
// Only Y-axis allowed.
euler[0] = 0;
euler[2] = 0;
} else if (p_rotation_mode == PathFollow3D::ROTATION_XY) {
// XY allowed.
euler[2] = 0;
}
Basis locked = Basis::from_euler(euler, EulerOrder::YXZ);
t.basis = locked;
}
return t;
}
void PathFollow3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_progress", "progress"), &PathFollow3D::set_progress);
ClassDB::bind_method(D_METHOD("get_progress"), &PathFollow3D::get_progress);
ClassDB::bind_method(D_METHOD("set_h_offset", "h_offset"), &PathFollow3D::set_h_offset);
ClassDB::bind_method(D_METHOD("get_h_offset"), &PathFollow3D::get_h_offset);
ClassDB::bind_method(D_METHOD("set_v_offset", "v_offset"), &PathFollow3D::set_v_offset);
ClassDB::bind_method(D_METHOD("get_v_offset"), &PathFollow3D::get_v_offset);
ClassDB::bind_method(D_METHOD("set_progress_ratio", "ratio"), &PathFollow3D::set_progress_ratio);
ClassDB::bind_method(D_METHOD("get_progress_ratio"), &PathFollow3D::get_progress_ratio);
ClassDB::bind_method(D_METHOD("set_rotation_mode", "rotation_mode"), &PathFollow3D::set_rotation_mode);
ClassDB::bind_method(D_METHOD("get_rotation_mode"), &PathFollow3D::get_rotation_mode);
ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enabled"), &PathFollow3D::set_cubic_interpolation_enabled);
ClassDB::bind_method(D_METHOD("get_cubic_interpolation"), &PathFollow3D::is_cubic_interpolation_enabled);
ClassDB::bind_method(D_METHOD("set_use_model_front", "enabled"), &PathFollow3D::set_use_model_front);
ClassDB::bind_method(D_METHOD("is_using_model_front"), &PathFollow3D::is_using_model_front);
ClassDB::bind_method(D_METHOD("set_loop", "loop"), &PathFollow3D::set_loop);
ClassDB::bind_method(D_METHOD("has_loop"), &PathFollow3D::has_loop);
ClassDB::bind_method(D_METHOD("set_tilt_enabled", "enabled"), &PathFollow3D::set_tilt_enabled);
ClassDB::bind_method(D_METHOD("is_tilt_enabled"), &PathFollow3D::is_tilt_enabled);
ClassDB::bind_static_method("PathFollow3D", D_METHOD("correct_posture", "transform", "rotation_mode"), &PathFollow3D::correct_posture);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "progress", PROPERTY_HINT_RANGE, "0,10000,0.01,or_less,or_greater,suffix:m"), "set_progress", "get_progress");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "progress_ratio", PROPERTY_HINT_RANGE, "0,1,0.0001,or_less,or_greater", PROPERTY_USAGE_EDITOR), "set_progress_ratio", "get_progress_ratio");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_h_offset", "get_h_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_v_offset", "get_v_offset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_mode", PROPERTY_HINT_ENUM, "None,Y,XY,XYZ,Oriented"), "set_rotation_mode", "get_rotation_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_model_front"), "set_use_model_front", "is_using_model_front");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tilt_enabled"), "set_tilt_enabled", "is_tilt_enabled");
BIND_ENUM_CONSTANT(ROTATION_NONE);
BIND_ENUM_CONSTANT(ROTATION_Y);
BIND_ENUM_CONSTANT(ROTATION_XY);
BIND_ENUM_CONSTANT(ROTATION_XYZ);
BIND_ENUM_CONSTANT(ROTATION_ORIENTED);
}
void PathFollow3D::set_progress(real_t p_progress) {
ERR_FAIL_COND(!std::isfinite(p_progress));
if (progress == p_progress) {
return;
}
progress = p_progress;
if (path) {
if (path->get_curve().is_valid()) {
real_t path_length = path->get_curve()->get_baked_length();
if (loop && path_length) {
progress = Math::fposmod(progress, path_length);
if (!Math::is_zero_approx(p_progress) && Math::is_zero_approx(progress)) {
progress = path_length;
}
} else {
progress = CLAMP(progress, 0, path_length);
}
}
update_transform();
}
}
void PathFollow3D::set_h_offset(real_t p_h_offset) {
if (h_offset == p_h_offset) {
return;
}
h_offset = p_h_offset;
update_transform();
}
real_t PathFollow3D::get_h_offset() const {
return h_offset;
}
void PathFollow3D::set_v_offset(real_t p_v_offset) {
if (v_offset == p_v_offset) {
return;
}
v_offset = p_v_offset;
update_transform();
}
real_t PathFollow3D::get_v_offset() const {
return v_offset;
}
real_t PathFollow3D::get_progress() const {
return progress;
}
void PathFollow3D::set_progress_ratio(real_t p_ratio) {
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 {
if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) {
return get_progress() / path->get_curve()->get_baked_length();
} else {
return 0;
}
}
void PathFollow3D::set_rotation_mode(RotationMode p_rotation_mode) {
if (rotation_mode == p_rotation_mode) {
return;
}
rotation_mode = p_rotation_mode;
update_configuration_warnings();
update_transform();
}
PathFollow3D::RotationMode PathFollow3D::get_rotation_mode() const {
return rotation_mode;
}
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();
}
bool PathFollow3D::is_using_model_front() const {
return use_model_front;
}
void PathFollow3D::set_loop(bool p_loop) {
if (loop == p_loop) {
return;
}
loop = p_loop;
update_transform();
}
bool PathFollow3D::has_loop() const {
return loop;
}
void PathFollow3D::set_tilt_enabled(bool p_enabled) {
if (tilt_enabled == p_enabled) {
return;
}
tilt_enabled = p_enabled;
update_transform();
}
bool PathFollow3D::is_tilt_enabled() const {
return tilt_enabled;
}

141
scene/3d/path_3d.h Normal file
View File

@@ -0,0 +1,141 @@
/**************************************************************************/
/* path_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
#include "scene/resources/curve.h"
class Path3D : public Node3D {
GDCLASS(Path3D, Node3D);
private:
Ref<Curve3D> curve;
RID debug_instance;
Color debug_custom_color;
Ref<ArrayMesh> debug_mesh;
Ref<Material> debug_material;
Callable update_callback; // Used only by CSG currently.
void _update_debug_mesh();
void _update_debug_path_material();
void _curve_changed();
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_update_callback(Callable p_callback);
void set_curve(const Ref<Curve3D> &p_curve);
Ref<Curve3D> get_curve() const;
const Color &get_debug_custom_color() const;
void set_debug_custom_color(const Color &p_color);
bool get_debug_show() const;
void set_debug_show(bool p_show);
Ref<StandardMaterial3D> get_debug_material();
Path3D();
~Path3D();
};
class PathFollow3D : public Node3D {
GDCLASS(PathFollow3D, Node3D);
public:
enum RotationMode {
ROTATION_NONE,
ROTATION_Y,
ROTATION_XY,
ROTATION_XYZ,
ROTATION_ORIENTED
};
private:
Path3D *path = nullptr;
real_t progress = 0.0;
real_t h_offset = 0.0;
real_t v_offset = 0.0;
bool cubic = true;
bool loop = true;
bool tilt_enabled = true;
bool transform_dirty = true;
bool use_model_front = false;
RotationMode rotation_mode = ROTATION_XYZ;
protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
static void _bind_methods();
public:
void set_progress(real_t p_progress);
real_t get_progress() const;
void set_h_offset(real_t p_h_offset);
real_t get_h_offset() const;
void set_v_offset(real_t p_v_offset);
real_t get_v_offset() const;
void set_progress_ratio(real_t p_ratio);
real_t get_progress_ratio() const;
void set_loop(bool p_loop);
bool has_loop() const;
void set_tilt_enabled(bool p_enabled);
bool is_tilt_enabled() const;
void set_rotation_mode(RotationMode p_rotation_mode);
RotationMode get_rotation_mode() const;
void set_use_model_front(bool p_use_model_front);
bool is_using_model_front() const;
void set_cubic_interpolation_enabled(bool p_enabled);
bool is_cubic_interpolation_enabled() const;
PackedStringArray get_configuration_warnings() const override;
void update_transform();
static Transform3D correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode);
};
VARIANT_ENUM_CAST(PathFollow3D::RotationMode);

9
scene/3d/physics/SCsub Normal file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.scene_sources, "*.cpp")
# Chain load SCsubs
SConscript("joints/SCsub")

View File

@@ -0,0 +1,128 @@
/**************************************************************************/
/* animatable_body_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 "animatable_body_3d.h"
Vector3 AnimatableBody3D::get_linear_velocity() const {
return linear_velocity;
}
Vector3 AnimatableBody3D::get_angular_velocity() const {
return angular_velocity;
}
void AnimatableBody3D::set_sync_to_physics(bool p_enable) {
if (sync_to_physics == p_enable) {
return;
}
sync_to_physics = p_enable;
_update_kinematic_motion();
}
bool AnimatableBody3D::is_sync_to_physics_enabled() const {
return sync_to_physics;
}
void AnimatableBody3D::_update_kinematic_motion() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
#endif
if (sync_to_physics) {
set_only_update_transform_changes(true);
set_notify_local_transform(true);
} else {
set_only_update_transform_changes(false);
set_notify_local_transform(false);
}
}
void AnimatableBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
linear_velocity = p_state->get_linear_velocity();
angular_velocity = p_state->get_angular_velocity();
if (!sync_to_physics) {
return;
}
last_valid_transform = p_state->get_transform();
set_notify_local_transform(false);
set_global_transform(last_valid_transform);
set_notify_local_transform(true);
_on_transform_changed();
}
void AnimatableBody3D::_notification(int p_what) {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
#endif
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
last_valid_transform = get_global_transform();
_update_kinematic_motion();
} break;
case NOTIFICATION_EXIT_TREE: {
set_only_update_transform_changes(false);
set_notify_local_transform(false);
} break;
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
// Used by sync to physics, send the new transform to the physics...
Transform3D new_transform = get_global_transform();
PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform);
// ... but then revert changes.
set_notify_local_transform(false);
set_global_transform(last_valid_transform);
set_notify_local_transform(true);
_on_transform_changed();
} break;
}
}
void AnimatableBody3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &AnimatableBody3D::set_sync_to_physics);
ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &AnimatableBody3D::is_sync_to_physics_enabled);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled");
}
AnimatableBody3D::AnimatableBody3D() :
StaticBody3D(PhysicsServer3D::BODY_MODE_KINEMATIC) {
PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), callable_mp(this, &AnimatableBody3D::_body_state_changed));
}

View File

@@ -0,0 +1,64 @@
/**************************************************************************/
/* animatable_body_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/physics/static_body_3d.h"
class AnimatableBody3D : public StaticBody3D {
GDCLASS(AnimatableBody3D, StaticBody3D);
private:
Vector3 linear_velocity;
Vector3 angular_velocity;
bool sync_to_physics = true;
Transform3D last_valid_transform;
static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state);
void _body_state_changed(PhysicsDirectBodyState3D *p_state);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
virtual Vector3 get_linear_velocity() const override;
virtual Vector3 get_angular_velocity() const override;
AnimatableBody3D();
private:
void _update_kinematic_motion();
void set_sync_to_physics(bool p_enable);
bool is_sync_to_physics_enabled() const;
};

View File

@@ -0,0 +1,826 @@
/**************************************************************************/
/* area_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 "area_3d.h"
#include "servers/audio_server.h"
void Area3D::set_gravity_space_override_mode(SpaceOverride p_mode) {
gravity_space_override = p_mode;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_OVERRIDE_MODE, p_mode);
}
Area3D::SpaceOverride Area3D::get_gravity_space_override_mode() const {
return gravity_space_override;
}
void Area3D::set_gravity_is_point(bool p_enabled) {
gravity_is_point = p_enabled;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_IS_POINT, p_enabled);
}
bool Area3D::is_gravity_a_point() const {
return gravity_is_point;
}
void Area3D::set_gravity_point_unit_distance(real_t p_scale) {
gravity_point_unit_distance = p_scale;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_POINT_UNIT_DISTANCE, p_scale);
}
real_t Area3D::get_gravity_point_unit_distance() const {
return gravity_point_unit_distance;
}
void Area3D::set_gravity_point_center(const Vector3 &p_center) {
gravity_vec = p_center;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, p_center);
}
const Vector3 &Area3D::get_gravity_point_center() const {
return gravity_vec;
}
void Area3D::set_gravity_direction(const Vector3 &p_direction) {
gravity_vec = p_direction;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, p_direction);
}
const Vector3 &Area3D::get_gravity_direction() const {
return gravity_vec;
}
void Area3D::set_gravity(real_t p_gravity) {
gravity = p_gravity;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY, p_gravity);
}
real_t Area3D::get_gravity() const {
return gravity;
}
void Area3D::set_linear_damp_space_override_mode(SpaceOverride p_mode) {
linear_damp_space_override = p_mode;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE, p_mode);
}
Area3D::SpaceOverride Area3D::get_linear_damp_space_override_mode() const {
return linear_damp_space_override;
}
void Area3D::set_angular_damp_space_override_mode(SpaceOverride p_mode) {
angular_damp_space_override = p_mode;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE, p_mode);
}
Area3D::SpaceOverride Area3D::get_angular_damp_space_override_mode() const {
return angular_damp_space_override;
}
void Area3D::set_linear_damp(real_t p_linear_damp) {
linear_damp = p_linear_damp;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, p_linear_damp);
}
real_t Area3D::get_linear_damp() const {
return linear_damp;
}
void Area3D::set_angular_damp(real_t p_angular_damp) {
angular_damp = p_angular_damp;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP, p_angular_damp);
}
real_t Area3D::get_angular_damp() const {
return angular_damp;
}
void Area3D::set_priority(int p_priority) {
priority = p_priority;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_PRIORITY, p_priority);
}
int Area3D::get_priority() const {
return priority;
}
void Area3D::set_wind_force_magnitude(real_t p_wind_force_magnitude) {
wind_force_magnitude = p_wind_force_magnitude;
if (is_inside_tree()) {
_initialize_wind();
}
}
real_t Area3D::get_wind_force_magnitude() const {
return wind_force_magnitude;
}
void Area3D::set_wind_attenuation_factor(real_t p_wind_force_attenuation_factor) {
wind_attenuation_factor = p_wind_force_attenuation_factor;
if (is_inside_tree()) {
_initialize_wind();
}
}
real_t Area3D::get_wind_attenuation_factor() const {
return wind_attenuation_factor;
}
void Area3D::set_wind_source_path(const NodePath &p_wind_source_path) {
wind_source_path = p_wind_source_path;
if (is_inside_tree()) {
_initialize_wind();
}
}
const NodePath &Area3D::get_wind_source_path() const {
return wind_source_path;
}
void Area3D::_initialize_wind() {
real_t temp_magnitude = 0.0;
Vector3 wind_direction(0., 0., 0.);
Vector3 wind_source(0., 0., 0.);
// Overwrite with area-specified info if available
if (!wind_source_path.is_empty()) {
Node *wind_source_node = get_node_or_null(wind_source_path);
ERR_FAIL_NULL_MSG(wind_source_node, "Path to wind source is invalid: '" + String(wind_source_path) + "'.");
Node3D *wind_source_node3d = Object::cast_to<Node3D>(wind_source_node);
ERR_FAIL_NULL_MSG(wind_source_node3d, "Path to wind source does not point to a Node3D: '" + String(wind_source_path) + "'.");
Transform3D global_transform = wind_source_node3d->get_transform();
wind_direction = -global_transform.basis.get_column(Vector3::AXIS_Z).normalized();
wind_source = global_transform.origin;
temp_magnitude = wind_force_magnitude;
}
// Set force, source and direction in the physics server.
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_ATTENUATION_FACTOR, wind_attenuation_factor);
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_SOURCE, wind_source);
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_DIRECTION, wind_direction);
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_FORCE_MAGNITUDE, temp_magnitude);
}
void Area3D::_body_enter_tree(ObjectID p_id) {
Object *obj = ObjectDB::get_instance(p_id);
Node *node = Object::cast_to<Node>(obj);
ERR_FAIL_NULL(node);
HashMap<ObjectID, BodyState>::Iterator E = body_map.find(p_id);
ERR_FAIL_COND(!E);
ERR_FAIL_COND(E->value.in_tree);
E->value.in_tree = true;
emit_signal(SceneStringName(body_entered), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
emit_signal(SceneStringName(body_shape_entered), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape);
}
}
void Area3D::_body_exit_tree(ObjectID p_id) {
Object *obj = ObjectDB::get_instance(p_id);
Node *node = Object::cast_to<Node>(obj);
ERR_FAIL_NULL(node);
HashMap<ObjectID, BodyState>::Iterator E = body_map.find(p_id);
ERR_FAIL_COND(!E);
ERR_FAIL_COND(!E->value.in_tree);
E->value.in_tree = false;
emit_signal(SceneStringName(body_exited), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
emit_signal(SceneStringName(body_shape_exited), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape);
}
}
void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_area_shape) {
bool body_in = p_status == PhysicsServer3D::AREA_BODY_ADDED;
ObjectID objid = p_instance;
// Exit early if instance is invalid.
if (objid.is_null()) {
lock_callback();
locked = true;
// Emit the appropriate signals.
if (body_in) {
emit_signal(SceneStringName(body_shape_entered), p_body, (Node *)nullptr, p_body_shape, p_area_shape);
} else {
emit_signal(SceneStringName(body_shape_exited), p_body, (Node *)nullptr, p_body_shape, p_area_shape);
}
locked = false;
unlock_callback();
return;
}
Object *obj = ObjectDB::get_instance(objid);
Node *node = Object::cast_to<Node>(obj);
HashMap<ObjectID, BodyState>::Iterator E = body_map.find(objid);
if (!body_in && !E) {
return; //likely removed from the tree
}
lock_callback();
locked = true;
if (body_in) {
if (!E) {
E = body_map.insert(objid, BodyState());
E->value.rid = p_body;
E->value.rc = 0;
E->value.in_tree = node && node->is_inside_tree();
if (node) {
node->connect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_body_enter_tree).bind(objid));
node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_body_exit_tree).bind(objid));
if (E->value.in_tree) {
emit_signal(SceneStringName(body_entered), node);
}
}
}
E->value.rc++;
if (node) {
E->value.shapes.insert(ShapePair(p_body_shape, p_area_shape));
}
if (!node || E->value.in_tree) {
emit_signal(SceneStringName(body_shape_entered), p_body, node, p_body_shape, p_area_shape);
}
} else {
E->value.rc--;
if (node) {
E->value.shapes.erase(ShapePair(p_body_shape, p_area_shape));
}
bool in_tree = E->value.in_tree;
if (E->value.rc == 0) {
body_map.remove(E);
if (node) {
node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_body_enter_tree));
node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_body_exit_tree));
if (in_tree) {
emit_signal(SceneStringName(body_exited), obj);
}
}
}
if (!node || in_tree) {
emit_signal(SceneStringName(body_shape_exited), p_body, obj, p_body_shape, p_area_shape);
}
}
locked = false;
unlock_callback();
}
void Area3D::_clear_monitoring() {
ERR_FAIL_COND_MSG(locked, "This function can't be used during the in/out signal.");
{
HashMap<ObjectID, BodyState> bmcopy = body_map;
body_map.clear();
//disconnect all monitored stuff
for (const KeyValue<ObjectID, BodyState> &E : bmcopy) {
Object *obj = ObjectDB::get_instance(E.key);
Node *node = Object::cast_to<Node>(obj);
if (!node) { //node may have been deleted in previous frame or at other legitimate point
continue;
}
//ERR_CONTINUE(!node);
node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_body_enter_tree));
node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_body_exit_tree));
if (!E.value.in_tree) {
continue;
}
for (int i = 0; i < E.value.shapes.size(); i++) {
emit_signal(SceneStringName(body_shape_exited), E.value.rid, node, E.value.shapes[i].body_shape, E.value.shapes[i].area_shape);
}
emit_signal(SceneStringName(body_exited), node);
}
}
{
HashMap<ObjectID, AreaState> bmcopy = area_map;
area_map.clear();
//disconnect all monitored stuff
for (const KeyValue<ObjectID, AreaState> &E : bmcopy) {
Object *obj = ObjectDB::get_instance(E.key);
Node *node = Object::cast_to<Node>(obj);
if (!node) { //node may have been deleted in previous frame or at other legitimate point
continue;
}
//ERR_CONTINUE(!node);
node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_area_enter_tree));
node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_area_exit_tree));
if (!E.value.in_tree) {
continue;
}
for (int i = 0; i < E.value.shapes.size(); i++) {
emit_signal(SceneStringName(area_shape_exited), E.value.rid, node, E.value.shapes[i].area_shape, E.value.shapes[i].self_shape);
}
emit_signal(SceneStringName(area_exited), obj);
}
}
}
void Area3D::_space_changed(const RID &p_new_space) {
if (p_new_space.is_null()) {
_clear_monitoring();
}
}
void Area3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
_initialize_wind();
} break;
}
}
void Area3D::set_monitoring(bool p_enable) {
ERR_FAIL_COND_MSG(locked, "Function blocked during in/out signal. Use set_deferred(\"monitoring\", true/false).");
if (p_enable == monitoring) {
return;
}
monitoring = p_enable;
if (monitoring) {
PhysicsServer3D::get_singleton()->area_set_monitor_callback(get_rid(), callable_mp(this, &Area3D::_body_inout));
PhysicsServer3D::get_singleton()->area_set_area_monitor_callback(get_rid(), callable_mp(this, &Area3D::_area_inout));
} else {
PhysicsServer3D::get_singleton()->area_set_monitor_callback(get_rid(), Callable());
PhysicsServer3D::get_singleton()->area_set_area_monitor_callback(get_rid(), Callable());
_clear_monitoring();
}
}
void Area3D::_area_enter_tree(ObjectID p_id) {
Object *obj = ObjectDB::get_instance(p_id);
Node *node = Object::cast_to<Node>(obj);
ERR_FAIL_NULL(node);
HashMap<ObjectID, AreaState>::Iterator E = area_map.find(p_id);
ERR_FAIL_COND(!E);
ERR_FAIL_COND(E->value.in_tree);
E->value.in_tree = true;
emit_signal(SceneStringName(area_entered), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
emit_signal(SceneStringName(area_shape_entered), E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape);
}
}
void Area3D::_area_exit_tree(ObjectID p_id) {
Object *obj = ObjectDB::get_instance(p_id);
Node *node = Object::cast_to<Node>(obj);
ERR_FAIL_NULL(node);
HashMap<ObjectID, AreaState>::Iterator E = area_map.find(p_id);
ERR_FAIL_COND(!E);
ERR_FAIL_COND(!E->value.in_tree);
E->value.in_tree = false;
emit_signal(SceneStringName(area_exited), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
emit_signal(SceneStringName(area_shape_exited), E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape);
}
}
void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, int p_area_shape, int p_self_shape) {
bool area_in = p_status == PhysicsServer3D::AREA_BODY_ADDED;
ObjectID objid = p_instance;
// Exit if instance is invalid.
if (objid.is_null()) {
lock_callback();
locked = true;
// Emit the appropriate signals.
if (area_in) {
emit_signal(SceneStringName(area_shape_entered), p_area, (Node *)nullptr, p_area_shape, p_self_shape);
} else {
emit_signal(SceneStringName(area_shape_exited), p_area, (Node *)nullptr, p_area_shape, p_self_shape);
}
locked = false;
unlock_callback();
return;
}
Object *obj = ObjectDB::get_instance(objid);
Node *node = Object::cast_to<Node>(obj);
HashMap<ObjectID, AreaState>::Iterator E = area_map.find(objid);
if (!area_in && !E) {
return; //likely removed from the tree
}
lock_callback();
locked = true;
if (area_in) {
if (!E) {
E = area_map.insert(objid, AreaState());
E->value.rid = p_area;
E->value.rc = 0;
E->value.in_tree = node && node->is_inside_tree();
if (node) {
node->connect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_area_enter_tree).bind(objid));
node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_area_exit_tree).bind(objid));
if (E->value.in_tree) {
emit_signal(SceneStringName(area_entered), node);
}
}
}
E->value.rc++;
if (node) {
E->value.shapes.insert(AreaShapePair(p_area_shape, p_self_shape));
}
if (!node || E->value.in_tree) {
emit_signal(SceneStringName(area_shape_entered), p_area, node, p_area_shape, p_self_shape);
}
} else {
E->value.rc--;
if (node) {
E->value.shapes.erase(AreaShapePair(p_area_shape, p_self_shape));
}
bool in_tree = E->value.in_tree;
if (E->value.rc == 0) {
area_map.remove(E);
if (node) {
node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_area_enter_tree));
node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_area_exit_tree));
if (in_tree) {
emit_signal(SceneStringName(area_exited), obj);
}
}
}
if (!node || in_tree) {
emit_signal(SceneStringName(area_shape_exited), p_area, obj, p_area_shape, p_self_shape);
}
}
locked = false;
unlock_callback();
}
bool Area3D::is_monitoring() const {
return monitoring;
}
TypedArray<Node3D> Area3D::get_overlapping_bodies() const {
TypedArray<Node3D> ret;
ERR_FAIL_COND_V_MSG(!monitoring, ret, "Can't find overlapping bodies when monitoring is off.");
ret.resize(body_map.size());
int idx = 0;
for (const KeyValue<ObjectID, BodyState> &E : body_map) {
Object *obj = ObjectDB::get_instance(E.key);
if (obj) {
ret[idx] = obj;
idx++;
}
}
ret.resize(idx);
return ret;
}
bool Area3D::has_overlapping_bodies() const {
ERR_FAIL_COND_V_MSG(!monitoring, false, "Can't find overlapping bodies when monitoring is off.");
return !body_map.is_empty();
}
void Area3D::set_monitorable(bool p_enable) {
ERR_FAIL_COND_MSG(locked || (is_inside_tree() && PhysicsServer3D::get_singleton()->is_flushing_queries()), "Function blocked during in/out signal. Use set_deferred(\"monitorable\", true/false).");
if (p_enable == monitorable) {
return;
}
monitorable = p_enable;
PhysicsServer3D::get_singleton()->area_set_monitorable(get_rid(), monitorable);
}
bool Area3D::is_monitorable() const {
return monitorable;
}
TypedArray<Area3D> Area3D::get_overlapping_areas() const {
TypedArray<Area3D> ret;
ERR_FAIL_COND_V_MSG(!monitoring, ret, "Can't find overlapping areas when monitoring is off.");
ret.resize(area_map.size());
int idx = 0;
for (const KeyValue<ObjectID, AreaState> &E : area_map) {
Object *obj = ObjectDB::get_instance(E.key);
if (obj) {
ret[idx] = obj;
idx++;
}
}
ret.resize(idx);
return ret;
}
bool Area3D::has_overlapping_areas() const {
ERR_FAIL_COND_V_MSG(!monitoring, false, "Can't find overlapping areas when monitoring is off.");
return !area_map.is_empty();
}
bool Area3D::overlaps_area(Node *p_area) const {
ERR_FAIL_NULL_V(p_area, false);
HashMap<ObjectID, AreaState>::ConstIterator E = area_map.find(p_area->get_instance_id());
if (!E) {
return false;
}
return E->value.in_tree;
}
bool Area3D::overlaps_body(Node *p_body) const {
ERR_FAIL_NULL_V(p_body, false);
HashMap<ObjectID, BodyState>::ConstIterator E = body_map.find(p_body->get_instance_id());
if (!E) {
return false;
}
return E->value.in_tree;
}
void Area3D::set_audio_bus_override(bool p_override) {
audio_bus_override = p_override;
}
bool Area3D::is_overriding_audio_bus() const {
return audio_bus_override;
}
void Area3D::set_audio_bus_name(const StringName &p_audio_bus) {
audio_bus = p_audio_bus;
}
StringName Area3D::get_audio_bus_name() const {
for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) {
if (AudioServer::get_singleton()->get_bus_name(i) == audio_bus) {
return audio_bus;
}
}
return SceneStringName(Master);
}
void Area3D::set_use_reverb_bus(bool p_enable) {
use_reverb_bus = p_enable;
}
bool Area3D::is_using_reverb_bus() const {
return use_reverb_bus;
}
void Area3D::set_reverb_bus_name(const StringName &p_audio_bus) {
reverb_bus = p_audio_bus;
}
StringName Area3D::get_reverb_bus_name() const {
for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) {
if (AudioServer::get_singleton()->get_bus_name(i) == reverb_bus) {
return reverb_bus;
}
}
return SceneStringName(Master);
}
void Area3D::set_reverb_amount(float p_amount) {
reverb_amount = p_amount;
}
float Area3D::get_reverb_amount() const {
return reverb_amount;
}
void Area3D::set_reverb_uniformity(float p_uniformity) {
reverb_uniformity = p_uniformity;
}
float Area3D::get_reverb_uniformity() const {
return reverb_uniformity;
}
void Area3D::_validate_property(PropertyInfo &p_property) const {
if (!Engine::get_singleton()->is_editor_hint()) {
return;
}
if (p_property.name == "audio_bus_name" || p_property.name == "reverb_bus_name") {
String options;
for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) {
if (i > 0) {
options += ",";
}
String name = AudioServer::get_singleton()->get_bus_name(i);
options += name;
}
p_property.hint_string = options;
} else if (p_property.name.begins_with("gravity") && p_property.name != "gravity_space_override") {
if (gravity_space_override == SPACE_OVERRIDE_DISABLED) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
} else {
if (gravity_is_point) {
if (p_property.name == "gravity_direction") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
} else {
if (p_property.name.begins_with("gravity_point_")) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
}
} else if (p_property.name.begins_with("linear_damp") && p_property.name != "linear_damp_space_override") {
if (linear_damp_space_override == SPACE_OVERRIDE_DISABLED) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
} else if (p_property.name.begins_with("angular_damp") && p_property.name != "angular_damp_space_override") {
if (angular_damp_space_override == SPACE_OVERRIDE_DISABLED) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
}
void Area3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_gravity_space_override_mode", "space_override_mode"), &Area3D::set_gravity_space_override_mode);
ClassDB::bind_method(D_METHOD("get_gravity_space_override_mode"), &Area3D::get_gravity_space_override_mode);
ClassDB::bind_method(D_METHOD("set_gravity_is_point", "enable"), &Area3D::set_gravity_is_point);
ClassDB::bind_method(D_METHOD("is_gravity_a_point"), &Area3D::is_gravity_a_point);
ClassDB::bind_method(D_METHOD("set_gravity_point_unit_distance", "distance_scale"), &Area3D::set_gravity_point_unit_distance);
ClassDB::bind_method(D_METHOD("get_gravity_point_unit_distance"), &Area3D::get_gravity_point_unit_distance);
ClassDB::bind_method(D_METHOD("set_gravity_point_center", "center"), &Area3D::set_gravity_point_center);
ClassDB::bind_method(D_METHOD("get_gravity_point_center"), &Area3D::get_gravity_point_center);
ClassDB::bind_method(D_METHOD("set_gravity_direction", "direction"), &Area3D::set_gravity_direction);
ClassDB::bind_method(D_METHOD("get_gravity_direction"), &Area3D::get_gravity_direction);
ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &Area3D::set_gravity);
ClassDB::bind_method(D_METHOD("get_gravity"), &Area3D::get_gravity);
ClassDB::bind_method(D_METHOD("set_linear_damp_space_override_mode", "space_override_mode"), &Area3D::set_linear_damp_space_override_mode);
ClassDB::bind_method(D_METHOD("get_linear_damp_space_override_mode"), &Area3D::get_linear_damp_space_override_mode);
ClassDB::bind_method(D_METHOD("set_angular_damp_space_override_mode", "space_override_mode"), &Area3D::set_angular_damp_space_override_mode);
ClassDB::bind_method(D_METHOD("get_angular_damp_space_override_mode"), &Area3D::get_angular_damp_space_override_mode);
ClassDB::bind_method(D_METHOD("set_angular_damp", "angular_damp"), &Area3D::set_angular_damp);
ClassDB::bind_method(D_METHOD("get_angular_damp"), &Area3D::get_angular_damp);
ClassDB::bind_method(D_METHOD("set_linear_damp", "linear_damp"), &Area3D::set_linear_damp);
ClassDB::bind_method(D_METHOD("get_linear_damp"), &Area3D::get_linear_damp);
ClassDB::bind_method(D_METHOD("set_priority", "priority"), &Area3D::set_priority);
ClassDB::bind_method(D_METHOD("get_priority"), &Area3D::get_priority);
ClassDB::bind_method(D_METHOD("set_wind_force_magnitude", "wind_force_magnitude"), &Area3D::set_wind_force_magnitude);
ClassDB::bind_method(D_METHOD("get_wind_force_magnitude"), &Area3D::get_wind_force_magnitude);
ClassDB::bind_method(D_METHOD("set_wind_attenuation_factor", "wind_attenuation_factor"), &Area3D::set_wind_attenuation_factor);
ClassDB::bind_method(D_METHOD("get_wind_attenuation_factor"), &Area3D::get_wind_attenuation_factor);
ClassDB::bind_method(D_METHOD("set_wind_source_path", "wind_source_path"), &Area3D::set_wind_source_path);
ClassDB::bind_method(D_METHOD("get_wind_source_path"), &Area3D::get_wind_source_path);
ClassDB::bind_method(D_METHOD("set_monitorable", "enable"), &Area3D::set_monitorable);
ClassDB::bind_method(D_METHOD("is_monitorable"), &Area3D::is_monitorable);
ClassDB::bind_method(D_METHOD("set_monitoring", "enable"), &Area3D::set_monitoring);
ClassDB::bind_method(D_METHOD("is_monitoring"), &Area3D::is_monitoring);
ClassDB::bind_method(D_METHOD("get_overlapping_bodies"), &Area3D::get_overlapping_bodies);
ClassDB::bind_method(D_METHOD("get_overlapping_areas"), &Area3D::get_overlapping_areas);
ClassDB::bind_method(D_METHOD("has_overlapping_bodies"), &Area3D::has_overlapping_bodies);
ClassDB::bind_method(D_METHOD("has_overlapping_areas"), &Area3D::has_overlapping_areas);
ClassDB::bind_method(D_METHOD("overlaps_body", "body"), &Area3D::overlaps_body);
ClassDB::bind_method(D_METHOD("overlaps_area", "area"), &Area3D::overlaps_area);
ClassDB::bind_method(D_METHOD("set_audio_bus_override", "enable"), &Area3D::set_audio_bus_override);
ClassDB::bind_method(D_METHOD("is_overriding_audio_bus"), &Area3D::is_overriding_audio_bus);
ClassDB::bind_method(D_METHOD("set_audio_bus_name", "name"), &Area3D::set_audio_bus_name);
ClassDB::bind_method(D_METHOD("get_audio_bus_name"), &Area3D::get_audio_bus_name);
ClassDB::bind_method(D_METHOD("set_use_reverb_bus", "enable"), &Area3D::set_use_reverb_bus);
ClassDB::bind_method(D_METHOD("is_using_reverb_bus"), &Area3D::is_using_reverb_bus);
ClassDB::bind_method(D_METHOD("set_reverb_bus_name", "name"), &Area3D::set_reverb_bus_name);
ClassDB::bind_method(D_METHOD("get_reverb_bus_name"), &Area3D::get_reverb_bus_name);
ClassDB::bind_method(D_METHOD("set_reverb_amount", "amount"), &Area3D::set_reverb_amount);
ClassDB::bind_method(D_METHOD("get_reverb_amount"), &Area3D::get_reverb_amount);
ClassDB::bind_method(D_METHOD("set_reverb_uniformity", "amount"), &Area3D::set_reverb_uniformity);
ClassDB::bind_method(D_METHOD("get_reverb_uniformity"), &Area3D::get_reverb_uniformity);
ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
ADD_SIGNAL(MethodInfo("body_entered", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
ADD_SIGNAL(MethodInfo("area_shape_entered", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area3D"), PropertyInfo(Variant::INT, "area_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
ADD_SIGNAL(MethodInfo("area_shape_exited", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area3D"), PropertyInfo(Variant::INT, "area_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
ADD_SIGNAL(MethodInfo("area_entered", PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area3D")));
ADD_SIGNAL(MethodInfo("area_exited", PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area3D")));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitoring"), "set_monitoring", "is_monitoring");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitorable"), "set_monitorable", "is_monitorable");
ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,100000,1,or_greater,or_less"), "set_priority", "get_priority");
ADD_GROUP("Gravity", "gravity_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "gravity_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_gravity_space_override_mode", "get_gravity_space_override_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_gravity_is_point", "is_gravity_a_point");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_point_unit_distance", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,exp,suffix:m"), "set_gravity_point_unit_distance", "get_gravity_point_unit_distance");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity_point_center", PROPERTY_HINT_NONE, "suffix:m"), "set_gravity_point_center", "get_gravity_point_center");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity_direction"), "set_gravity_direction", "get_gravity_direction");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, U"-32,32,0.001,or_less,or_greater,suffix:m/s\u00B2"), "set_gravity", "get_gravity");
ADD_GROUP("Linear Damp", "linear_damp_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_linear_damp_space_override_mode", "get_linear_damp_space_override_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp");
ADD_GROUP("Angular Damp", "angular_damp_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_angular_damp_space_override_mode", "get_angular_damp_space_override_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp");
ADD_GROUP("Wind", "wind_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wind_force_magnitude", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater"), "set_wind_force_magnitude", "get_wind_force_magnitude");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wind_attenuation_factor", PROPERTY_HINT_RANGE, "0.0,3.0,0.001,or_greater"), "set_wind_attenuation_factor", "get_wind_attenuation_factor");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "wind_source_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_wind_source_path", "get_wind_source_path");
ADD_GROUP("Audio Bus", "audio_bus_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_bus_override"), "set_audio_bus_override", "is_overriding_audio_bus");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "audio_bus_name", PROPERTY_HINT_ENUM, ""), "set_audio_bus_name", "get_audio_bus_name");
ADD_GROUP("Reverb Bus", "reverb_bus_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reverb_bus_enabled"), "set_use_reverb_bus", "is_using_reverb_bus");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "reverb_bus_name", PROPERTY_HINT_ENUM, ""), "set_reverb_bus_name", "get_reverb_bus_name");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "reverb_bus_amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_reverb_amount", "get_reverb_amount");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "reverb_bus_uniformity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_reverb_uniformity", "get_reverb_uniformity");
BIND_ENUM_CONSTANT(SPACE_OVERRIDE_DISABLED);
BIND_ENUM_CONSTANT(SPACE_OVERRIDE_COMBINE);
BIND_ENUM_CONSTANT(SPACE_OVERRIDE_COMBINE_REPLACE);
BIND_ENUM_CONSTANT(SPACE_OVERRIDE_REPLACE);
BIND_ENUM_CONSTANT(SPACE_OVERRIDE_REPLACE_COMBINE);
}
Area3D::Area3D() :
CollisionObject3D(PhysicsServer3D::get_singleton()->area_create(), true) {
audio_bus = SceneStringName(Master);
reverb_bus = SceneStringName(Master);
set_gravity(9.8);
set_gravity_direction(Vector3(0, -1, 0));
set_monitoring(true);
set_monitorable(true);
}
Area3D::~Area3D() {
}

232
scene/3d/physics/area_3d.h Normal file
View File

@@ -0,0 +1,232 @@
/**************************************************************************/
/* area_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. */
/**************************************************************************/
#pragma once
#include "core/templates/vset.h"
#include "scene/3d/physics/collision_object_3d.h"
class Area3D : public CollisionObject3D {
GDCLASS(Area3D, CollisionObject3D);
public:
enum SpaceOverride {
SPACE_OVERRIDE_DISABLED,
SPACE_OVERRIDE_COMBINE,
SPACE_OVERRIDE_COMBINE_REPLACE,
SPACE_OVERRIDE_REPLACE,
SPACE_OVERRIDE_REPLACE_COMBINE
};
private:
SpaceOverride gravity_space_override = SPACE_OVERRIDE_DISABLED;
Vector3 gravity_vec;
real_t gravity = 0.0;
bool gravity_is_point = false;
real_t gravity_point_unit_distance = 0.0;
SpaceOverride linear_damp_space_override = SPACE_OVERRIDE_DISABLED;
SpaceOverride angular_damp_space_override = SPACE_OVERRIDE_DISABLED;
real_t angular_damp = 0.1;
real_t linear_damp = 0.1;
int priority = 0;
real_t wind_force_magnitude = 0.0;
real_t wind_attenuation_factor = 0.0;
NodePath wind_source_path;
bool monitoring = false;
bool monitorable = false;
bool locked = false;
void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_area_shape);
void _body_enter_tree(ObjectID p_id);
void _body_exit_tree(ObjectID p_id);
struct ShapePair {
int body_shape = 0;
int area_shape = 0;
bool operator<(const ShapePair &p_sp) const {
if (body_shape == p_sp.body_shape) {
return area_shape < p_sp.area_shape;
} else {
return body_shape < p_sp.body_shape;
}
}
ShapePair() {}
ShapePair(int p_bs, int p_as) {
body_shape = p_bs;
area_shape = p_as;
}
};
struct BodyState {
RID rid;
int rc = 0;
bool in_tree = false;
VSet<ShapePair> shapes;
};
HashMap<ObjectID, BodyState> body_map;
void _area_inout(int p_status, const RID &p_area, ObjectID p_instance, int p_area_shape, int p_self_shape);
void _area_enter_tree(ObjectID p_id);
void _area_exit_tree(ObjectID p_id);
struct AreaShapePair {
int area_shape = 0;
int self_shape = 0;
bool operator<(const AreaShapePair &p_sp) const {
if (area_shape == p_sp.area_shape) {
return self_shape < p_sp.self_shape;
} else {
return area_shape < p_sp.area_shape;
}
}
AreaShapePair() {}
AreaShapePair(int p_bs, int p_as) {
area_shape = p_bs;
self_shape = p_as;
}
};
struct AreaState {
RID rid;
int rc = 0;
bool in_tree = false;
VSet<AreaShapePair> shapes;
};
HashMap<ObjectID, AreaState> area_map;
void _clear_monitoring();
bool audio_bus_override = false;
StringName audio_bus;
bool use_reverb_bus = false;
StringName reverb_bus;
float reverb_amount = 0.0;
float reverb_uniformity = 0.0;
void _initialize_wind();
protected:
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
virtual void _space_changed(const RID &p_new_space) override;
public:
void set_gravity_space_override_mode(SpaceOverride p_mode);
SpaceOverride get_gravity_space_override_mode() const;
void set_gravity_is_point(bool p_enabled);
bool is_gravity_a_point() const;
void set_gravity_point_unit_distance(real_t p_scale);
real_t get_gravity_point_unit_distance() const;
void set_gravity_point_center(const Vector3 &p_center);
const Vector3 &get_gravity_point_center() const;
void set_gravity_direction(const Vector3 &p_direction);
const Vector3 &get_gravity_direction() const;
void set_gravity(real_t p_gravity);
real_t get_gravity() const;
void set_linear_damp_space_override_mode(SpaceOverride p_mode);
SpaceOverride get_linear_damp_space_override_mode() const;
void set_angular_damp_space_override_mode(SpaceOverride p_mode);
SpaceOverride get_angular_damp_space_override_mode() const;
void set_angular_damp(real_t p_angular_damp);
real_t get_angular_damp() const;
void set_linear_damp(real_t p_linear_damp);
real_t get_linear_damp() const;
void set_priority(int p_priority);
int get_priority() const;
void set_wind_force_magnitude(real_t p_wind_force_magnitude);
real_t get_wind_force_magnitude() const;
void set_wind_attenuation_factor(real_t p_wind_attenuation_factor);
real_t get_wind_attenuation_factor() const;
void set_wind_source_path(const NodePath &p_wind_source_path);
const NodePath &get_wind_source_path() const;
void set_monitoring(bool p_enable);
bool is_monitoring() const;
void set_monitorable(bool p_enable);
bool is_monitorable() const;
TypedArray<Node3D> get_overlapping_bodies() const;
TypedArray<Area3D> get_overlapping_areas() const; //function for script
bool has_overlapping_bodies() const;
bool has_overlapping_areas() const;
bool overlaps_area(Node *p_area) const;
bool overlaps_body(Node *p_body) const;
void set_audio_bus_override(bool p_override);
bool is_overriding_audio_bus() const;
void set_audio_bus_name(const StringName &p_audio_bus);
StringName get_audio_bus_name() const;
void set_use_reverb_bus(bool p_enable);
bool is_using_reverb_bus() const;
void set_reverb_bus_name(const StringName &p_audio_bus);
StringName get_reverb_bus_name() const;
void set_reverb_amount(float p_amount);
float get_reverb_amount() const;
void set_reverb_uniformity(float p_uniformity);
float get_reverb_uniformity() const;
Area3D();
~Area3D();
};
VARIANT_ENUM_CAST(Area3D::SpaceOverride);

View File

@@ -0,0 +1,965 @@
/**************************************************************************/
/* character_body_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 "character_body_3d.h"
#ifndef DISABLE_DEPRECATED
#include "servers/extensions/physics_server_3d_extension.h"
#endif
//so, if you pass 45 as limit, avoid numerical precision errors when angle is 45.
#define FLOOR_ANGLE_THRESHOLD 0.01
bool CharacterBody3D::move_and_slide() {
// Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky
double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time();
for (int i = 0; i < 3; i++) {
if (locked_axis & (1 << i)) {
velocity[i] = 0.0;
}
}
Transform3D gt = get_global_transform();
previous_position = gt.origin;
Vector3 current_platform_velocity = platform_velocity;
if ((collision_state.floor || collision_state.wall) && platform_rid.is_valid()) {
bool excluded = false;
if (collision_state.floor) {
excluded = (platform_floor_layers & platform_layer) == 0;
} else if (collision_state.wall) {
excluded = (platform_wall_layers & platform_layer) == 0;
}
if (!excluded) {
PhysicsDirectBodyState3D *bs = nullptr;
// 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.
// 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);
}
if (bs) {
Vector3 local_position = gt.origin - bs->get_transform().origin;
current_platform_velocity = bs->get_velocity_at_local_position(local_position);
} else {
// Body is removed or destroyed, invalidate floor.
current_platform_velocity = Vector3();
platform_rid = RID();
}
} else {
current_platform_velocity = Vector3();
}
}
motion_results.clear();
bool was_on_floor = collision_state.floor;
collision_state.state = 0;
last_motion = Vector3();
if (!current_platform_velocity.is_zero_approx()) {
PhysicsServer3D::MotionParameters parameters(get_global_transform(), current_platform_velocity * delta, margin);
parameters.recovery_as_collision = true; // Also report collisions generated only from recovery.
parameters.exclude_bodies.insert(platform_rid);
if (platform_object_id.is_valid()) {
parameters.exclude_objects.insert(platform_object_id);
}
PhysicsServer3D::MotionResult floor_result;
if (move_and_collide(parameters, floor_result, false, false)) {
motion_results.push_back(floor_result);
CollisionState result_state;
_set_collision_direction(floor_result, result_state);
}
}
if (motion_mode == MOTION_MODE_GROUNDED) {
_move_and_slide_grounded(delta, was_on_floor);
} else {
_move_and_slide_floating(delta);
}
// Compute real velocity.
real_velocity = get_position_delta() / delta;
if (platform_on_leave != PLATFORM_ON_LEAVE_DO_NOTHING) {
// Add last platform velocity when just left a moving platform.
if (!collision_state.floor && !collision_state.wall) {
if (platform_on_leave == PLATFORM_ON_LEAVE_ADD_UPWARD_VELOCITY && current_platform_velocity.dot(up_direction) < 0) {
current_platform_velocity = current_platform_velocity.slide(up_direction);
}
velocity += current_platform_velocity;
}
}
return motion_results.size() > 0;
}
void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor) {
Vector3 motion = velocity * p_delta;
Vector3 motion_slide_up = motion.slide(up_direction);
Vector3 prev_floor_normal = floor_normal;
platform_rid = RID();
platform_object_id = ObjectID();
platform_velocity = Vector3();
platform_angular_velocity = Vector3();
platform_ceiling_velocity = Vector3();
floor_normal = Vector3();
wall_normal = Vector3();
ceiling_normal = Vector3();
// No sliding on first attempt to keep floor motion stable when possible,
// When stop on slope is enabled or when there is no up direction.
bool sliding_enabled = !floor_stop_on_slope;
// Constant speed can be applied only the first time sliding is enabled.
bool can_apply_constant_speed = sliding_enabled;
// If the platform's ceiling push down the body.
bool apply_ceiling_velocity = false;
bool first_slide = true;
bool vel_dir_facing_up = velocity.dot(up_direction) > 0;
Vector3 total_travel;
for (int iteration = 0; iteration < max_slides; ++iteration) {
PhysicsServer3D::MotionParameters parameters(get_global_transform(), motion, margin);
parameters.max_collisions = 6; // There can be 4 collisions between 2 walls + 2 more for the floor.
parameters.recovery_as_collision = true; // Also report collisions generated only from recovery.
PhysicsServer3D::MotionResult result;
bool collided = move_and_collide(parameters, result, false, !sliding_enabled);
last_motion = result.travel;
if (collided) {
motion_results.push_back(result);
CollisionState previous_state = collision_state;
CollisionState result_state;
_set_collision_direction(result, result_state);
// If we hit a ceiling platform, we set the vertical velocity to at least the platform one.
if (collision_state.ceiling && platform_ceiling_velocity != Vector3() && platform_ceiling_velocity.dot(up_direction) < 0) {
// If ceiling sliding is on, only apply when the ceiling is flat or when the motion is upward.
if (!slide_on_ceiling || motion.dot(up_direction) < 0 || (ceiling_normal + up_direction).length() < 0.01) {
apply_ceiling_velocity = true;
Vector3 ceiling_vertical_velocity = up_direction * up_direction.dot(platform_ceiling_velocity);
Vector3 motion_vertical_velocity = up_direction * up_direction.dot(velocity);
if (motion_vertical_velocity.dot(up_direction) > 0 || ceiling_vertical_velocity.length_squared() > motion_vertical_velocity.length_squared()) {
velocity = ceiling_vertical_velocity + velocity.slide(up_direction);
}
}
}
if (collision_state.floor && floor_stop_on_slope && (velocity.normalized() + up_direction).length() < 0.01) {
Transform3D gt = get_global_transform();
if (result.travel.length() <= margin + CMP_EPSILON) {
gt.origin -= result.travel;
}
set_global_transform(gt);
velocity = Vector3();
motion = Vector3();
last_motion = Vector3();
break;
}
if (result.remainder.is_zero_approx()) {
motion = Vector3();
break;
}
// Apply regular sliding by default.
bool apply_default_sliding = true;
// Wall collision checks.
if (result_state.wall && (motion_slide_up.dot(wall_normal) <= 0)) {
// Move on floor only checks.
if (floor_block_on_wall) {
// Needs horizontal motion from current motion instead of motion_slide_up
// to properly test the angle and avoid standing on slopes
Vector3 horizontal_motion = motion.slide(up_direction);
Vector3 horizontal_normal = wall_normal.slide(up_direction).normalized();
real_t motion_angle = Math::abs(Math::acos(-horizontal_normal.dot(horizontal_motion.normalized())));
// Avoid to move forward on a wall if floor_block_on_wall is true.
// Applies only when the motion angle is under 90 degrees,
// in order to avoid blocking lateral motion along a wall.
if (motion_angle < .5 * Math::PI) {
apply_default_sliding = false;
if (p_was_on_floor && !vel_dir_facing_up) {
// Cancel the motion.
Transform3D gt = get_global_transform();
real_t travel_total = result.travel.length();
real_t cancel_dist_max = MIN(0.1, margin * 20);
if (travel_total <= margin + CMP_EPSILON) {
gt.origin -= result.travel;
result.travel = Vector3(); // Cancel for constant speed computation.
} else if (travel_total < cancel_dist_max) { // If the movement is large the body can be prevented from reaching the walls.
gt.origin -= result.travel.slide(up_direction);
// Keep remaining motion in sync with amount canceled.
motion = motion.slide(up_direction);
result.travel = Vector3();
} else {
// Travel is too high to be safely canceled, we take it into account.
result.travel = result.travel.slide(up_direction);
motion = result.remainder;
}
set_global_transform(gt);
// Determines if you are on the ground, and limits the possibility of climbing on the walls because of the approximations.
_snap_on_floor(true, false);
} else {
// If the movement is not canceled we only keep the remaining.
motion = result.remainder;
}
// Apply slide on forward in order to allow only lateral motion on next step.
Vector3 forward = wall_normal.slide(up_direction).normalized();
motion = motion.slide(forward);
// Scales the horizontal velocity according to the wall slope.
if (vel_dir_facing_up) {
Vector3 slide_motion = velocity.slide(result.collisions[0].normal);
// Keeps the vertical motion from velocity and add the horizontal motion of the projection.
velocity = up_direction * up_direction.dot(velocity) + slide_motion.slide(up_direction);
} else {
velocity = velocity.slide(forward);
}
// Allow only lateral motion along previous floor when already on floor.
// Fixes slowing down when moving in diagonal against an inclined wall.
if (p_was_on_floor && !vel_dir_facing_up && (motion.dot(up_direction) > 0.0)) {
// Slide along the corner between the wall and previous floor.
Vector3 floor_side = prev_floor_normal.cross(wall_normal);
if (floor_side != Vector3()) {
motion = floor_side * motion.dot(floor_side);
}
}
// Stop all motion when a second wall is hit (unless sliding down or jumping),
// in order to avoid jittering in corner cases.
bool stop_all_motion = previous_state.wall && !vel_dir_facing_up;
// Allow sliding when the body falls.
if (!collision_state.floor && motion.dot(up_direction) < 0) {
Vector3 slide_motion = motion.slide(wall_normal);
// Test again to allow sliding only if the result goes downwards.
// Fixes jittering issues at the bottom of inclined walls.
if (slide_motion.dot(up_direction) < 0) {
stop_all_motion = false;
motion = slide_motion;
}
}
if (stop_all_motion) {
motion = Vector3();
velocity = Vector3();
}
}
}
// Stop horizontal motion when under wall slide threshold.
if (p_was_on_floor && (wall_min_slide_angle > 0.0) && result_state.wall) {
Vector3 horizontal_normal = wall_normal.slide(up_direction).normalized();
real_t motion_angle = Math::abs(Math::acos(-horizontal_normal.dot(motion_slide_up.normalized())));
if (motion_angle < wall_min_slide_angle) {
motion = up_direction * motion.dot(up_direction);
velocity = up_direction * velocity.dot(up_direction);
apply_default_sliding = false;
}
}
}
if (apply_default_sliding) {
// Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling.
if ((sliding_enabled || !collision_state.floor) && (!collision_state.ceiling || slide_on_ceiling || !vel_dir_facing_up) && !apply_ceiling_velocity) {
const PhysicsServer3D::MotionCollision &collision = result.collisions[0];
Vector3 slide_motion = result.remainder.slide(collision.normal);
if (collision_state.floor && !collision_state.wall && !motion_slide_up.is_zero_approx()) {
// Slide using the intersection between the motion plane and the floor plane,
// in order to keep the direction intact.
real_t motion_length = slide_motion.length();
slide_motion = up_direction.cross(result.remainder).cross(floor_normal);
// Keep the length from default slide to change speed in slopes by default,
// when constant speed is not enabled.
slide_motion.normalize();
slide_motion *= motion_length;
}
if (slide_motion.dot(velocity) > 0.0) {
motion = slide_motion;
} else {
motion = Vector3();
}
if (slide_on_ceiling && result_state.ceiling) {
// Apply slide only in the direction of the input motion, otherwise just stop to avoid jittering when moving against a wall.
if (vel_dir_facing_up) {
velocity = velocity.slide(collision.normal);
} else {
// Avoid acceleration in slope when falling.
velocity = up_direction * up_direction.dot(velocity);
}
}
}
// No sliding on first attempt to keep floor motion stable when possible.
else {
motion = result.remainder;
if (result_state.ceiling && !slide_on_ceiling && vel_dir_facing_up) {
velocity = velocity.slide(up_direction);
motion = motion.slide(up_direction);
}
}
}
total_travel += result.travel;
// Apply Constant Speed.
if (p_was_on_floor && floor_constant_speed && can_apply_constant_speed && collision_state.floor && !motion.is_zero_approx()) {
Vector3 travel_slide_up = total_travel.slide(up_direction);
motion = motion.normalized() * MAX(0, (motion_slide_up.length() - travel_slide_up.length()));
}
}
// When you move forward in a downward slope you dont collide because you will be in the air.
// This test ensures that constant speed is applied, only if the player is still on the ground after the snap is applied.
else if (floor_constant_speed && first_slide && _on_floor_if_snapped(p_was_on_floor, vel_dir_facing_up)) {
can_apply_constant_speed = false;
sliding_enabled = true;
Transform3D gt = get_global_transform();
gt.origin = gt.origin - result.travel;
set_global_transform(gt);
// Slide using the intersection between the motion plane and the floor plane,
// in order to keep the direction intact.
Vector3 motion_slide_norm = up_direction.cross(motion).cross(prev_floor_normal);
motion_slide_norm.normalize();
motion = motion_slide_norm * (motion_slide_up.length());
collided = true;
}
if (!collided || motion.is_zero_approx()) {
break;
}
can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled;
sliding_enabled = true;
first_slide = false;
}
_snap_on_floor(p_was_on_floor, vel_dir_facing_up);
// Reset the gravity accumulation when touching the ground.
if (collision_state.floor && !vel_dir_facing_up) {
velocity = velocity.slide(up_direction);
}
}
void CharacterBody3D::_move_and_slide_floating(double p_delta) {
Vector3 motion = velocity * p_delta;
platform_rid = RID();
platform_object_id = ObjectID();
floor_normal = Vector3();
platform_velocity = Vector3();
platform_angular_velocity = Vector3();
bool first_slide = true;
for (int iteration = 0; iteration < max_slides; ++iteration) {
PhysicsServer3D::MotionParameters parameters(get_global_transform(), motion, margin);
parameters.recovery_as_collision = true; // Also report collisions generated only from recovery.
PhysicsServer3D::MotionResult result;
bool collided = move_and_collide(parameters, result, false, false);
last_motion = result.travel;
if (collided) {
motion_results.push_back(result);
CollisionState result_state;
_set_collision_direction(result, result_state);
if (result.remainder.is_zero_approx()) {
motion = Vector3();
break;
}
if (wall_min_slide_angle != 0 && Math::acos(wall_normal.dot(-velocity.normalized())) < wall_min_slide_angle + FLOOR_ANGLE_THRESHOLD) {
motion = Vector3();
if (result.travel.length() < margin + CMP_EPSILON) {
Transform3D gt = get_global_transform();
gt.origin -= result.travel;
set_global_transform(gt);
}
} else if (first_slide) {
Vector3 motion_slide_norm = result.remainder.slide(wall_normal).normalized();
motion = motion_slide_norm * (motion.length() - result.travel.length());
} else {
motion = result.remainder.slide(wall_normal);
}
if (motion.dot(velocity) <= 0.0) {
motion = Vector3();
}
}
if (!collided || motion.is_zero_approx()) {
break;
}
first_slide = false;
}
}
void CharacterBody3D::apply_floor_snap() {
if (collision_state.floor) {
return;
}
// Snap by at least collision margin to keep floor state consistent.
real_t length = MAX(floor_snap_length, margin);
PhysicsServer3D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin);
parameters.max_collisions = 4;
parameters.recovery_as_collision = true; // Also report collisions generated only from recovery.
parameters.collide_separation_ray = true;
PhysicsServer3D::MotionResult result;
if (move_and_collide(parameters, result, true, false)) {
CollisionState result_state;
// Apply direction for floor only.
_set_collision_direction(result, result_state, CollisionState(true, false, false));
if (result_state.floor) {
// 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;
set_global_transform(parameters.from);
}
}
}
void CharacterBody3D::_snap_on_floor(bool p_was_on_floor, bool p_vel_dir_facing_up) {
if (collision_state.floor || !p_was_on_floor || p_vel_dir_facing_up) {
return;
}
apply_floor_snap();
}
bool CharacterBody3D::_on_floor_if_snapped(bool p_was_on_floor, bool p_vel_dir_facing_up) {
if (up_direction == Vector3() || collision_state.floor || !p_was_on_floor || p_vel_dir_facing_up) {
return false;
}
// Snap by at least collision margin to keep floor state consistent.
real_t length = MAX(floor_snap_length, margin);
PhysicsServer3D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin);
parameters.max_collisions = 4;
parameters.recovery_as_collision = true; // Also report collisions generated only from recovery.
parameters.collide_separation_ray = true;
PhysicsServer3D::MotionResult result;
if (move_and_collide(parameters, result, true, false)) {
CollisionState result_state;
// Don't apply direction for any type.
_set_collision_direction(result, result_state, CollisionState());
return result_state.floor;
}
return false;
}
void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResult &p_result, CollisionState &r_state, CollisionState p_apply_state) {
r_state.state = 0;
real_t wall_depth = -1.0;
real_t floor_depth = -1.0;
bool was_on_wall = collision_state.wall;
Vector3 prev_wall_normal = wall_normal;
int wall_collision_count = 0;
Vector3 combined_wall_normal;
Vector3 tmp_wall_col; // Avoid duplicate on average calculation.
for (int i = p_result.collision_count - 1; i >= 0; i--) {
const PhysicsServer3D::MotionCollision &collision = p_result.collisions[i];
if (motion_mode == MOTION_MODE_GROUNDED) {
// Check if any collision is floor.
real_t floor_angle = collision.get_angle(up_direction);
if (floor_angle <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {
r_state.floor = true;
if (p_apply_state.floor && collision.depth > floor_depth) {
collision_state.floor = true;
floor_normal = collision.normal;
floor_depth = collision.depth;
_set_platform_data(collision);
}
continue;
}
// Check if any collision is ceiling.
real_t ceiling_angle = collision.get_angle(-up_direction);
if (ceiling_angle <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {
r_state.ceiling = true;
if (p_apply_state.ceiling) {
platform_ceiling_velocity = collision.collider_velocity;
ceiling_normal = collision.normal;
collision_state.ceiling = true;
}
continue;
}
}
// Collision is wall by default.
r_state.wall = true;
if (p_apply_state.wall && collision.depth > wall_depth) {
collision_state.wall = true;
wall_depth = collision.depth;
wall_normal = collision.normal;
// Don't apply wall velocity when the collider is a CharacterBody3D.
if (ObjectDB::get_instance<CharacterBody3D>(collision.collider_id) == nullptr) {
_set_platform_data(collision);
}
}
// Collect normal for calculating average.
if (!collision.normal.is_equal_approx(tmp_wall_col)) {
tmp_wall_col = collision.normal;
combined_wall_normal += collision.normal;
wall_collision_count++;
}
}
if (r_state.wall) {
if (wall_collision_count > 1 && !r_state.floor) {
// Check if wall normals cancel out to floor support.
if (!r_state.floor && motion_mode == MOTION_MODE_GROUNDED) {
combined_wall_normal.normalize();
real_t floor_angle = Math::acos(combined_wall_normal.dot(up_direction));
if (floor_angle <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {
r_state.floor = true;
r_state.wall = false;
if (p_apply_state.floor) {
collision_state.floor = true;
floor_normal = combined_wall_normal;
}
if (p_apply_state.wall) {
collision_state.wall = was_on_wall;
wall_normal = prev_wall_normal;
}
return;
}
}
}
}
}
void CharacterBody3D::_set_platform_data(const PhysicsServer3D::MotionCollision &p_collision) {
PhysicsDirectBodyState3D *bs = PhysicsServer3D::get_singleton()->body_get_direct_state(p_collision.collider);
if (bs == nullptr) {
return;
}
platform_rid = p_collision.collider;
platform_object_id = p_collision.collider_id;
platform_velocity = p_collision.collider_velocity;
platform_angular_velocity = p_collision.collider_angular_velocity;
#ifndef DISABLE_DEPRECATED
// Try to accommodate for any physics extensions that have yet to implement `PhysicsDirectBodyState3D::get_collision_layer`.
PhysicsDirectBodyState3DExtension *bs_ext = Object::cast_to<PhysicsDirectBodyState3DExtension>(bs);
if (bs_ext != nullptr && !GDVIRTUAL_IS_OVERRIDDEN_PTR(bs_ext, _get_collision_layer)) {
platform_layer = PhysicsServer3D::get_singleton()->body_get_collision_layer(p_collision.collider);
} else
#endif
{
platform_layer = bs->get_collision_layer();
}
}
void CharacterBody3D::set_safe_margin(real_t p_margin) {
margin = p_margin;
}
real_t CharacterBody3D::get_safe_margin() const {
return margin;
}
const Vector3 &CharacterBody3D::get_velocity() const {
return velocity;
}
void CharacterBody3D::set_velocity(const Vector3 &p_velocity) {
velocity = p_velocity;
}
bool CharacterBody3D::is_on_floor() const {
return collision_state.floor;
}
bool CharacterBody3D::is_on_floor_only() const {
return collision_state.floor && !collision_state.wall && !collision_state.ceiling;
}
bool CharacterBody3D::is_on_wall() const {
return collision_state.wall;
}
bool CharacterBody3D::is_on_wall_only() const {
return collision_state.wall && !collision_state.floor && !collision_state.ceiling;
}
bool CharacterBody3D::is_on_ceiling() const {
return collision_state.ceiling;
}
bool CharacterBody3D::is_on_ceiling_only() const {
return collision_state.ceiling && !collision_state.floor && !collision_state.wall;
}
const Vector3 &CharacterBody3D::get_floor_normal() const {
return floor_normal;
}
const Vector3 &CharacterBody3D::get_wall_normal() const {
return wall_normal;
}
const Vector3 &CharacterBody3D::get_last_motion() const {
return last_motion;
}
Vector3 CharacterBody3D::get_position_delta() const {
return get_global_transform().origin - previous_position;
}
const Vector3 &CharacterBody3D::get_real_velocity() const {
return real_velocity;
}
real_t CharacterBody3D::get_floor_angle(const Vector3 &p_up_direction) const {
ERR_FAIL_COND_V(p_up_direction == Vector3(), 0);
return Math::acos(floor_normal.dot(p_up_direction));
}
const Vector3 &CharacterBody3D::get_platform_velocity() const {
return platform_velocity;
}
const Vector3 &CharacterBody3D::get_platform_angular_velocity() const {
return platform_angular_velocity;
}
Vector3 CharacterBody3D::get_linear_velocity() const {
return get_real_velocity();
}
int CharacterBody3D::get_slide_collision_count() const {
return motion_results.size();
}
PhysicsServer3D::MotionResult CharacterBody3D::get_slide_collision(int p_bounce) const {
ERR_FAIL_INDEX_V(p_bounce, motion_results.size(), PhysicsServer3D::MotionResult());
return motion_results[p_bounce];
}
Ref<KinematicCollision3D> CharacterBody3D::_get_slide_collision(int p_bounce) {
ERR_FAIL_INDEX_V(p_bounce, motion_results.size(), Ref<KinematicCollision3D>());
if (p_bounce >= slide_colliders.size()) {
slide_colliders.resize(p_bounce + 1);
}
// Create a new instance when the cached reference is invalid or still in use in script.
if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->get_reference_count() > 1) {
slide_colliders.write[p_bounce].instantiate();
slide_colliders.write[p_bounce]->owner_id = get_instance_id();
}
slide_colliders.write[p_bounce]->result = motion_results[p_bounce];
return slide_colliders[p_bounce];
}
Ref<KinematicCollision3D> CharacterBody3D::_get_last_slide_collision() {
if (motion_results.is_empty()) {
return Ref<KinematicCollision3D>();
}
return _get_slide_collision(motion_results.size() - 1);
}
bool CharacterBody3D::is_floor_stop_on_slope_enabled() const {
return floor_stop_on_slope;
}
void CharacterBody3D::set_floor_stop_on_slope_enabled(bool p_enabled) {
floor_stop_on_slope = p_enabled;
}
bool CharacterBody3D::is_floor_constant_speed_enabled() const {
return floor_constant_speed;
}
void CharacterBody3D::set_floor_constant_speed_enabled(bool p_enabled) {
floor_constant_speed = p_enabled;
}
bool CharacterBody3D::is_floor_block_on_wall_enabled() const {
return floor_block_on_wall;
}
void CharacterBody3D::set_floor_block_on_wall_enabled(bool p_enabled) {
floor_block_on_wall = p_enabled;
}
bool CharacterBody3D::is_slide_on_ceiling_enabled() const {
return slide_on_ceiling;
}
void CharacterBody3D::set_slide_on_ceiling_enabled(bool p_enabled) {
slide_on_ceiling = p_enabled;
}
uint32_t CharacterBody3D::get_platform_floor_layers() const {
return platform_floor_layers;
}
void CharacterBody3D::set_platform_floor_layers(uint32_t p_exclude_layers) {
platform_floor_layers = p_exclude_layers;
}
uint32_t CharacterBody3D::get_platform_wall_layers() const {
return platform_wall_layers;
}
void CharacterBody3D::set_platform_wall_layers(uint32_t p_exclude_layers) {
platform_wall_layers = p_exclude_layers;
}
void CharacterBody3D::set_motion_mode(MotionMode p_mode) {
motion_mode = p_mode;
}
CharacterBody3D::MotionMode CharacterBody3D::get_motion_mode() const {
return motion_mode;
}
void CharacterBody3D::set_platform_on_leave(PlatformOnLeave p_on_leave_apply_velocity) {
platform_on_leave = p_on_leave_apply_velocity;
}
CharacterBody3D::PlatformOnLeave CharacterBody3D::get_platform_on_leave() const {
return platform_on_leave;
}
int CharacterBody3D::get_max_slides() const {
return max_slides;
}
void CharacterBody3D::set_max_slides(int p_max_slides) {
ERR_FAIL_COND(p_max_slides < 1);
max_slides = p_max_slides;
}
real_t CharacterBody3D::get_floor_max_angle() const {
return floor_max_angle;
}
void CharacterBody3D::set_floor_max_angle(real_t p_radians) {
floor_max_angle = p_radians;
}
real_t CharacterBody3D::get_floor_snap_length() {
return floor_snap_length;
}
void CharacterBody3D::set_floor_snap_length(real_t p_floor_snap_length) {
ERR_FAIL_COND(p_floor_snap_length < 0);
floor_snap_length = p_floor_snap_length;
}
real_t CharacterBody3D::get_wall_min_slide_angle() const {
return wall_min_slide_angle;
}
void CharacterBody3D::set_wall_min_slide_angle(real_t p_radians) {
wall_min_slide_angle = p_radians;
}
const Vector3 &CharacterBody3D::get_up_direction() const {
return up_direction;
}
void CharacterBody3D::set_up_direction(const Vector3 &p_up_direction) {
ERR_FAIL_COND_MSG(p_up_direction == Vector3(), "up_direction can't be equal to Vector3.ZERO, consider using Floating motion mode instead.");
up_direction = p_up_direction.normalized();
}
void CharacterBody3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
// Reset move_and_slide() data.
collision_state.state = 0;
platform_rid = RID();
platform_object_id = ObjectID();
motion_results.clear();
platform_velocity = Vector3();
platform_angular_velocity = Vector3();
} break;
}
}
void CharacterBody3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("move_and_slide"), &CharacterBody3D::move_and_slide);
ClassDB::bind_method(D_METHOD("apply_floor_snap"), &CharacterBody3D::apply_floor_snap);
ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &CharacterBody3D::set_velocity);
ClassDB::bind_method(D_METHOD("get_velocity"), &CharacterBody3D::get_velocity);
ClassDB::bind_method(D_METHOD("set_safe_margin", "margin"), &CharacterBody3D::set_safe_margin);
ClassDB::bind_method(D_METHOD("get_safe_margin"), &CharacterBody3D::get_safe_margin);
ClassDB::bind_method(D_METHOD("is_floor_stop_on_slope_enabled"), &CharacterBody3D::is_floor_stop_on_slope_enabled);
ClassDB::bind_method(D_METHOD("set_floor_stop_on_slope_enabled", "enabled"), &CharacterBody3D::set_floor_stop_on_slope_enabled);
ClassDB::bind_method(D_METHOD("set_floor_constant_speed_enabled", "enabled"), &CharacterBody3D::set_floor_constant_speed_enabled);
ClassDB::bind_method(D_METHOD("is_floor_constant_speed_enabled"), &CharacterBody3D::is_floor_constant_speed_enabled);
ClassDB::bind_method(D_METHOD("set_floor_block_on_wall_enabled", "enabled"), &CharacterBody3D::set_floor_block_on_wall_enabled);
ClassDB::bind_method(D_METHOD("is_floor_block_on_wall_enabled"), &CharacterBody3D::is_floor_block_on_wall_enabled);
ClassDB::bind_method(D_METHOD("set_slide_on_ceiling_enabled", "enabled"), &CharacterBody3D::set_slide_on_ceiling_enabled);
ClassDB::bind_method(D_METHOD("is_slide_on_ceiling_enabled"), &CharacterBody3D::is_slide_on_ceiling_enabled);
ClassDB::bind_method(D_METHOD("set_platform_floor_layers", "exclude_layer"), &CharacterBody3D::set_platform_floor_layers);
ClassDB::bind_method(D_METHOD("get_platform_floor_layers"), &CharacterBody3D::get_platform_floor_layers);
ClassDB::bind_method(D_METHOD("set_platform_wall_layers", "exclude_layer"), &CharacterBody3D::set_platform_wall_layers);
ClassDB::bind_method(D_METHOD("get_platform_wall_layers"), &CharacterBody3D::get_platform_wall_layers);
ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody3D::get_max_slides);
ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody3D::set_max_slides);
ClassDB::bind_method(D_METHOD("get_floor_max_angle"), &CharacterBody3D::get_floor_max_angle);
ClassDB::bind_method(D_METHOD("set_floor_max_angle", "radians"), &CharacterBody3D::set_floor_max_angle);
ClassDB::bind_method(D_METHOD("get_floor_snap_length"), &CharacterBody3D::get_floor_snap_length);
ClassDB::bind_method(D_METHOD("set_floor_snap_length", "floor_snap_length"), &CharacterBody3D::set_floor_snap_length);
ClassDB::bind_method(D_METHOD("get_wall_min_slide_angle"), &CharacterBody3D::get_wall_min_slide_angle);
ClassDB::bind_method(D_METHOD("set_wall_min_slide_angle", "radians"), &CharacterBody3D::set_wall_min_slide_angle);
ClassDB::bind_method(D_METHOD("get_up_direction"), &CharacterBody3D::get_up_direction);
ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody3D::set_up_direction);
ClassDB::bind_method(D_METHOD("set_motion_mode", "mode"), &CharacterBody3D::set_motion_mode);
ClassDB::bind_method(D_METHOD("get_motion_mode"), &CharacterBody3D::get_motion_mode);
ClassDB::bind_method(D_METHOD("set_platform_on_leave", "on_leave_apply_velocity"), &CharacterBody3D::set_platform_on_leave);
ClassDB::bind_method(D_METHOD("get_platform_on_leave"), &CharacterBody3D::get_platform_on_leave);
ClassDB::bind_method(D_METHOD("is_on_floor"), &CharacterBody3D::is_on_floor);
ClassDB::bind_method(D_METHOD("is_on_floor_only"), &CharacterBody3D::is_on_floor_only);
ClassDB::bind_method(D_METHOD("is_on_ceiling"), &CharacterBody3D::is_on_ceiling);
ClassDB::bind_method(D_METHOD("is_on_ceiling_only"), &CharacterBody3D::is_on_ceiling_only);
ClassDB::bind_method(D_METHOD("is_on_wall"), &CharacterBody3D::is_on_wall);
ClassDB::bind_method(D_METHOD("is_on_wall_only"), &CharacterBody3D::is_on_wall_only);
ClassDB::bind_method(D_METHOD("get_floor_normal"), &CharacterBody3D::get_floor_normal);
ClassDB::bind_method(D_METHOD("get_wall_normal"), &CharacterBody3D::get_wall_normal);
ClassDB::bind_method(D_METHOD("get_last_motion"), &CharacterBody3D::get_last_motion);
ClassDB::bind_method(D_METHOD("get_position_delta"), &CharacterBody3D::get_position_delta);
ClassDB::bind_method(D_METHOD("get_real_velocity"), &CharacterBody3D::get_real_velocity);
ClassDB::bind_method(D_METHOD("get_floor_angle", "up_direction"), &CharacterBody3D::get_floor_angle, DEFVAL(Vector3(0.0, 1.0, 0.0)));
ClassDB::bind_method(D_METHOD("get_platform_velocity"), &CharacterBody3D::get_platform_velocity);
ClassDB::bind_method(D_METHOD("get_platform_angular_velocity"), &CharacterBody3D::get_platform_angular_velocity);
ClassDB::bind_method(D_METHOD("get_slide_collision_count"), &CharacterBody3D::get_slide_collision_count);
ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody3D::_get_slide_collision);
ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody3D::_get_last_slide_collision);
ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Floating", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_direction"), "set_up_direction", "get_up_direction");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "suffix:m/s", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_slides", "get_max_slides");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wall_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians_as_degrees", PROPERTY_USAGE_DEFAULT), "set_wall_min_slide_angle", "get_wall_min_slide_angle");
ADD_GROUP("Floor", "floor_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_stop_on_slope"), "set_floor_stop_on_slope_enabled", "is_floor_stop_on_slope_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_constant_speed"), "set_floor_constant_speed_enabled", "is_floor_constant_speed_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_block_on_wall"), "set_floor_block_on_wall_enabled", "is_floor_block_on_wall_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians_as_degrees"), "set_floor_max_angle", "get_floor_max_angle");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater,suffix:m"), "set_floor_snap_length", "get_floor_snap_length");
ADD_GROUP("Moving Platform", "platform_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "platform_on_leave", PROPERTY_HINT_ENUM, "Add Velocity,Add Upward Velocity,Do Nothing", PROPERTY_USAGE_DEFAULT), "set_platform_on_leave", "get_platform_on_leave");
ADD_PROPERTY(PropertyInfo(Variant::INT, "platform_floor_layers", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_platform_floor_layers", "get_platform_floor_layers");
ADD_PROPERTY(PropertyInfo(Variant::INT, "platform_wall_layers", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_platform_wall_layers", "get_platform_wall_layers");
ADD_GROUP("Collision", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001,suffix:m"), "set_safe_margin", "get_safe_margin");
BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED);
BIND_ENUM_CONSTANT(MOTION_MODE_FLOATING);
BIND_ENUM_CONSTANT(PLATFORM_ON_LEAVE_ADD_VELOCITY);
BIND_ENUM_CONSTANT(PLATFORM_ON_LEAVE_ADD_UPWARD_VELOCITY);
BIND_ENUM_CONSTANT(PLATFORM_ON_LEAVE_DO_NOTHING);
}
void CharacterBody3D::_validate_property(PropertyInfo &p_property) const {
if (!Engine::get_singleton()->is_editor_hint()) {
return;
}
if (motion_mode == MOTION_MODE_FLOATING) {
if (p_property.name.begins_with("floor_") || p_property.name == "up_direction" || p_property.name == "slide_on_ceiling") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
}
CharacterBody3D::CharacterBody3D() :
PhysicsBody3D(PhysicsServer3D::BODY_MODE_KINEMATIC) {
}

View File

@@ -0,0 +1,186 @@
/**************************************************************************/
/* character_body_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/physics/kinematic_collision_3d.h"
#include "scene/3d/physics/physics_body_3d.h"
class CharacterBody3D : public PhysicsBody3D {
GDCLASS(CharacterBody3D, PhysicsBody3D);
public:
enum MotionMode {
MOTION_MODE_GROUNDED,
MOTION_MODE_FLOATING,
};
enum PlatformOnLeave {
PLATFORM_ON_LEAVE_ADD_VELOCITY,
PLATFORM_ON_LEAVE_ADD_UPWARD_VELOCITY,
PLATFORM_ON_LEAVE_DO_NOTHING,
};
bool move_and_slide();
void apply_floor_snap();
const Vector3 &get_velocity() const;
void set_velocity(const Vector3 &p_velocity);
bool is_on_floor() const;
bool is_on_floor_only() const;
bool is_on_wall() const;
bool is_on_wall_only() const;
bool is_on_ceiling() const;
bool is_on_ceiling_only() const;
const Vector3 &get_last_motion() const;
Vector3 get_position_delta() const;
const Vector3 &get_floor_normal() const;
const Vector3 &get_wall_normal() const;
const Vector3 &get_real_velocity() const;
real_t get_floor_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const;
const Vector3 &get_platform_velocity() const;
const Vector3 &get_platform_angular_velocity() const;
virtual Vector3 get_linear_velocity() const override;
int get_slide_collision_count() const;
PhysicsServer3D::MotionResult get_slide_collision(int p_bounce) const;
void set_safe_margin(real_t p_margin);
real_t get_safe_margin() const;
bool is_floor_stop_on_slope_enabled() const;
void set_floor_stop_on_slope_enabled(bool p_enabled);
bool is_floor_constant_speed_enabled() const;
void set_floor_constant_speed_enabled(bool p_enabled);
bool is_floor_block_on_wall_enabled() const;
void set_floor_block_on_wall_enabled(bool p_enabled);
bool is_slide_on_ceiling_enabled() const;
void set_slide_on_ceiling_enabled(bool p_enabled);
int get_max_slides() const;
void set_max_slides(int p_max_slides);
real_t get_floor_max_angle() const;
void set_floor_max_angle(real_t p_radians);
real_t get_floor_snap_length();
void set_floor_snap_length(real_t p_floor_snap_length);
real_t get_wall_min_slide_angle() const;
void set_wall_min_slide_angle(real_t p_radians);
uint32_t get_platform_floor_layers() const;
void set_platform_floor_layers(const uint32_t p_exclude_layer);
uint32_t get_platform_wall_layers() const;
void set_platform_wall_layers(const uint32_t p_exclude_layer);
void set_motion_mode(MotionMode p_mode);
MotionMode get_motion_mode() const;
void set_platform_on_leave(PlatformOnLeave p_on_leave_velocity);
PlatformOnLeave get_platform_on_leave() const;
CharacterBody3D();
private:
real_t margin = 0.001;
MotionMode motion_mode = MOTION_MODE_GROUNDED;
PlatformOnLeave platform_on_leave = PLATFORM_ON_LEAVE_ADD_VELOCITY;
union CollisionState {
uint32_t state = 0;
struct {
bool floor;
bool wall;
bool ceiling;
};
CollisionState() {
}
CollisionState(bool p_floor, bool p_wall, bool p_ceiling) {
floor = p_floor;
wall = p_wall;
ceiling = p_ceiling;
}
};
CollisionState collision_state;
bool floor_constant_speed = false;
bool floor_stop_on_slope = true;
bool floor_block_on_wall = true;
bool slide_on_ceiling = true;
int max_slides = 6;
int platform_layer = 0;
RID platform_rid;
ObjectID platform_object_id;
uint32_t platform_floor_layers = UINT32_MAX;
uint32_t platform_wall_layers = 0;
real_t floor_snap_length = 0.1;
real_t floor_max_angle = Math::deg_to_rad((real_t)45.0);
real_t wall_min_slide_angle = Math::deg_to_rad((real_t)15.0);
Vector3 up_direction = Vector3(0.0, 1.0, 0.0);
Vector3 velocity;
Vector3 floor_normal;
Vector3 wall_normal;
Vector3 ceiling_normal;
Vector3 last_motion;
Vector3 platform_velocity;
Vector3 platform_angular_velocity;
Vector3 platform_ceiling_velocity;
Vector3 previous_position;
Vector3 real_velocity;
Vector<PhysicsServer3D::MotionResult> motion_results;
Vector<Ref<KinematicCollision3D>> slide_colliders;
void _move_and_slide_floating(double p_delta);
void _move_and_slide_grounded(double p_delta, bool p_was_on_floor);
Ref<KinematicCollision3D> _get_slide_collision(int p_bounce);
Ref<KinematicCollision3D> _get_last_slide_collision();
const Vector3 &get_up_direction() const;
bool _on_floor_if_snapped(bool p_was_on_floor, bool p_vel_dir_facing_up);
void set_up_direction(const Vector3 &p_up_direction);
void _set_collision_direction(const PhysicsServer3D::MotionResult &p_result, CollisionState &r_state, CollisionState p_apply_state = CollisionState(true, true, true));
void _set_platform_data(const PhysicsServer3D::MotionCollision &p_collision);
void _snap_on_floor(bool p_was_on_floor, bool p_vel_dir_facing_up);
protected:
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
};
VARIANT_ENUM_CAST(CharacterBody3D::MotionMode);
VARIANT_ENUM_CAST(CharacterBody3D::PlatformOnLeave);

View File

@@ -0,0 +1,758 @@
/**************************************************************************/
/* collision_object_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 "collision_object_3d.h"
#include "scene/resources/3d/shape_3d.h"
void CollisionObject3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (_are_collision_shapes_visible()) {
debug_shape_old_transform = get_global_transform();
for (const KeyValue<uint32_t, ShapeData> &E : shapes) {
debug_shapes_to_update.insert(E.key);
}
_update_debug_shapes();
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
set_notify_local_transform(true); // Used for warnings and only in editor.
}
#endif
} break;
case NOTIFICATION_EXIT_TREE: {
if (debug_shapes_count > 0) {
_clear_debug_shapes();
}
} break;
case NOTIFICATION_ENTER_WORLD: {
if (area) {
PhysicsServer3D::get_singleton()->area_set_transform(rid, get_global_transform());
} else {
PhysicsServer3D::get_singleton()->body_set_state(rid, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform());
}
bool disabled = !is_enabled();
if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) {
_apply_disabled();
}
if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) {
Ref<World3D> world_ref = get_world_3d();
ERR_FAIL_COND(world_ref.is_null());
RID space = world_ref->get_space();
if (area) {
PhysicsServer3D::get_singleton()->area_set_space(rid, space);
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, space);
}
_space_changed(space);
}
_update_pickable();
} break;
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
update_configuration_warnings();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if (only_update_transform_changes) {
return;
}
if (area) {
PhysicsServer3D::get_singleton()->area_set_transform(rid, get_global_transform());
} else {
PhysicsServer3D::get_singleton()->body_set_state(rid, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform());
}
_on_transform_changed();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
_update_pickable();
} break;
case NOTIFICATION_EXIT_WORLD: {
bool disabled = !is_enabled();
if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) {
if (callback_lock > 0) {
ERR_PRINT("Removing a CollisionObject node during a physics callback is not allowed and will cause undesired behavior. Remove with call_deferred() instead.");
} else {
if (area) {
PhysicsServer3D::get_singleton()->area_set_space(rid, RID());
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, RID());
}
_space_changed(RID());
}
}
if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) {
_apply_enabled();
}
} break;
case NOTIFICATION_DISABLED: {
_apply_disabled();
} break;
case NOTIFICATION_ENABLED: {
_apply_enabled();
} break;
}
}
void CollisionObject3D::set_collision_layer(uint32_t p_layer) {
collision_layer = p_layer;
if (area) {
PhysicsServer3D::get_singleton()->area_set_collision_layer(get_rid(), p_layer);
} else {
PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), p_layer);
}
}
uint32_t CollisionObject3D::get_collision_layer() const {
return collision_layer;
}
void CollisionObject3D::set_collision_mask(uint32_t p_mask) {
collision_mask = p_mask;
if (area) {
PhysicsServer3D::get_singleton()->area_set_collision_mask(get_rid(), p_mask);
} else {
PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), p_mask);
}
}
uint32_t CollisionObject3D::get_collision_mask() const {
return collision_mask;
}
void CollisionObject3D::set_collision_layer_value(int p_layer_number, bool p_value) {
ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive.");
uint32_t collision_layer_new = get_collision_layer();
if (p_value) {
collision_layer_new |= 1 << (p_layer_number - 1);
} else {
collision_layer_new &= ~(1 << (p_layer_number - 1));
}
set_collision_layer(collision_layer_new);
}
bool CollisionObject3D::get_collision_layer_value(int p_layer_number) const {
ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive.");
return get_collision_layer() & (1 << (p_layer_number - 1));
}
void CollisionObject3D::set_collision_mask_value(int p_layer_number, bool p_value) {
ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive.");
uint32_t mask = get_collision_mask();
if (p_value) {
mask |= 1 << (p_layer_number - 1);
} else {
mask &= ~(1 << (p_layer_number - 1));
}
set_collision_mask(mask);
}
bool CollisionObject3D::get_collision_mask_value(int p_layer_number) const {
ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive.");
return get_collision_mask() & (1 << (p_layer_number - 1));
}
void CollisionObject3D::set_collision_priority(real_t p_priority) {
collision_priority = p_priority;
if (!area) {
PhysicsServer3D::get_singleton()->body_set_collision_priority(get_rid(), p_priority);
}
}
real_t CollisionObject3D::get_collision_priority() const {
return collision_priority;
}
void CollisionObject3D::set_disable_mode(DisableMode p_mode) {
if (disable_mode == p_mode) {
return;
}
bool disabled = is_inside_tree() && !is_enabled();
if (disabled) {
// Cancel previous disable mode.
_apply_enabled();
}
disable_mode = p_mode;
if (disabled) {
// Apply new disable mode.
_apply_disabled();
}
}
CollisionObject3D::DisableMode CollisionObject3D::get_disable_mode() const {
return disable_mode;
}
void CollisionObject3D::_apply_disabled() {
switch (disable_mode) {
case DISABLE_MODE_REMOVE: {
if (is_inside_tree()) {
if (callback_lock > 0) {
ERR_PRINT("Disabling a CollisionObject node during a physics callback is not allowed and will cause undesired behavior. Disable with call_deferred() instead.");
} else {
if (area) {
PhysicsServer3D::get_singleton()->area_set_space(rid, RID());
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, RID());
}
_space_changed(RID());
}
}
} break;
case DISABLE_MODE_MAKE_STATIC: {
if (!area && (body_mode != PhysicsServer3D::BODY_MODE_STATIC)) {
PhysicsServer3D::get_singleton()->body_set_mode(rid, PhysicsServer3D::BODY_MODE_STATIC);
}
} break;
case DISABLE_MODE_KEEP_ACTIVE: {
// Nothing to do.
} break;
}
}
void CollisionObject3D::_apply_enabled() {
switch (disable_mode) {
case DISABLE_MODE_REMOVE: {
if (is_inside_tree()) {
RID space = get_world_3d()->get_space();
if (area) {
PhysicsServer3D::get_singleton()->area_set_space(rid, space);
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, space);
}
_space_changed(space);
}
} break;
case DISABLE_MODE_MAKE_STATIC: {
if (!area && (body_mode != PhysicsServer3D::BODY_MODE_STATIC)) {
PhysicsServer3D::get_singleton()->body_set_mode(rid, body_mode);
}
} break;
case DISABLE_MODE_KEEP_ACTIVE: {
// Nothing to do.
} break;
}
}
void CollisionObject3D::_input_event_call(Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) {
GDVIRTUAL_CALL(_input_event, p_camera, p_input_event, p_pos, p_normal, p_shape);
emit_signal(SceneStringName(input_event), p_camera, p_input_event, p_pos, p_normal, p_shape);
}
void CollisionObject3D::_mouse_enter() {
GDVIRTUAL_CALL(_mouse_enter);
emit_signal(SceneStringName(mouse_entered));
}
void CollisionObject3D::_mouse_exit() {
GDVIRTUAL_CALL(_mouse_exit);
emit_signal(SceneStringName(mouse_exited));
}
void CollisionObject3D::set_body_mode(PhysicsServer3D::BodyMode p_mode) {
ERR_FAIL_COND(area);
if (body_mode == p_mode) {
return;
}
body_mode = p_mode;
if (is_inside_tree() && !is_enabled() && (disable_mode == DISABLE_MODE_MAKE_STATIC)) {
return;
}
PhysicsServer3D::get_singleton()->body_set_mode(rid, p_mode);
}
void CollisionObject3D::_space_changed(const RID &p_new_space) {
}
void CollisionObject3D::set_only_update_transform_changes(bool p_enable) {
only_update_transform_changes = p_enable;
}
bool CollisionObject3D::is_only_update_transform_changes_enabled() const {
return only_update_transform_changes;
}
void CollisionObject3D::_update_pickable() {
if (!is_inside_tree()) {
return;
}
bool pickable = ray_pickable && is_visible_in_tree();
if (area) {
PhysicsServer3D::get_singleton()->area_set_ray_pickable(rid, pickable);
} else {
PhysicsServer3D::get_singleton()->body_set_ray_pickable(rid, pickable);
}
}
bool CollisionObject3D::_are_collision_shapes_visible() {
return is_inside_tree() && get_tree()->is_debugging_collisions_hint() && !Engine::get_singleton()->is_editor_hint();
}
void CollisionObject3D::_update_shape_data(uint32_t p_owner) {
if (_are_collision_shapes_visible()) {
if (debug_shapes_to_update.is_empty()) {
callable_mp(this, &CollisionObject3D::_update_debug_shapes).call_deferred();
}
debug_shapes_to_update.insert(p_owner);
}
}
void CollisionObject3D::_shape_changed(const Ref<Shape3D> &p_shape) {
for (KeyValue<uint32_t, ShapeData> &E : shapes) {
ShapeData &shapedata = E.value;
ShapeData::ShapeBase *shape_bases = shapedata.shapes.ptrw();
for (int i = 0; i < shapedata.shapes.size(); i++) {
ShapeData::ShapeBase &s = shape_bases[i];
if (s.shape == p_shape && s.debug_shape.is_valid()) {
Ref<Mesh> mesh = s.shape->get_debug_mesh();
RS::get_singleton()->instance_set_base(s.debug_shape, mesh->get_rid());
}
}
}
}
void CollisionObject3D::_update_debug_shapes() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
if (!is_inside_tree()) {
debug_shapes_to_update.clear();
return;
}
for (const uint32_t &shapedata_idx : debug_shapes_to_update) {
if (shapes.has(shapedata_idx)) {
ShapeData &shapedata = shapes[shapedata_idx];
ShapeData::ShapeBase *shape_bases = shapedata.shapes.ptrw();
for (int i = 0; i < shapedata.shapes.size(); i++) {
ShapeData::ShapeBase &s = shape_bases[i];
if (s.shape.is_null() || shapedata.disabled) {
if (s.debug_shape.is_valid()) {
RS::get_singleton()->free(s.debug_shape);
s.debug_shape = RID();
--debug_shapes_count;
}
continue;
}
if (s.debug_shape.is_null()) {
s.debug_shape = RS::get_singleton()->instance_create();
RS::get_singleton()->instance_set_scenario(s.debug_shape, get_world_3d()->get_scenario());
s.shape->connect_changed(callable_mp(this, &CollisionObject3D::_shape_changed).bind(s.shape), CONNECT_DEFERRED);
++debug_shapes_count;
}
Ref<Mesh> mesh = s.shape->get_debug_mesh();
RS::get_singleton()->instance_set_base(s.debug_shape, mesh->get_rid());
RS::get_singleton()->instance_set_transform(s.debug_shape, get_global_transform() * shapedata.xform);
}
}
}
debug_shapes_to_update.clear();
}
void CollisionObject3D::_clear_debug_shapes() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
for (KeyValue<uint32_t, ShapeData> &E : shapes) {
ShapeData &shapedata = E.value;
ShapeData::ShapeBase *shape_bases = shapedata.shapes.ptrw();
for (int i = 0; i < shapedata.shapes.size(); i++) {
ShapeData::ShapeBase &s = shape_bases[i];
if (s.debug_shape.is_valid()) {
RS::get_singleton()->free(s.debug_shape);
s.debug_shape = RID();
if (s.shape.is_valid()) {
s.shape->disconnect_changed(callable_mp(this, &CollisionObject3D::_update_shape_data));
}
}
}
}
debug_shapes_count = 0;
}
void CollisionObject3D::_on_transform_changed() {
if (debug_shapes_count > 0 && !debug_shape_old_transform.is_equal_approx(get_global_transform())) {
debug_shape_old_transform = get_global_transform();
for (KeyValue<uint32_t, ShapeData> &E : shapes) {
ShapeData &shapedata = E.value;
if (shapedata.disabled) {
continue; // If disabled then there are no debug shapes to update.
}
const ShapeData::ShapeBase *shape_bases = shapedata.shapes.ptr();
for (int i = 0; i < shapedata.shapes.size(); i++) {
if (shape_bases[i].debug_shape.is_null()) {
continue;
}
RS::get_singleton()->instance_set_transform(shape_bases[i].debug_shape, debug_shape_old_transform * shapedata.xform);
}
}
}
}
void CollisionObject3D::set_ray_pickable(bool p_ray_pickable) {
ray_pickable = p_ray_pickable;
_update_pickable();
}
bool CollisionObject3D::is_ray_pickable() const {
return ray_pickable;
}
void CollisionObject3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collision_layer", "layer"), &CollisionObject3D::set_collision_layer);
ClassDB::bind_method(D_METHOD("get_collision_layer"), &CollisionObject3D::get_collision_layer);
ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &CollisionObject3D::set_collision_mask);
ClassDB::bind_method(D_METHOD("get_collision_mask"), &CollisionObject3D::get_collision_mask);
ClassDB::bind_method(D_METHOD("set_collision_layer_value", "layer_number", "value"), &CollisionObject3D::set_collision_layer_value);
ClassDB::bind_method(D_METHOD("get_collision_layer_value", "layer_number"), &CollisionObject3D::get_collision_layer_value);
ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &CollisionObject3D::set_collision_mask_value);
ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &CollisionObject3D::get_collision_mask_value);
ClassDB::bind_method(D_METHOD("set_collision_priority", "priority"), &CollisionObject3D::set_collision_priority);
ClassDB::bind_method(D_METHOD("get_collision_priority"), &CollisionObject3D::get_collision_priority);
ClassDB::bind_method(D_METHOD("set_disable_mode", "mode"), &CollisionObject3D::set_disable_mode);
ClassDB::bind_method(D_METHOD("get_disable_mode"), &CollisionObject3D::get_disable_mode);
ClassDB::bind_method(D_METHOD("set_ray_pickable", "ray_pickable"), &CollisionObject3D::set_ray_pickable);
ClassDB::bind_method(D_METHOD("is_ray_pickable"), &CollisionObject3D::is_ray_pickable);
ClassDB::bind_method(D_METHOD("set_capture_input_on_drag", "enable"), &CollisionObject3D::set_capture_input_on_drag);
ClassDB::bind_method(D_METHOD("get_capture_input_on_drag"), &CollisionObject3D::get_capture_input_on_drag);
ClassDB::bind_method(D_METHOD("get_rid"), &CollisionObject3D::get_rid);
ClassDB::bind_method(D_METHOD("create_shape_owner", "owner"), &CollisionObject3D::create_shape_owner);
ClassDB::bind_method(D_METHOD("remove_shape_owner", "owner_id"), &CollisionObject3D::remove_shape_owner);
ClassDB::bind_method(D_METHOD("get_shape_owners"), &CollisionObject3D::_get_shape_owners);
ClassDB::bind_method(D_METHOD("shape_owner_set_transform", "owner_id", "transform"), &CollisionObject3D::shape_owner_set_transform);
ClassDB::bind_method(D_METHOD("shape_owner_get_transform", "owner_id"), &CollisionObject3D::shape_owner_get_transform);
ClassDB::bind_method(D_METHOD("shape_owner_get_owner", "owner_id"), &CollisionObject3D::shape_owner_get_owner);
ClassDB::bind_method(D_METHOD("shape_owner_set_disabled", "owner_id", "disabled"), &CollisionObject3D::shape_owner_set_disabled);
ClassDB::bind_method(D_METHOD("is_shape_owner_disabled", "owner_id"), &CollisionObject3D::is_shape_owner_disabled);
ClassDB::bind_method(D_METHOD("shape_owner_add_shape", "owner_id", "shape"), &CollisionObject3D::shape_owner_add_shape);
ClassDB::bind_method(D_METHOD("shape_owner_get_shape_count", "owner_id"), &CollisionObject3D::shape_owner_get_shape_count);
ClassDB::bind_method(D_METHOD("shape_owner_get_shape", "owner_id", "shape_id"), &CollisionObject3D::shape_owner_get_shape);
ClassDB::bind_method(D_METHOD("shape_owner_get_shape_index", "owner_id", "shape_id"), &CollisionObject3D::shape_owner_get_shape_index);
ClassDB::bind_method(D_METHOD("shape_owner_remove_shape", "owner_id", "shape_id"), &CollisionObject3D::shape_owner_remove_shape);
ClassDB::bind_method(D_METHOD("shape_owner_clear_shapes", "owner_id"), &CollisionObject3D::shape_owner_clear_shapes);
ClassDB::bind_method(D_METHOD("shape_find_owner", "shape_index"), &CollisionObject3D::shape_find_owner);
GDVIRTUAL_BIND(_input_event, "camera", "event", "event_position", "normal", "shape_idx");
GDVIRTUAL_BIND(_mouse_enter);
GDVIRTUAL_BIND(_mouse_exit);
ADD_SIGNAL(MethodInfo("input_event", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::VECTOR3, "event_position"), PropertyInfo(Variant::VECTOR3, "normal"), PropertyInfo(Variant::INT, "shape_idx")));
ADD_SIGNAL(MethodInfo("mouse_entered"));
ADD_SIGNAL(MethodInfo("mouse_exited"));
ADD_PROPERTY(PropertyInfo(Variant::INT, "disable_mode", PROPERTY_HINT_ENUM, "Remove,Make Static,Keep Active"), "set_disable_mode", "get_disable_mode");
ADD_GROUP("Collision", "collision_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_layer", "get_collision_layer");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_priority"), "set_collision_priority", "get_collision_priority");
ADD_GROUP("Input", "input_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_ray_pickable"), "set_ray_pickable", "is_ray_pickable");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_capture_on_drag"), "set_capture_input_on_drag", "get_capture_input_on_drag");
BIND_ENUM_CONSTANT(DISABLE_MODE_REMOVE);
BIND_ENUM_CONSTANT(DISABLE_MODE_MAKE_STATIC);
BIND_ENUM_CONSTANT(DISABLE_MODE_KEEP_ACTIVE);
}
uint32_t CollisionObject3D::create_shape_owner(Object *p_owner) {
ShapeData sd;
uint32_t id;
if (shapes.is_empty()) {
id = 0;
} else {
id = shapes.back()->key() + 1;
}
sd.owner_id = p_owner ? p_owner->get_instance_id() : ObjectID();
shapes[id] = sd;
return id;
}
void CollisionObject3D::remove_shape_owner(uint32_t owner) {
ERR_FAIL_COND(!shapes.has(owner));
shape_owner_clear_shapes(owner);
shapes.erase(owner);
}
void CollisionObject3D::shape_owner_set_disabled(uint32_t p_owner, bool p_disabled) {
ERR_FAIL_COND(!shapes.has(p_owner));
ShapeData &sd = shapes[p_owner];
if (sd.disabled == p_disabled) {
return;
}
sd.disabled = p_disabled;
for (int i = 0; i < sd.shapes.size(); i++) {
if (area) {
PhysicsServer3D::get_singleton()->area_set_shape_disabled(rid, sd.shapes[i].index, p_disabled);
} else {
PhysicsServer3D::get_singleton()->body_set_shape_disabled(rid, sd.shapes[i].index, p_disabled);
}
}
_update_shape_data(p_owner);
}
bool CollisionObject3D::is_shape_owner_disabled(uint32_t p_owner) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), false);
return shapes[p_owner].disabled;
}
void CollisionObject3D::get_shape_owners(List<uint32_t> *r_owners) {
for (const KeyValue<uint32_t, ShapeData> &E : shapes) {
r_owners->push_back(E.key);
}
}
PackedInt32Array CollisionObject3D::_get_shape_owners() {
PackedInt32Array ret;
for (const KeyValue<uint32_t, ShapeData> &E : shapes) {
ret.push_back(E.key);
}
return ret;
}
void CollisionObject3D::shape_owner_set_transform(uint32_t p_owner, const Transform3D &p_transform) {
ERR_FAIL_COND(!shapes.has(p_owner));
ShapeData &sd = shapes[p_owner];
sd.xform = p_transform;
for (int i = 0; i < sd.shapes.size(); i++) {
if (area) {
PhysicsServer3D::get_singleton()->area_set_shape_transform(rid, sd.shapes[i].index, p_transform);
} else {
PhysicsServer3D::get_singleton()->body_set_shape_transform(rid, sd.shapes[i].index, p_transform);
}
}
_update_shape_data(p_owner);
}
Transform3D CollisionObject3D::shape_owner_get_transform(uint32_t p_owner) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), Transform3D());
return shapes[p_owner].xform;
}
Object *CollisionObject3D::shape_owner_get_owner(uint32_t p_owner) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), nullptr);
return ObjectDB::get_instance(shapes[p_owner].owner_id);
}
void CollisionObject3D::shape_owner_add_shape(uint32_t p_owner, const Ref<Shape3D> &p_shape) {
ERR_FAIL_COND(!shapes.has(p_owner));
ERR_FAIL_COND(p_shape.is_null());
ShapeData &sd = shapes[p_owner];
ShapeData::ShapeBase s;
s.index = total_subshapes;
s.shape = p_shape;
if (area) {
PhysicsServer3D::get_singleton()->area_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled);
} else {
PhysicsServer3D::get_singleton()->body_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled);
}
sd.shapes.push_back(s);
total_subshapes++;
_update_shape_data(p_owner);
update_gizmos();
}
int CollisionObject3D::shape_owner_get_shape_count(uint32_t p_owner) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), 0);
return shapes[p_owner].shapes.size();
}
Ref<Shape3D> CollisionObject3D::shape_owner_get_shape(uint32_t p_owner, int p_shape) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), Ref<Shape3D>());
ERR_FAIL_INDEX_V(p_shape, shapes[p_owner].shapes.size(), Ref<Shape3D>());
return shapes[p_owner].shapes[p_shape].shape;
}
int CollisionObject3D::shape_owner_get_shape_index(uint32_t p_owner, int p_shape) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), -1);
ERR_FAIL_INDEX_V(p_shape, shapes[p_owner].shapes.size(), -1);
return shapes[p_owner].shapes[p_shape].index;
}
void CollisionObject3D::shape_owner_remove_shape(uint32_t p_owner, int p_shape) {
ERR_FAIL_NULL(RenderingServer::get_singleton());
ERR_FAIL_COND(!shapes.has(p_owner));
ERR_FAIL_INDEX(p_shape, shapes[p_owner].shapes.size());
ShapeData::ShapeBase &s = shapes[p_owner].shapes.write[p_shape];
int index_to_remove = s.index;
if (area) {
PhysicsServer3D::get_singleton()->area_remove_shape(rid, index_to_remove);
} else {
PhysicsServer3D::get_singleton()->body_remove_shape(rid, index_to_remove);
}
if (s.debug_shape.is_valid()) {
RS::get_singleton()->free(s.debug_shape);
if (s.shape.is_valid()) {
s.shape->disconnect_changed(callable_mp(this, &CollisionObject3D::_shape_changed));
}
--debug_shapes_count;
}
shapes[p_owner].shapes.remove_at(p_shape);
for (KeyValue<uint32_t, ShapeData> &E : shapes) {
for (int i = 0; i < E.value.shapes.size(); i++) {
if (E.value.shapes[i].index > index_to_remove) {
E.value.shapes.write[i].index -= 1;
}
}
}
total_subshapes--;
}
void CollisionObject3D::shape_owner_clear_shapes(uint32_t p_owner) {
ERR_FAIL_COND(!shapes.has(p_owner));
while (shape_owner_get_shape_count(p_owner) > 0) {
shape_owner_remove_shape(p_owner, 0);
}
update_gizmos();
}
uint32_t CollisionObject3D::shape_find_owner(int p_shape_index) const {
ERR_FAIL_INDEX_V(p_shape_index, total_subshapes, UINT32_MAX);
for (const KeyValue<uint32_t, ShapeData> &E : shapes) {
for (int i = 0; i < E.value.shapes.size(); i++) {
if (E.value.shapes[i].index == p_shape_index) {
return E.key;
}
}
}
//in theory it should be unreachable
ERR_FAIL_V_MSG(UINT32_MAX, "Can't find owner for shape index " + itos(p_shape_index) + ".");
}
CollisionObject3D::CollisionObject3D(RID p_rid, bool p_area) {
rid = p_rid;
area = p_area;
set_notify_transform(true);
if (p_area) {
PhysicsServer3D::get_singleton()->area_attach_object_instance_id(rid, get_instance_id());
} else {
PhysicsServer3D::get_singleton()->body_attach_object_instance_id(rid, get_instance_id());
PhysicsServer3D::get_singleton()->body_set_mode(rid, body_mode);
}
}
void CollisionObject3D::set_capture_input_on_drag(bool p_capture) {
capture_input_on_drag = p_capture;
}
bool CollisionObject3D::get_capture_input_on_drag() const {
return capture_input_on_drag;
}
PackedStringArray CollisionObject3D::get_configuration_warnings() const {
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."));
}
Vector3 scale = get_transform().get_basis().get_scale();
if (!(Math::is_zero_approx(scale.x - scale.y) && Math::is_zero_approx(scale.y - scale.z))) {
warnings.push_back(RTR("With a non-uniform scale this node will probably not function as expected.\nPlease make its scale uniform (i.e. the same on all axes), and change the size in children collision shapes instead."));
}
return warnings;
}
CollisionObject3D::CollisionObject3D() {
set_notify_transform(true);
//owner=
//set_transform_notify(true);
}
CollisionObject3D::~CollisionObject3D() {
ERR_FAIL_NULL(PhysicsServer3D::get_singleton());
PhysicsServer3D::get_singleton()->free(rid);
}

View File

@@ -0,0 +1,181 @@
/**************************************************************************/
/* collision_object_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/camera_3d.h"
#include "scene/3d/node_3d.h"
class CollisionObject3D : public Node3D {
GDCLASS(CollisionObject3D, Node3D);
public:
enum DisableMode {
DISABLE_MODE_REMOVE,
DISABLE_MODE_MAKE_STATIC,
DISABLE_MODE_KEEP_ACTIVE,
};
private:
uint32_t collision_layer = 1;
uint32_t collision_mask = 1;
real_t collision_priority = 1.0;
bool area = false;
RID rid;
uint32_t callback_lock = 0;
DisableMode disable_mode = DISABLE_MODE_REMOVE;
PhysicsServer3D::BodyMode body_mode = PhysicsServer3D::BODY_MODE_STATIC;
struct ShapeData {
ObjectID owner_id;
Transform3D xform;
struct ShapeBase {
RID debug_shape;
Ref<Shape3D> shape;
int index = 0;
};
Vector<ShapeBase> shapes;
bool disabled = false;
};
int total_subshapes = 0;
RBMap<uint32_t, ShapeData> shapes;
bool only_update_transform_changes = false; // This is used for sync to physics.
bool capture_input_on_drag = false;
bool ray_pickable = true;
HashSet<uint32_t> debug_shapes_to_update;
int debug_shapes_count = 0;
Transform3D debug_shape_old_transform;
void _update_pickable();
bool _are_collision_shapes_visible();
void _update_shape_data(uint32_t p_owner);
void _shape_changed(const Ref<Shape3D> &p_shape);
void _update_debug_shapes();
void _clear_debug_shapes();
void _apply_disabled();
void _apply_enabled();
protected:
CollisionObject3D(RID p_rid, bool p_area);
_FORCE_INLINE_ void lock_callback() { callback_lock++; }
_FORCE_INLINE_ void unlock_callback() {
ERR_FAIL_COND(callback_lock == 0);
callback_lock--;
}
void _notification(int p_what);
static void _bind_methods();
void _on_transform_changed();
friend class Viewport;
virtual void _input_event_call(Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape);
virtual void _mouse_enter();
virtual void _mouse_exit();
void set_body_mode(PhysicsServer3D::BodyMode p_mode);
virtual void _space_changed(const RID &p_new_space);
void set_only_update_transform_changes(bool p_enable);
bool is_only_update_transform_changes_enabled() const;
GDVIRTUAL5(_input_event, Camera3D *, Ref<InputEvent>, Vector3, Vector3, int)
GDVIRTUAL0(_mouse_enter)
GDVIRTUAL0(_mouse_exit)
public:
void set_collision_layer(uint32_t p_layer);
uint32_t get_collision_layer() const;
void set_collision_mask(uint32_t p_mask);
uint32_t get_collision_mask() const;
void set_collision_layer_value(int p_layer_number, bool p_value);
bool get_collision_layer_value(int p_layer_number) const;
void set_collision_mask_value(int p_layer_number, bool p_value);
bool get_collision_mask_value(int p_layer_number) const;
void set_collision_priority(real_t p_priority);
real_t get_collision_priority() const;
void set_disable_mode(DisableMode p_mode);
DisableMode get_disable_mode() const;
uint32_t create_shape_owner(Object *p_owner);
void remove_shape_owner(uint32_t owner);
void get_shape_owners(List<uint32_t> *r_owners);
PackedInt32Array _get_shape_owners();
void shape_owner_set_transform(uint32_t p_owner, const Transform3D &p_transform);
Transform3D shape_owner_get_transform(uint32_t p_owner) const;
Object *shape_owner_get_owner(uint32_t p_owner) const;
void shape_owner_set_disabled(uint32_t p_owner, bool p_disabled);
bool is_shape_owner_disabled(uint32_t p_owner) const;
void shape_owner_add_shape(uint32_t p_owner, const Ref<Shape3D> &p_shape);
int shape_owner_get_shape_count(uint32_t p_owner) const;
Ref<Shape3D> shape_owner_get_shape(uint32_t p_owner, int p_shape) const;
int shape_owner_get_shape_index(uint32_t p_owner, int p_shape) const;
void shape_owner_remove_shape(uint32_t p_owner, int p_shape);
void shape_owner_clear_shapes(uint32_t p_owner);
uint32_t shape_find_owner(int p_shape_index) const;
void set_ray_pickable(bool p_ray_pickable);
bool is_ray_pickable() const;
void set_capture_input_on_drag(bool p_capture);
bool get_capture_input_on_drag() const;
_FORCE_INLINE_ RID get_rid() const { return rid; }
PackedStringArray get_configuration_warnings() const override;
CollisionObject3D();
~CollisionObject3D();
};
VARIANT_ENUM_CAST(CollisionObject3D::DisableMode);

View File

@@ -0,0 +1,294 @@
/**************************************************************************/
/* collision_polygon_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 "collision_polygon_3d.h"
#include "core/math/geometry_2d.h"
#include "scene/3d/physics/collision_object_3d.h"
#include "scene/resources/3d/convex_polygon_shape_3d.h"
void CollisionPolygon3D::_build_polygon() {
if (!collision_object) {
return;
}
collision_object->shape_owner_clear_shapes(owner_id);
if (polygon.is_empty()) {
return;
}
Vector<Vector<Vector2>> decomp = Geometry2D::decompose_polygon_in_convex(polygon);
if (decomp.is_empty()) {
return;
}
//here comes the sun, lalalala
//decompose concave into multiple convex polygons and add them
for (int i = 0; i < decomp.size(); i++) {
Ref<ConvexPolygonShape3D> convex = memnew(ConvexPolygonShape3D);
Vector<Vector3> cp;
int cs = decomp[i].size();
cp.resize(cs * 2);
{
Vector3 *w = cp.ptrw();
int idx = 0;
for (int j = 0; j < cs; j++) {
Vector2 d = decomp[i][j];
w[idx++] = Vector3(d.x, d.y, depth * 0.5);
w[idx++] = Vector3(d.x, d.y, -depth * 0.5);
}
}
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);
}
}
void CollisionPolygon3D::_update_in_shape_owner(bool p_xform_only) {
collision_object->shape_owner_set_transform(owner_id, get_transform());
if (p_xform_only) {
return;
}
collision_object->shape_owner_set_disabled(owner_id, disabled);
}
void CollisionPolygon3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PARENTED: {
collision_object = Object::cast_to<CollisionObject3D>(get_parent());
if (collision_object) {
owner_id = collision_object->create_shape_owner(this);
_build_polygon();
_update_in_shape_owner();
}
} break;
case NOTIFICATION_ENTER_TREE: {
if (collision_object) {
_update_in_shape_owner();
}
} break;
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
if (collision_object) {
_update_in_shape_owner(true);
}
update_configuration_warnings();
} break;
case NOTIFICATION_UNPARENTED: {
if (collision_object) {
collision_object->remove_shape_owner(owner_id);
}
owner_id = 0;
collision_object = nullptr;
} break;
}
}
void CollisionPolygon3D::set_polygon(const Vector<Point2> &p_polygon) {
polygon = p_polygon;
if (collision_object) {
_build_polygon();
}
update_configuration_warnings();
update_gizmos();
}
Vector<Point2> CollisionPolygon3D::get_polygon() const {
return polygon;
}
AABB CollisionPolygon3D::get_item_rect() const {
return aabb;
}
void CollisionPolygon3D::set_depth(real_t p_depth) {
depth = p_depth;
_build_polygon();
update_gizmos();
}
real_t CollisionPolygon3D::get_depth() const {
return depth;
}
void CollisionPolygon3D::set_disabled(bool p_disabled) {
disabled = p_disabled;
update_gizmos();
if (collision_object) {
collision_object->shape_owner_set_disabled(owner_id, p_disabled);
}
}
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;
}
void CollisionPolygon3D::set_margin(real_t p_margin) {
margin = p_margin;
if (collision_object) {
_build_polygon();
}
}
PackedStringArray CollisionPolygon3D::get_configuration_warnings() const {
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."));
}
if (polygon.is_empty()) {
warnings.push_back(RTR("An empty CollisionPolygon3D has no effect on collision."));
}
Vector3 scale = get_transform().get_basis().get_scale();
if (!(Math::is_zero_approx(scale.x - scale.y) && Math::is_zero_approx(scale.y - scale.z))) {
warnings.push_back(RTR("A non-uniformly scaled CollisionPolygon3D node will probably not function as expected.\nPlease make its scale uniform (i.e. the same on all axes), and change its polygon's vertices instead."));
}
return warnings;
}
bool CollisionPolygon3D::_is_editable_3d_polygon() const {
return true;
}
void CollisionPolygon3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_depth", "depth"), &CollisionPolygon3D::set_depth);
ClassDB::bind_method(D_METHOD("get_depth"), &CollisionPolygon3D::get_depth);
ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &CollisionPolygon3D::set_polygon);
ClassDB::bind_method(D_METHOD("get_polygon"), &CollisionPolygon3D::get_polygon);
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);
ClassDB::bind_method(D_METHOD("_is_editable_3d_polygon"), &CollisionPolygon3D::_is_editable_3d_polygon);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_NONE, "suffix:m"), "set_depth", "get_depth");
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();
}

View File

@@ -0,0 +1,95 @@
/**************************************************************************/
/* collision_polygon_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
class CollisionObject3D;
class CollisionPolygon3D : public Node3D {
GDCLASS(CollisionPolygon3D, Node3D);
real_t margin = 0.04;
protected:
real_t depth = 1.0;
AABB aabb = AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2));
Vector<Point2> polygon;
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();
void _update_in_shape_owner(bool p_xform_only = false);
bool _is_editable_3d_polygon() const;
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;
void set_polygon(const Vector<Point2> &p_polygon);
Vector<Point2> get_polygon() const;
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;
void set_margin(real_t p_margin);
PackedStringArray get_configuration_warnings() const override;
CollisionPolygon3D();
};

View File

@@ -0,0 +1,330 @@
/**************************************************************************/
/* collision_shape_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 "collision_shape_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/physics/character_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"
#include "scene/resources/3d/world_boundary_shape_3d.h"
void CollisionShape3D::make_convex_from_siblings() {
Node *p = get_parent();
if (!p) {
return;
}
Vector<Vector3> vertices;
for (int i = 0; i < p->get_child_count(); i++) {
Node *n = p->get_child(i);
MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(n);
if (mi) {
Ref<Mesh> m = mi->get_mesh();
if (m.is_valid()) {
for (int j = 0; j < m->get_surface_count(); j++) {
Array a = m->surface_get_arrays(j);
if (!a.is_empty()) {
Vector<Vector3> v = a[RenderingServer::ARRAY_VERTEX];
for (int k = 0; k < v.size(); k++) {
vertices.append(mi->get_transform().xform(v[k]));
}
}
}
}
}
}
Ref<ConvexPolygonShape3D> shape_new = memnew(ConvexPolygonShape3D);
shape_new->set_points(vertices);
set_shape(shape_new);
}
void CollisionShape3D::_update_in_shape_owner(bool p_xform_only) {
collision_object->shape_owner_set_transform(owner_id, get_transform());
if (p_xform_only) {
return;
}
collision_object->shape_owner_set_disabled(owner_id, disabled);
}
void CollisionShape3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PARENTED: {
collision_object = Object::cast_to<CollisionObject3D>(get_parent());
if (collision_object) {
owner_id = collision_object->create_shape_owner(this);
if (shape.is_valid()) {
collision_object->shape_owner_add_shape(owner_id, shape);
}
_update_in_shape_owner();
}
} break;
case NOTIFICATION_ENTER_TREE: {
if (collision_object) {
_update_in_shape_owner();
}
} break;
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
if (collision_object) {
_update_in_shape_owner(true);
}
update_configuration_warnings();
} break;
case NOTIFICATION_UNPARENTED: {
if (collision_object) {
collision_object->remove_shape_owner(owner_id);
}
owner_id = 0;
collision_object = nullptr;
} break;
}
}
#ifndef DISABLE_DEPRECATED
void CollisionShape3D::resource_changed(Ref<Resource> res) {
}
#endif
PackedStringArray CollisionShape3D::get_configuration_warnings() const {
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_null()) {
warnings.push_back(RTR("A shape must be provided for CollisionShape3D to function. Please create a shape resource for it."));
}
if (shape.is_valid() && Object::cast_to<RigidBody3D>(col_object)) {
String body_type = "RigidBody3D";
if (Object::cast_to<VehicleBody3D>(col_object)) {
body_type = "VehicleBody3D";
}
if (Object::cast_to<ConcavePolygonShape3D>(*shape)) {
warnings.push_back(vformat(RTR("When used for collision, ConcavePolygonShape3D is intended to work with static CollisionObject3D nodes like StaticBody3D.\nIt will likely not behave well for %ss (except when frozen and freeze_mode set to FREEZE_MODE_STATIC)."), body_type));
} else if (Object::cast_to<WorldBoundaryShape3D>(*shape)) {
warnings.push_back(RTR("WorldBoundaryShape3D doesn't support RigidBody3D in another mode than static."));
}
}
if (shape.is_valid() && Object::cast_to<CharacterBody3D>(col_object)) {
if (Object::cast_to<ConcavePolygonShape3D>(*shape)) {
warnings.push_back(RTR("When used for collision, ConcavePolygonShape3D is intended to work with static CollisionObject3D nodes like StaticBody3D.\nIt will likely not behave well for CharacterBody3Ds."));
}
}
Vector3 scale = get_transform().get_basis().get_scale();
if (!(Math::is_zero_approx(scale.x - scale.y) && Math::is_zero_approx(scale.y - scale.z))) {
warnings.push_back(RTR("A non-uniformly scaled CollisionShape3D node will probably not function as expected.\nPlease make its scale uniform (i.e. the same on all axes), and change the size of its shape resource instead."));
}
return warnings;
}
void CollisionShape3D::_bind_methods() {
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("resource_changed", "resource"), &CollisionShape3D::resource_changed);
#endif
ClassDB::bind_method(D_METHOD("set_shape", "shape"), &CollisionShape3D::set_shape);
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) {
if (p_shape == 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) {
collision_object->shape_owner_clear_shapes(owner_id);
if (shape.is_valid()) {
collision_object->shape_owner_add_shape(owner_id, shape);
}
}
if (is_inside_tree() && collision_object) {
// If this is a heightfield shape our center may have changed
_update_in_shape_owner(true);
}
update_configuration_warnings();
}
Ref<Shape3D> CollisionShape3D::get_shape() const {
return shape;
}
void CollisionShape3D::set_disabled(bool p_disabled) {
disabled = p_disabled;
update_gizmos();
if (collision_object) {
collision_object->shape_owner_set_disabled(owner_id, p_disabled);
}
}
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() {
//RenderingServer::get_singleton()->free(indicator);
}

View File

@@ -0,0 +1,91 @@
/**************************************************************************/
/* collision_shape_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
#include "scene/resources/3d/shape_3d.h"
class CollisionObject3D;
class CollisionShape3D : public Node3D {
GDCLASS(CollisionShape3D, Node3D);
Ref<Shape3D> shape;
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
bool disabled = false;
protected:
void _update_in_shape_owner(bool p_xform_only = false);
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();
void set_shape(const Ref<Shape3D> &p_shape);
Ref<Shape3D> get_shape() const;
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();
~CollisionShape3D();
};

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.scene_sources, "*.cpp")

View File

@@ -0,0 +1,95 @@
/**************************************************************************/
/* cone_twist_joint_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 "cone_twist_joint_3d.h"
void ConeTwistJoint3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &ConeTwistJoint3D::set_param);
ClassDB::bind_method(D_METHOD("get_param", "param"), &ConeTwistJoint3D::get_param);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "swing_span", PROPERTY_HINT_RANGE, "-180,180,0.1,radians_as_degrees"), "set_param", "get_param", PARAM_SWING_SPAN);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "twist_span", PROPERTY_HINT_RANGE, "-40000,40000,0.1,radians_as_degrees"), "set_param", "get_param", PARAM_TWIST_SPAN);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "relaxation", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_RELAXATION);
BIND_ENUM_CONSTANT(PARAM_SWING_SPAN);
BIND_ENUM_CONSTANT(PARAM_TWIST_SPAN);
BIND_ENUM_CONSTANT(PARAM_BIAS);
BIND_ENUM_CONSTANT(PARAM_SOFTNESS);
BIND_ENUM_CONSTANT(PARAM_RELAXATION);
BIND_ENUM_CONSTANT(PARAM_MAX);
}
void ConeTwistJoint3D::set_param(Param p_param, real_t p_value) {
ERR_FAIL_INDEX(p_param, PARAM_MAX);
params[p_param] = p_value;
if (is_configured()) {
PhysicsServer3D::get_singleton()->cone_twist_joint_set_param(get_rid(), PhysicsServer3D::ConeTwistJointParam(p_param), p_value);
}
update_gizmos();
}
real_t ConeTwistJoint3D::get_param(Param p_param) const {
ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0);
return params[p_param];
}
void ConeTwistJoint3D::_configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) {
Transform3D gt = get_global_transform();
Transform3D ainv = body_a->get_global_transform().affine_inverse();
Transform3D local_a = ainv * gt;
local_a.orthonormalize();
Transform3D local_b = gt;
if (body_b) {
Transform3D binv = body_b->get_global_transform().affine_inverse();
local_b = binv * gt;
}
local_b.orthonormalize();
PhysicsServer3D::get_singleton()->joint_make_cone_twist(p_joint, body_a->get_rid(), local_a, body_b ? body_b->get_rid() : RID(), local_b);
for (int i = 0; i < PARAM_MAX; i++) {
PhysicsServer3D::get_singleton()->cone_twist_joint_set_param(p_joint, PhysicsServer3D::ConeTwistJointParam(i), params[i]);
}
}
ConeTwistJoint3D::ConeTwistJoint3D() {
params[PARAM_SWING_SPAN] = Math::PI * 0.25;
params[PARAM_TWIST_SPAN] = Math::PI;
params[PARAM_BIAS] = 0.3;
params[PARAM_SOFTNESS] = 0.8;
params[PARAM_RELAXATION] = 1.0;
}

View File

@@ -0,0 +1,60 @@
/**************************************************************************/
/* cone_twist_joint_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/physics/joints/joint_3d.h"
class ConeTwistJoint3D : public Joint3D {
GDCLASS(ConeTwistJoint3D, Joint3D);
public:
enum Param {
PARAM_SWING_SPAN,
PARAM_TWIST_SPAN,
PARAM_BIAS,
PARAM_SOFTNESS,
PARAM_RELAXATION,
PARAM_MAX
};
protected:
real_t params[PARAM_MAX];
virtual void _configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) override;
static void _bind_methods();
public:
void set_param(Param p_param, real_t p_value);
real_t get_param(Param p_param) const;
ConeTwistJoint3D();
};
VARIANT_ENUM_CAST(ConeTwistJoint3D::Param);

View File

@@ -0,0 +1,405 @@
/**************************************************************************/
/* generic_6dof_joint_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 "generic_6dof_joint_3d.h"
void Generic6DOFJoint3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_param_x", "param", "value"), &Generic6DOFJoint3D::set_param_x);
ClassDB::bind_method(D_METHOD("get_param_x", "param"), &Generic6DOFJoint3D::get_param_x);
ClassDB::bind_method(D_METHOD("set_param_y", "param", "value"), &Generic6DOFJoint3D::set_param_y);
ClassDB::bind_method(D_METHOD("get_param_y", "param"), &Generic6DOFJoint3D::get_param_y);
ClassDB::bind_method(D_METHOD("set_param_z", "param", "value"), &Generic6DOFJoint3D::set_param_z);
ClassDB::bind_method(D_METHOD("get_param_z", "param"), &Generic6DOFJoint3D::get_param_z);
ClassDB::bind_method(D_METHOD("set_flag_x", "flag", "value"), &Generic6DOFJoint3D::set_flag_x);
ClassDB::bind_method(D_METHOD("get_flag_x", "flag"), &Generic6DOFJoint3D::get_flag_x);
ClassDB::bind_method(D_METHOD("set_flag_y", "flag", "value"), &Generic6DOFJoint3D::set_flag_y);
ClassDB::bind_method(D_METHOD("get_flag_y", "flag"), &Generic6DOFJoint3D::get_flag_y);
ClassDB::bind_method(D_METHOD("set_flag_z", "flag", "value"), &Generic6DOFJoint3D::set_flag_z);
ClassDB::bind_method(D_METHOD("get_flag_z", "flag"), &Generic6DOFJoint3D::get_flag_z);
ADD_GROUP("Linear Limit", "linear_limit_");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_limit_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_LINEAR_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/upper_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_x", "get_param_x", PARAM_LINEAR_UPPER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/lower_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_x", "get_param_x", PARAM_LINEAR_LOWER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_LINEAR_LIMIT_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_LINEAR_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_LINEAR_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_limit_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_LINEAR_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/upper_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_y", "get_param_y", PARAM_LINEAR_UPPER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/lower_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_y", "get_param_y", PARAM_LINEAR_LOWER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_LINEAR_LIMIT_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_LINEAR_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_LINEAR_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_limit_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_LINEAR_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/upper_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_z", "get_param_z", PARAM_LINEAR_UPPER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/lower_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_z", "get_param_z", PARAM_LINEAR_LOWER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_LINEAR_LIMIT_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_LINEAR_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_LINEAR_DAMPING);
ADD_GROUP("Linear Motor", "linear_motor_");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_motor_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_LINEAR_MOTOR);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_x/target_velocity", PROPERTY_HINT_NONE, "suffix:m/s"), "set_param_x", "get_param_x", PARAM_LINEAR_MOTOR_TARGET_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_x/force_limit", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m/s\u00B2 (N)"), "set_param_x", "get_param_x", PARAM_LINEAR_MOTOR_FORCE_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_motor_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_LINEAR_MOTOR);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_y/target_velocity", PROPERTY_HINT_NONE, "suffix:m/s"), "set_param_y", "get_param_y", PARAM_LINEAR_MOTOR_TARGET_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_y/force_limit", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m/s\u00B2 (N)"), "set_param_y", "get_param_y", PARAM_LINEAR_MOTOR_FORCE_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_motor_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_LINEAR_MOTOR);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_z/target_velocity", PROPERTY_HINT_NONE, "suffix:m/s"), "set_param_z", "get_param_z", PARAM_LINEAR_MOTOR_TARGET_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_z/force_limit", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m/s\u00B2 (N)"), "set_param_z", "get_param_z", PARAM_LINEAR_MOTOR_FORCE_LIMIT);
ADD_GROUP("Linear Spring", "linear_spring_");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_spring_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_LINEAR_SPRING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_x/stiffness"), "set_param_x", "get_param_x", PARAM_LINEAR_SPRING_STIFFNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_x/damping"), "set_param_x", "get_param_x", PARAM_LINEAR_SPRING_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_x/equilibrium_point", PROPERTY_HINT_NONE, "suffix:m"), "set_param_x", "get_param_x", PARAM_LINEAR_SPRING_EQUILIBRIUM_POINT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_spring_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_LINEAR_SPRING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_y/stiffness"), "set_param_y", "get_param_y", PARAM_LINEAR_SPRING_STIFFNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_y/damping"), "set_param_y", "get_param_y", PARAM_LINEAR_SPRING_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_y/equilibrium_point", PROPERTY_HINT_NONE, "suffix:m"), "set_param_y", "get_param_y", PARAM_LINEAR_SPRING_EQUILIBRIUM_POINT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_spring_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_LINEAR_SPRING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_z/stiffness"), "set_param_z", "get_param_z", PARAM_LINEAR_SPRING_STIFFNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_z/damping"), "set_param_z", "get_param_z", PARAM_LINEAR_SPRING_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_z/equilibrium_point", PROPERTY_HINT_NONE, "suffix:m"), "set_param_z", "get_param_z", PARAM_LINEAR_SPRING_EQUILIBRIUM_POINT);
ADD_GROUP("Angular Limit", "angular_limit_");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_ANGULAR_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_x/upper_angle", PROPERTY_HINT_RANGE, "-180,180,0.01,radians_as_degrees"), "set_param_x", "get_param_x", PARAM_ANGULAR_UPPER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_x/lower_angle", PROPERTY_HINT_RANGE, "-180,180,0.01,radians_as_degrees"), "set_param_x", "get_param_x", PARAM_ANGULAR_LOWER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_x/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_ANGULAR_LIMIT_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_x/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_ANGULAR_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_x/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_ANGULAR_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_x/force_limit", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m\u00B2/s\u00B2 (Nm)"), "set_param_x", "get_param_x", PARAM_ANGULAR_FORCE_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_x/erp"), "set_param_x", "get_param_x", PARAM_ANGULAR_ERP);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_ANGULAR_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/upper_angle", PROPERTY_HINT_RANGE, "-180,180,0.01,radians_as_degrees"), "set_param_y", "get_param_y", PARAM_ANGULAR_UPPER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/lower_angle", PROPERTY_HINT_RANGE, "-180,180,0.01,radians_as_degrees"), "set_param_y", "get_param_y", PARAM_ANGULAR_LOWER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_ANGULAR_LIMIT_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_ANGULAR_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_ANGULAR_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/force_limit", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m\u00B2/s\u00B2 (Nm)"), "set_param_y", "get_param_y", PARAM_ANGULAR_FORCE_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/erp"), "set_param_y", "get_param_y", PARAM_ANGULAR_ERP);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_ANGULAR_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_z/upper_angle", PROPERTY_HINT_RANGE, "-180,180,0.01,radians_as_degrees"), "set_param_z", "get_param_z", PARAM_ANGULAR_UPPER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_z/lower_angle", PROPERTY_HINT_RANGE, "-180,180,0.01,radians_as_degrees"), "set_param_z", "get_param_z", PARAM_ANGULAR_LOWER_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_z/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_ANGULAR_LIMIT_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_z/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_ANGULAR_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_z/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_ANGULAR_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_z/force_limit", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m\u00B2/s\u00B2 (Nm)"), "set_param_z", "get_param_z", PARAM_ANGULAR_FORCE_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_z/erp"), "set_param_z", "get_param_z", PARAM_ANGULAR_ERP);
ADD_GROUP("Angular Motor", "angular_motor_");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_motor_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_MOTOR);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_x/target_velocity", PROPERTY_HINT_NONE, U"radians_as_degrees,suffix:\u00B0/s"), "set_param_x", "get_param_x", PARAM_ANGULAR_MOTOR_TARGET_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_x/force_limit", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m\u00B2/s\u00B2 (Nm)"), "set_param_x", "get_param_x", PARAM_ANGULAR_MOTOR_FORCE_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_motor_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_MOTOR);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_y/target_velocity", PROPERTY_HINT_NONE, U"radians_as_degrees,suffix:\u00B0/s"), "set_param_y", "get_param_y", PARAM_ANGULAR_MOTOR_TARGET_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_y/force_limit", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m\u00B2/s\u00B2 (Nm)"), "set_param_y", "get_param_y", PARAM_ANGULAR_MOTOR_FORCE_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_motor_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_MOTOR);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_z/target_velocity", PROPERTY_HINT_NONE, U"radians_as_degrees,suffix:\u00B0/s"), "set_param_z", "get_param_z", PARAM_ANGULAR_MOTOR_TARGET_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_z/force_limit", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m\u00B2/s\u00B2 (Nm)"), "set_param_z", "get_param_z", PARAM_ANGULAR_MOTOR_FORCE_LIMIT);
ADD_GROUP("Angular Spring", "angular_spring_");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_spring_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_ANGULAR_SPRING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_x/stiffness"), "set_param_x", "get_param_x", PARAM_ANGULAR_SPRING_STIFFNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_x/damping"), "set_param_x", "get_param_x", PARAM_ANGULAR_SPRING_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_x/equilibrium_point", PROPERTY_HINT_RANGE, "-180,180,0.01,radians_as_degrees"), "set_param_x", "get_param_x", PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_spring_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_ANGULAR_SPRING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_y/stiffness"), "set_param_y", "get_param_y", PARAM_ANGULAR_SPRING_STIFFNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_y/damping"), "set_param_y", "get_param_y", PARAM_ANGULAR_SPRING_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_y/equilibrium_point", PROPERTY_HINT_RANGE, "-180,180,0.01,radians_as_degrees"), "set_param_y", "get_param_y", PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_spring_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_ANGULAR_SPRING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_z/stiffness"), "set_param_z", "get_param_z", PARAM_ANGULAR_SPRING_STIFFNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_z/damping"), "set_param_z", "get_param_z", PARAM_ANGULAR_SPRING_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_z/equilibrium_point", PROPERTY_HINT_RANGE, "-180,180,0.01,radians_as_degrees"), "set_param_z", "get_param_z", PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT);
BIND_ENUM_CONSTANT(PARAM_LINEAR_LOWER_LIMIT);
BIND_ENUM_CONSTANT(PARAM_LINEAR_UPPER_LIMIT);
BIND_ENUM_CONSTANT(PARAM_LINEAR_LIMIT_SOFTNESS);
BIND_ENUM_CONSTANT(PARAM_LINEAR_RESTITUTION);
BIND_ENUM_CONSTANT(PARAM_LINEAR_DAMPING);
BIND_ENUM_CONSTANT(PARAM_LINEAR_MOTOR_TARGET_VELOCITY);
BIND_ENUM_CONSTANT(PARAM_LINEAR_MOTOR_FORCE_LIMIT);
BIND_ENUM_CONSTANT(PARAM_LINEAR_SPRING_STIFFNESS);
BIND_ENUM_CONSTANT(PARAM_LINEAR_SPRING_DAMPING);
BIND_ENUM_CONSTANT(PARAM_LINEAR_SPRING_EQUILIBRIUM_POINT);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_LOWER_LIMIT);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_UPPER_LIMIT);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_LIMIT_SOFTNESS);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_DAMPING);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_RESTITUTION);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_FORCE_LIMIT);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_ERP);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_MOTOR_TARGET_VELOCITY);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_MOTOR_FORCE_LIMIT);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_SPRING_STIFFNESS);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_SPRING_DAMPING);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT);
BIND_ENUM_CONSTANT(PARAM_MAX);
BIND_ENUM_CONSTANT(FLAG_ENABLE_LINEAR_LIMIT);
BIND_ENUM_CONSTANT(FLAG_ENABLE_ANGULAR_LIMIT);
BIND_ENUM_CONSTANT(FLAG_ENABLE_LINEAR_SPRING);
BIND_ENUM_CONSTANT(FLAG_ENABLE_ANGULAR_SPRING);
BIND_ENUM_CONSTANT(FLAG_ENABLE_MOTOR);
BIND_ENUM_CONSTANT(FLAG_ENABLE_LINEAR_MOTOR);
BIND_ENUM_CONSTANT(FLAG_MAX);
}
void Generic6DOFJoint3D::set_param_x(Param p_param, real_t p_value) {
ERR_FAIL_INDEX(p_param, PARAM_MAX);
params_x[p_param] = p_value;
if (is_configured()) {
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_rid(), Vector3::AXIS_X, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value);
}
update_gizmos();
}
real_t Generic6DOFJoint3D::get_param_x(Param p_param) const {
ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0);
return params_x[p_param];
}
void Generic6DOFJoint3D::set_param_y(Param p_param, real_t p_value) {
ERR_FAIL_INDEX(p_param, PARAM_MAX);
params_y[p_param] = p_value;
if (is_configured()) {
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_rid(), Vector3::AXIS_Y, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value);
}
update_gizmos();
}
real_t Generic6DOFJoint3D::get_param_y(Param p_param) const {
ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0);
return params_y[p_param];
}
void Generic6DOFJoint3D::set_param_z(Param p_param, real_t p_value) {
ERR_FAIL_INDEX(p_param, PARAM_MAX);
params_z[p_param] = p_value;
if (is_configured()) {
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_rid(), Vector3::AXIS_Z, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value);
}
update_gizmos();
}
real_t Generic6DOFJoint3D::get_param_z(Param p_param) const {
ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0);
return params_z[p_param];
}
void Generic6DOFJoint3D::set_flag_x(Flag p_flag, bool p_enabled) {
ERR_FAIL_INDEX(p_flag, FLAG_MAX);
flags_x[p_flag] = p_enabled;
if (is_configured()) {
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(get_rid(), Vector3::AXIS_X, PhysicsServer3D::G6DOFJointAxisFlag(p_flag), p_enabled);
}
update_gizmos();
}
bool Generic6DOFJoint3D::get_flag_x(Flag p_flag) const {
ERR_FAIL_INDEX_V(p_flag, FLAG_MAX, false);
return flags_x[p_flag];
}
void Generic6DOFJoint3D::set_flag_y(Flag p_flag, bool p_enabled) {
ERR_FAIL_INDEX(p_flag, FLAG_MAX);
flags_y[p_flag] = p_enabled;
if (is_configured()) {
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(get_rid(), Vector3::AXIS_Y, PhysicsServer3D::G6DOFJointAxisFlag(p_flag), p_enabled);
}
update_gizmos();
}
bool Generic6DOFJoint3D::get_flag_y(Flag p_flag) const {
ERR_FAIL_INDEX_V(p_flag, FLAG_MAX, false);
return flags_y[p_flag];
}
void Generic6DOFJoint3D::set_flag_z(Flag p_flag, bool p_enabled) {
ERR_FAIL_INDEX(p_flag, FLAG_MAX);
flags_z[p_flag] = p_enabled;
if (is_configured()) {
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(get_rid(), Vector3::AXIS_Z, PhysicsServer3D::G6DOFJointAxisFlag(p_flag), p_enabled);
}
update_gizmos();
}
bool Generic6DOFJoint3D::get_flag_z(Flag p_flag) const {
ERR_FAIL_INDEX_V(p_flag, FLAG_MAX, false);
return flags_z[p_flag];
}
void Generic6DOFJoint3D::_configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) {
Transform3D gt = get_global_transform();
//Vector3 cone_twistpos = gt.origin;
//Vector3 cone_twistdir = gt.basis.get_axis(2);
Transform3D ainv = body_a->get_global_transform().affine_inverse();
Transform3D local_a = ainv * gt;
local_a.orthonormalize();
Transform3D local_b = gt;
if (body_b) {
Transform3D binv = body_b->get_global_transform().affine_inverse();
local_b = binv * gt;
}
local_b.orthonormalize();
PhysicsServer3D::get_singleton()->joint_make_generic_6dof(p_joint, body_a->get_rid(), local_a, body_b ? body_b->get_rid() : RID(), local_b);
for (int i = 0; i < PARAM_MAX; i++) {
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(p_joint, Vector3::AXIS_X, PhysicsServer3D::G6DOFJointAxisParam(i), params_x[i]);
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(p_joint, Vector3::AXIS_Y, PhysicsServer3D::G6DOFJointAxisParam(i), params_y[i]);
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(p_joint, Vector3::AXIS_Z, PhysicsServer3D::G6DOFJointAxisParam(i), params_z[i]);
}
for (int i = 0; i < FLAG_MAX; i++) {
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(p_joint, Vector3::AXIS_X, PhysicsServer3D::G6DOFJointAxisFlag(i), flags_x[i]);
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(p_joint, Vector3::AXIS_Y, PhysicsServer3D::G6DOFJointAxisFlag(i), flags_y[i]);
PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(p_joint, Vector3::AXIS_Z, PhysicsServer3D::G6DOFJointAxisFlag(i), flags_z[i]);
}
}
Generic6DOFJoint3D::Generic6DOFJoint3D() {
set_param_x(PARAM_LINEAR_LOWER_LIMIT, 0);
set_param_x(PARAM_LINEAR_UPPER_LIMIT, 0);
set_param_x(PARAM_LINEAR_LIMIT_SOFTNESS, 0.7);
set_param_x(PARAM_LINEAR_RESTITUTION, 0.5);
set_param_x(PARAM_LINEAR_DAMPING, 1.0);
set_param_x(PARAM_LINEAR_MOTOR_TARGET_VELOCITY, 0);
set_param_x(PARAM_LINEAR_MOTOR_FORCE_LIMIT, 0);
set_param_x(PARAM_LINEAR_SPRING_STIFFNESS, 0.01);
set_param_x(PARAM_LINEAR_SPRING_DAMPING, 0.01);
set_param_x(PARAM_LINEAR_SPRING_EQUILIBRIUM_POINT, 0.0);
set_param_x(PARAM_ANGULAR_LOWER_LIMIT, 0);
set_param_x(PARAM_ANGULAR_UPPER_LIMIT, 0);
set_param_x(PARAM_ANGULAR_LIMIT_SOFTNESS, 0.5f);
set_param_x(PARAM_ANGULAR_DAMPING, 1.0f);
set_param_x(PARAM_ANGULAR_RESTITUTION, 0);
set_param_x(PARAM_ANGULAR_FORCE_LIMIT, 0);
set_param_x(PARAM_ANGULAR_ERP, 0.5);
set_param_x(PARAM_ANGULAR_MOTOR_TARGET_VELOCITY, 0);
set_param_x(PARAM_ANGULAR_MOTOR_FORCE_LIMIT, 300);
set_param_x(PARAM_ANGULAR_SPRING_STIFFNESS, 0);
set_param_x(PARAM_ANGULAR_SPRING_DAMPING, 0);
set_param_x(PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT, 0);
set_flag_x(FLAG_ENABLE_ANGULAR_LIMIT, true);
set_flag_x(FLAG_ENABLE_LINEAR_LIMIT, true);
set_flag_x(FLAG_ENABLE_ANGULAR_SPRING, false);
set_flag_x(FLAG_ENABLE_LINEAR_SPRING, false);
set_flag_x(FLAG_ENABLE_MOTOR, false);
set_flag_x(FLAG_ENABLE_LINEAR_MOTOR, false);
set_param_y(PARAM_LINEAR_LOWER_LIMIT, 0);
set_param_y(PARAM_LINEAR_UPPER_LIMIT, 0);
set_param_y(PARAM_LINEAR_LIMIT_SOFTNESS, 0.7);
set_param_y(PARAM_LINEAR_RESTITUTION, 0.5);
set_param_y(PARAM_LINEAR_DAMPING, 1.0);
set_param_y(PARAM_LINEAR_MOTOR_TARGET_VELOCITY, 0);
set_param_y(PARAM_LINEAR_MOTOR_FORCE_LIMIT, 0);
set_param_y(PARAM_LINEAR_SPRING_STIFFNESS, 0.01);
set_param_y(PARAM_LINEAR_SPRING_DAMPING, 0.01);
set_param_y(PARAM_LINEAR_SPRING_EQUILIBRIUM_POINT, 0.0);
set_param_y(PARAM_ANGULAR_LOWER_LIMIT, 0);
set_param_y(PARAM_ANGULAR_UPPER_LIMIT, 0);
set_param_y(PARAM_ANGULAR_LIMIT_SOFTNESS, 0.5f);
set_param_y(PARAM_ANGULAR_DAMPING, 1.0f);
set_param_y(PARAM_ANGULAR_RESTITUTION, 0);
set_param_y(PARAM_ANGULAR_FORCE_LIMIT, 0);
set_param_y(PARAM_ANGULAR_ERP, 0.5);
set_param_y(PARAM_ANGULAR_MOTOR_TARGET_VELOCITY, 0);
set_param_y(PARAM_ANGULAR_MOTOR_FORCE_LIMIT, 300);
set_param_y(PARAM_ANGULAR_SPRING_STIFFNESS, 0);
set_param_y(PARAM_ANGULAR_SPRING_DAMPING, 0);
set_param_y(PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT, 0);
set_flag_y(FLAG_ENABLE_ANGULAR_LIMIT, true);
set_flag_y(FLAG_ENABLE_LINEAR_LIMIT, true);
set_flag_y(FLAG_ENABLE_ANGULAR_SPRING, false);
set_flag_y(FLAG_ENABLE_LINEAR_SPRING, false);
set_flag_y(FLAG_ENABLE_MOTOR, false);
set_flag_y(FLAG_ENABLE_LINEAR_MOTOR, false);
set_param_z(PARAM_LINEAR_LOWER_LIMIT, 0);
set_param_z(PARAM_LINEAR_UPPER_LIMIT, 0);
set_param_z(PARAM_LINEAR_LIMIT_SOFTNESS, 0.7);
set_param_z(PARAM_LINEAR_RESTITUTION, 0.5);
set_param_z(PARAM_LINEAR_DAMPING, 1.0);
set_param_z(PARAM_LINEAR_MOTOR_TARGET_VELOCITY, 0);
set_param_z(PARAM_LINEAR_MOTOR_FORCE_LIMIT, 0);
set_param_z(PARAM_LINEAR_SPRING_STIFFNESS, 0.01);
set_param_z(PARAM_LINEAR_SPRING_DAMPING, 0.01);
set_param_z(PARAM_LINEAR_SPRING_EQUILIBRIUM_POINT, 0.0);
set_param_z(PARAM_ANGULAR_LOWER_LIMIT, 0);
set_param_z(PARAM_ANGULAR_UPPER_LIMIT, 0);
set_param_z(PARAM_ANGULAR_LIMIT_SOFTNESS, 0.5f);
set_param_z(PARAM_ANGULAR_DAMPING, 1.0f);
set_param_z(PARAM_ANGULAR_RESTITUTION, 0);
set_param_z(PARAM_ANGULAR_FORCE_LIMIT, 0);
set_param_z(PARAM_ANGULAR_ERP, 0.5);
set_param_z(PARAM_ANGULAR_MOTOR_TARGET_VELOCITY, 0);
set_param_z(PARAM_ANGULAR_MOTOR_FORCE_LIMIT, 300);
set_param_z(PARAM_ANGULAR_SPRING_STIFFNESS, 0);
set_param_z(PARAM_ANGULAR_SPRING_DAMPING, 0);
set_param_z(PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT, 0);
set_flag_z(FLAG_ENABLE_ANGULAR_LIMIT, true);
set_flag_z(FLAG_ENABLE_LINEAR_LIMIT, true);
set_flag_z(FLAG_ENABLE_ANGULAR_SPRING, false);
set_flag_z(FLAG_ENABLE_LINEAR_SPRING, false);
set_flag_z(FLAG_ENABLE_MOTOR, false);
set_flag_z(FLAG_ENABLE_LINEAR_MOTOR, false);
}

View File

@@ -0,0 +1,109 @@
/**************************************************************************/
/* generic_6dof_joint_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/physics/joints/joint_3d.h"
class Generic6DOFJoint3D : public Joint3D {
GDCLASS(Generic6DOFJoint3D, Joint3D);
public:
enum Param {
PARAM_LINEAR_LOWER_LIMIT = PhysicsServer3D::G6DOF_JOINT_LINEAR_LOWER_LIMIT,
PARAM_LINEAR_UPPER_LIMIT = PhysicsServer3D::G6DOF_JOINT_LINEAR_UPPER_LIMIT,
PARAM_LINEAR_LIMIT_SOFTNESS = PhysicsServer3D::G6DOF_JOINT_LINEAR_LIMIT_SOFTNESS,
PARAM_LINEAR_RESTITUTION = PhysicsServer3D::G6DOF_JOINT_LINEAR_RESTITUTION,
PARAM_LINEAR_DAMPING = PhysicsServer3D::G6DOF_JOINT_LINEAR_DAMPING,
PARAM_LINEAR_MOTOR_TARGET_VELOCITY = PhysicsServer3D::G6DOF_JOINT_LINEAR_MOTOR_TARGET_VELOCITY,
PARAM_LINEAR_MOTOR_FORCE_LIMIT = PhysicsServer3D::G6DOF_JOINT_LINEAR_MOTOR_FORCE_LIMIT,
PARAM_LINEAR_SPRING_STIFFNESS = PhysicsServer3D::G6DOF_JOINT_LINEAR_SPRING_STIFFNESS,
PARAM_LINEAR_SPRING_DAMPING = PhysicsServer3D::G6DOF_JOINT_LINEAR_SPRING_DAMPING,
PARAM_LINEAR_SPRING_EQUILIBRIUM_POINT = PhysicsServer3D::G6DOF_JOINT_LINEAR_SPRING_EQUILIBRIUM_POINT,
PARAM_ANGULAR_LOWER_LIMIT = PhysicsServer3D::G6DOF_JOINT_ANGULAR_LOWER_LIMIT,
PARAM_ANGULAR_UPPER_LIMIT = PhysicsServer3D::G6DOF_JOINT_ANGULAR_UPPER_LIMIT,
PARAM_ANGULAR_LIMIT_SOFTNESS = PhysicsServer3D::G6DOF_JOINT_ANGULAR_LIMIT_SOFTNESS,
PARAM_ANGULAR_DAMPING = PhysicsServer3D::G6DOF_JOINT_ANGULAR_DAMPING,
PARAM_ANGULAR_RESTITUTION = PhysicsServer3D::G6DOF_JOINT_ANGULAR_RESTITUTION,
PARAM_ANGULAR_FORCE_LIMIT = PhysicsServer3D::G6DOF_JOINT_ANGULAR_FORCE_LIMIT,
PARAM_ANGULAR_ERP = PhysicsServer3D::G6DOF_JOINT_ANGULAR_ERP,
PARAM_ANGULAR_MOTOR_TARGET_VELOCITY = PhysicsServer3D::G6DOF_JOINT_ANGULAR_MOTOR_TARGET_VELOCITY,
PARAM_ANGULAR_MOTOR_FORCE_LIMIT = PhysicsServer3D::G6DOF_JOINT_ANGULAR_MOTOR_FORCE_LIMIT,
PARAM_ANGULAR_SPRING_STIFFNESS = PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_STIFFNESS,
PARAM_ANGULAR_SPRING_DAMPING = PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_DAMPING,
PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT = PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT,
PARAM_MAX = PhysicsServer3D::G6DOF_JOINT_MAX,
};
enum Flag {
FLAG_ENABLE_LINEAR_LIMIT = PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT,
FLAG_ENABLE_ANGULAR_LIMIT = PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT,
FLAG_ENABLE_LINEAR_SPRING = PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_SPRING,
FLAG_ENABLE_ANGULAR_SPRING = PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_SPRING,
FLAG_ENABLE_MOTOR = PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_MOTOR,
FLAG_ENABLE_LINEAR_MOTOR = PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_MOTOR,
FLAG_MAX = PhysicsServer3D::G6DOF_JOINT_FLAG_MAX
};
protected:
real_t params_x[PARAM_MAX];
bool flags_x[FLAG_MAX];
real_t params_y[PARAM_MAX];
bool flags_y[FLAG_MAX];
real_t params_z[PARAM_MAX];
bool flags_z[FLAG_MAX];
virtual void _configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) override;
static void _bind_methods();
public:
void set_param_x(Param p_param, real_t p_value);
real_t get_param_x(Param p_param) const;
void set_param_y(Param p_param, real_t p_value);
real_t get_param_y(Param p_param) const;
void set_param_z(Param p_param, real_t p_value);
real_t get_param_z(Param p_param) const;
void set_flag_x(Flag p_flag, bool p_enabled);
bool get_flag_x(Flag p_flag) const;
void set_flag_y(Flag p_flag, bool p_enabled);
bool get_flag_y(Flag p_flag) const;
void set_flag_z(Flag p_flag, bool p_enabled);
bool get_flag_z(Flag p_flag) const;
Generic6DOFJoint3D();
};
VARIANT_ENUM_CAST(Generic6DOFJoint3D::Param);
VARIANT_ENUM_CAST(Generic6DOFJoint3D::Flag);

View File

@@ -0,0 +1,135 @@
/**************************************************************************/
/* hinge_joint_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 "hinge_joint_3d.h"
void HingeJoint3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &HingeJoint3D::set_param);
ClassDB::bind_method(D_METHOD("get_param", "param"), &HingeJoint3D::get_param);
ClassDB::bind_method(D_METHOD("set_flag", "flag", "enabled"), &HingeJoint3D::set_flag);
ClassDB::bind_method(D_METHOD("get_flag", "flag"), &HingeJoint3D::get_flag);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "params/bias", PROPERTY_HINT_RANGE, "0.00,0.99,0.01"), "set_param", "get_param", PARAM_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit/enable"), "set_flag", "get_flag", FLAG_USE_LIMIT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/upper", PROPERTY_HINT_RANGE, "-180,180,0.1,radians_as_degrees"), "set_param", "get_param", PARAM_LIMIT_UPPER);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/lower", PROPERTY_HINT_RANGE, "-180,180,0.1,radians_as_degrees"), "set_param", "get_param", PARAM_LIMIT_LOWER);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/bias", PROPERTY_HINT_RANGE, "0.01,0.99,0.01"), "set_param", "get_param", PARAM_LIMIT_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param", "get_param", PARAM_LIMIT_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/relaxation", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param", "get_param", PARAM_LIMIT_RELAXATION);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "motor/enable"), "set_flag", "get_flag", FLAG_ENABLE_MOTOR);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "motor/target_velocity", PROPERTY_HINT_RANGE, U"-200,200,0.01,or_greater,or_less,radians_as_degrees,suffix:\u00B0/s"), "set_param", "get_param", PARAM_MOTOR_TARGET_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "motor/max_impulse", PROPERTY_HINT_RANGE, "0.01,1024,0.01"), "set_param", "get_param", PARAM_MOTOR_MAX_IMPULSE);
BIND_ENUM_CONSTANT(PARAM_BIAS);
BIND_ENUM_CONSTANT(PARAM_LIMIT_UPPER);
BIND_ENUM_CONSTANT(PARAM_LIMIT_LOWER);
BIND_ENUM_CONSTANT(PARAM_LIMIT_BIAS);
BIND_ENUM_CONSTANT(PARAM_LIMIT_SOFTNESS);
BIND_ENUM_CONSTANT(PARAM_LIMIT_RELAXATION);
BIND_ENUM_CONSTANT(PARAM_MOTOR_TARGET_VELOCITY);
BIND_ENUM_CONSTANT(PARAM_MOTOR_MAX_IMPULSE);
BIND_ENUM_CONSTANT(PARAM_MAX);
BIND_ENUM_CONSTANT(FLAG_USE_LIMIT);
BIND_ENUM_CONSTANT(FLAG_ENABLE_MOTOR);
BIND_ENUM_CONSTANT(FLAG_MAX);
}
void HingeJoint3D::set_param(Param p_param, real_t p_value) {
ERR_FAIL_INDEX(p_param, PARAM_MAX);
params[p_param] = p_value;
if (is_configured()) {
PhysicsServer3D::get_singleton()->hinge_joint_set_param(get_rid(), PhysicsServer3D::HingeJointParam(p_param), p_value);
}
update_gizmos();
}
real_t HingeJoint3D::get_param(Param p_param) const {
ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0);
return params[p_param];
}
void HingeJoint3D::set_flag(Flag p_flag, bool p_value) {
ERR_FAIL_INDEX(p_flag, FLAG_MAX);
flags[p_flag] = p_value;
if (is_configured()) {
PhysicsServer3D::get_singleton()->hinge_joint_set_flag(get_rid(), PhysicsServer3D::HingeJointFlag(p_flag), p_value);
}
update_gizmos();
}
bool HingeJoint3D::get_flag(Flag p_flag) const {
ERR_FAIL_INDEX_V(p_flag, FLAG_MAX, false);
return flags[p_flag];
}
void HingeJoint3D::_configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) {
Transform3D gt = get_global_transform();
Transform3D ainv = body_a->get_global_transform().affine_inverse();
Transform3D local_a = ainv * gt;
local_a.orthonormalize();
Transform3D local_b = gt;
if (body_b) {
Transform3D binv = body_b->get_global_transform().affine_inverse();
local_b = binv * gt;
}
local_b.orthonormalize();
PhysicsServer3D::get_singleton()->joint_make_hinge(p_joint, body_a->get_rid(), local_a, body_b ? body_b->get_rid() : RID(), local_b);
for (int i = 0; i < PARAM_MAX; i++) {
PhysicsServer3D::get_singleton()->hinge_joint_set_param(p_joint, PhysicsServer3D::HingeJointParam(i), params[i]);
}
for (int i = 0; i < FLAG_MAX; i++) {
set_flag(Flag(i), flags[i]);
PhysicsServer3D::get_singleton()->hinge_joint_set_flag(p_joint, PhysicsServer3D::HingeJointFlag(i), flags[i]);
}
}
HingeJoint3D::HingeJoint3D() {
params[PARAM_BIAS] = 0.3;
params[PARAM_LIMIT_UPPER] = Math::PI * 0.5;
params[PARAM_LIMIT_LOWER] = -Math::PI * 0.5;
params[PARAM_LIMIT_BIAS] = 0.3;
params[PARAM_LIMIT_SOFTNESS] = 0.9;
params[PARAM_LIMIT_RELAXATION] = 1.0;
params[PARAM_MOTOR_TARGET_VELOCITY] = 1;
params[PARAM_MOTOR_MAX_IMPULSE] = 1;
flags[FLAG_USE_LIMIT] = false;
flags[FLAG_ENABLE_MOTOR] = false;
}

View File

@@ -0,0 +1,74 @@
/**************************************************************************/
/* hinge_joint_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/physics/joints/joint_3d.h"
class HingeJoint3D : public Joint3D {
GDCLASS(HingeJoint3D, Joint3D);
public:
enum Param {
PARAM_BIAS = PhysicsServer3D::HINGE_JOINT_BIAS,
PARAM_LIMIT_UPPER = PhysicsServer3D::HINGE_JOINT_LIMIT_UPPER,
PARAM_LIMIT_LOWER = PhysicsServer3D::HINGE_JOINT_LIMIT_LOWER,
PARAM_LIMIT_BIAS = PhysicsServer3D::HINGE_JOINT_LIMIT_BIAS,
PARAM_LIMIT_SOFTNESS = PhysicsServer3D::HINGE_JOINT_LIMIT_SOFTNESS,
PARAM_LIMIT_RELAXATION = PhysicsServer3D::HINGE_JOINT_LIMIT_RELAXATION,
PARAM_MOTOR_TARGET_VELOCITY = PhysicsServer3D::HINGE_JOINT_MOTOR_TARGET_VELOCITY,
PARAM_MOTOR_MAX_IMPULSE = PhysicsServer3D::HINGE_JOINT_MOTOR_MAX_IMPULSE,
PARAM_MAX = PhysicsServer3D::HINGE_JOINT_MAX
};
enum Flag {
FLAG_USE_LIMIT = PhysicsServer3D::HINGE_JOINT_FLAG_USE_LIMIT,
FLAG_ENABLE_MOTOR = PhysicsServer3D::HINGE_JOINT_FLAG_ENABLE_MOTOR,
FLAG_MAX = PhysicsServer3D::HINGE_JOINT_FLAG_MAX
};
protected:
real_t params[PARAM_MAX];
bool flags[FLAG_MAX];
virtual void _configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) override;
static void _bind_methods();
public:
void set_param(Param p_param, real_t p_value);
real_t get_param(Param p_param) const;
void set_flag(Flag p_flag, bool p_value);
bool get_flag(Flag p_flag) const;
HingeJoint3D();
};
VARIANT_ENUM_CAST(HingeJoint3D::Param);
VARIANT_ENUM_CAST(HingeJoint3D::Flag);

View File

@@ -0,0 +1,243 @@
/**************************************************************************/
/* joint_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 "joint_3d.h"
void Joint3D::_disconnect_signals() {
Node *node_a = get_node_or_null(a);
PhysicsBody3D *body_a = Object::cast_to<PhysicsBody3D>(node_a);
if (body_a) {
body_a->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree));
}
Node *node_b = get_node_or_null(b);
PhysicsBody3D *body_b = Object::cast_to<PhysicsBody3D>(node_b);
if (body_b) {
body_b->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree));
}
}
void Joint3D::_body_exit_tree() {
_disconnect_signals();
_update_joint(true);
update_configuration_warnings();
}
void Joint3D::_update_joint(bool p_only_free) {
if (ba.is_valid() && bb.is_valid()) {
PhysicsServer3D::get_singleton()->body_remove_collision_exception(ba, bb);
PhysicsServer3D::get_singleton()->body_remove_collision_exception(bb, ba);
}
ba = RID();
bb = RID();
configured = false;
if (p_only_free || !is_inside_tree()) {
PhysicsServer3D::get_singleton()->joint_clear(joint);
warning = String();
return;
}
Node *node_a = get_node_or_null(a);
Node *node_b = get_node_or_null(b);
PhysicsBody3D *body_a = Object::cast_to<PhysicsBody3D>(node_a);
PhysicsBody3D *body_b = Object::cast_to<PhysicsBody3D>(node_b);
if (node_a && !body_a && node_b && !body_b) {
warning = RTR("Node A and Node B must be PhysicsBody3Ds");
} else if (node_a && !body_a) {
warning = RTR("Node A must be a PhysicsBody3D");
} else if (node_b && !body_b) {
warning = RTR("Node B must be a PhysicsBody3D");
} else if (!body_a && !body_b) {
warning = RTR("Joint is not connected to any PhysicsBody3Ds");
} else if (body_a == body_b) {
warning = RTR("Node A and Node B must be different PhysicsBody3Ds");
} else {
warning = String();
}
update_configuration_warnings();
if (!warning.is_empty()) {
PhysicsServer3D::get_singleton()->joint_clear(joint);
return;
}
configured = true;
if (body_a) {
_configure_joint(joint, body_a, body_b);
} else if (body_b) {
_configure_joint(joint, body_b, nullptr);
}
PhysicsServer3D::get_singleton()->joint_set_solver_priority(joint, solver_priority);
if (body_a) {
ba = body_a->get_rid();
if (!body_a->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree))) {
body_a->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree));
}
}
if (body_b) {
bb = body_b->get_rid();
if (!body_b->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree))) {
body_b->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree));
}
}
PhysicsServer3D::get_singleton()->joint_disable_collisions_between_bodies(joint, exclude_from_collision);
}
void Joint3D::set_node_a(const NodePath &p_node_a) {
if (a == p_node_a) {
return;
}
if (is_configured()) {
_disconnect_signals();
}
a = p_node_a;
_update_joint();
}
NodePath Joint3D::get_node_a() const {
return a;
}
void Joint3D::set_node_b(const NodePath &p_node_b) {
if (b == p_node_b) {
return;
}
if (is_configured()) {
_disconnect_signals();
}
b = p_node_b;
_update_joint();
}
NodePath Joint3D::get_node_b() const {
return b;
}
void Joint3D::set_solver_priority(int p_priority) {
solver_priority = p_priority;
if (joint.is_valid()) {
PhysicsServer3D::get_singleton()->joint_set_solver_priority(joint, solver_priority);
}
}
int Joint3D::get_solver_priority() const {
return solver_priority;
}
void Joint3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POST_ENTER_TREE: {
if (is_configured()) {
_disconnect_signals();
}
_update_joint();
} break;
case NOTIFICATION_EXIT_TREE: {
if (is_configured()) {
_disconnect_signals();
}
_update_joint(true);
} break;
}
}
void Joint3D::set_exclude_nodes_from_collision(bool p_enable) {
if (exclude_from_collision == p_enable) {
return;
}
if (is_configured()) {
_disconnect_signals();
}
_update_joint(true);
exclude_from_collision = p_enable;
_update_joint();
}
bool Joint3D::get_exclude_nodes_from_collision() const {
return exclude_from_collision;
}
PackedStringArray Joint3D::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();
if (!warning.is_empty()) {
warnings.push_back(warning);
}
return warnings;
}
void Joint3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_node_a", "node"), &Joint3D::set_node_a);
ClassDB::bind_method(D_METHOD("get_node_a"), &Joint3D::get_node_a);
ClassDB::bind_method(D_METHOD("set_node_b", "node"), &Joint3D::set_node_b);
ClassDB::bind_method(D_METHOD("get_node_b"), &Joint3D::get_node_b);
ClassDB::bind_method(D_METHOD("set_solver_priority", "priority"), &Joint3D::set_solver_priority);
ClassDB::bind_method(D_METHOD("get_solver_priority"), &Joint3D::get_solver_priority);
ClassDB::bind_method(D_METHOD("set_exclude_nodes_from_collision", "enable"), &Joint3D::set_exclude_nodes_from_collision);
ClassDB::bind_method(D_METHOD("get_exclude_nodes_from_collision"), &Joint3D::get_exclude_nodes_from_collision);
ClassDB::bind_method(D_METHOD("get_rid"), &Joint3D::get_rid);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicsBody3D"), "set_node_a", "get_node_a");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicsBody3D"), "set_node_b", "get_node_b");
ADD_PROPERTY(PropertyInfo(Variant::INT, "solver_priority", PROPERTY_HINT_RANGE, "1,8,1"), "set_solver_priority", "get_solver_priority");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_nodes_from_collision"), "set_exclude_nodes_from_collision", "get_exclude_nodes_from_collision");
}
Joint3D::Joint3D() {
set_notify_transform(true);
joint = PhysicsServer3D::get_singleton()->joint_create();
}
Joint3D::~Joint3D() {
ERR_FAIL_NULL(PhysicsServer3D::get_singleton());
PhysicsServer3D::get_singleton()->free(joint);
}

View File

@@ -0,0 +1,82 @@
/**************************************************************************/
/* joint_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/node_3d.h"
#include "scene/3d/physics/physics_body_3d.h"
class Joint3D : public Node3D {
GDCLASS(Joint3D, Node3D);
RID ba, bb;
RID joint;
NodePath a;
NodePath b;
int solver_priority = 1;
bool exclude_from_collision = true;
String warning;
bool configured = false;
protected:
void _disconnect_signals();
void _body_exit_tree();
void _update_joint(bool p_only_free = false);
void _notification(int p_what);
virtual void _configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) = 0;
static void _bind_methods();
_FORCE_INLINE_ bool is_configured() const { return configured; }
public:
virtual PackedStringArray get_configuration_warnings() const override;
void set_node_a(const NodePath &p_node_a);
NodePath get_node_a() const;
void set_node_b(const NodePath &p_node_b);
NodePath get_node_b() const;
void set_solver_priority(int p_priority);
int get_solver_priority() const;
void set_exclude_nodes_from_collision(bool p_enable);
bool get_exclude_nodes_from_collision() const;
RID get_rid() const { return joint; }
Joint3D();
~Joint3D();
};

View File

@@ -0,0 +1,80 @@
/**************************************************************************/
/* pin_joint_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 "pin_joint_3d.h"
void PinJoint3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &PinJoint3D::set_param);
ClassDB::bind_method(D_METHOD("get_param", "param"), &PinJoint3D::get_param);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "params/bias", PROPERTY_HINT_RANGE, "0.01,0.99,0.01"), "set_param", "get_param", PARAM_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "params/damping", PROPERTY_HINT_RANGE, "0.01,8.0,0.01"), "set_param", "get_param", PARAM_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "params/impulse_clamp", PROPERTY_HINT_RANGE, "0.0,64.0,0.01"), "set_param", "get_param", PARAM_IMPULSE_CLAMP);
BIND_ENUM_CONSTANT(PARAM_BIAS);
BIND_ENUM_CONSTANT(PARAM_DAMPING);
BIND_ENUM_CONSTANT(PARAM_IMPULSE_CLAMP);
}
void PinJoint3D::set_param(Param p_param, real_t p_value) {
ERR_FAIL_INDEX(p_param, 3);
params[p_param] = p_value;
if (is_configured()) {
PhysicsServer3D::get_singleton()->pin_joint_set_param(get_rid(), PhysicsServer3D::PinJointParam(p_param), p_value);
}
}
real_t PinJoint3D::get_param(Param p_param) const {
ERR_FAIL_INDEX_V(p_param, 3, 0);
return params[p_param];
}
void PinJoint3D::_configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) {
Vector3 pinpos = get_global_transform().origin;
Vector3 local_a = body_a->to_local(pinpos);
Vector3 local_b;
if (body_b) {
local_b = body_b->to_local(pinpos);
} else {
local_b = pinpos;
}
PhysicsServer3D::get_singleton()->joint_make_pin(p_joint, body_a->get_rid(), local_a, body_b ? body_b->get_rid() : RID(), local_b);
for (int i = 0; i < 3; i++) {
PhysicsServer3D::get_singleton()->pin_joint_set_param(p_joint, PhysicsServer3D::PinJointParam(i), params[i]);
}
}
PinJoint3D::PinJoint3D() {
params[PARAM_BIAS] = 0.3;
params[PARAM_DAMPING] = 1;
params[PARAM_IMPULSE_CLAMP] = 0;
}

View File

@@ -0,0 +1,57 @@
/**************************************************************************/
/* pin_joint_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/physics/joints/joint_3d.h"
class PinJoint3D : public Joint3D {
GDCLASS(PinJoint3D, Joint3D);
public:
enum Param {
PARAM_BIAS = PhysicsServer3D::PIN_JOINT_BIAS,
PARAM_DAMPING = PhysicsServer3D::PIN_JOINT_DAMPING,
PARAM_IMPULSE_CLAMP = PhysicsServer3D::PIN_JOINT_IMPULSE_CLAMP
};
protected:
real_t params[3];
virtual void _configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) override;
static void _bind_methods();
public:
void set_param(Param p_param, real_t p_value);
real_t get_param(Param p_param) const;
PinJoint3D();
};
VARIANT_ENUM_CAST(PinJoint3D::Param);

View File

@@ -0,0 +1,147 @@
/**************************************************************************/
/* slider_joint_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 "slider_joint_3d.h"
void SliderJoint3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &SliderJoint3D::set_param);
ClassDB::bind_method(D_METHOD("get_param", "param"), &SliderJoint3D::get_param);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/upper_distance", PROPERTY_HINT_RANGE, "-1024,1024,0.01,suffix:m"), "set_param", "get_param", PARAM_LINEAR_LIMIT_UPPER);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/lower_distance", PROPERTY_HINT_RANGE, "-1024,1024,0.01,suffix:m"), "set_param", "get_param", PARAM_LINEAR_LIMIT_LOWER);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_LIMIT_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_LIMIT_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/damping", PROPERTY_HINT_RANGE, "0,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_LIMIT_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motion/softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_MOTION_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motion/restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_MOTION_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motion/damping", PROPERTY_HINT_RANGE, "0,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_MOTION_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_ortho/softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_ORTHOGONAL_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_ortho/restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_ORTHOGONAL_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_ortho/damping", PROPERTY_HINT_RANGE, "0,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_ORTHOGONAL_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/upper_angle", PROPERTY_HINT_RANGE, "-180,180,0.1,radians_as_degrees"), "set_param", "get_param", PARAM_ANGULAR_LIMIT_UPPER);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/lower_angle", PROPERTY_HINT_RANGE, "-180,180,0.1,radians_as_degrees"), "set_param", "get_param", PARAM_ANGULAR_LIMIT_LOWER);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_ANGULAR_LIMIT_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_ANGULAR_LIMIT_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/damping", PROPERTY_HINT_RANGE, "0,16.0,0.01"), "set_param", "get_param", PARAM_ANGULAR_LIMIT_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motion/softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_ANGULAR_MOTION_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motion/restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_ANGULAR_MOTION_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motion/damping", PROPERTY_HINT_RANGE, "0,16.0,0.01"), "set_param", "get_param", PARAM_ANGULAR_MOTION_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_ortho/softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_ANGULAR_ORTHOGONAL_SOFTNESS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_ortho/restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_ANGULAR_ORTHOGONAL_RESTITUTION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_ortho/damping", PROPERTY_HINT_RANGE, "0,16.0,0.01"), "set_param", "get_param", PARAM_ANGULAR_ORTHOGONAL_DAMPING);
BIND_ENUM_CONSTANT(PARAM_LINEAR_LIMIT_UPPER);
BIND_ENUM_CONSTANT(PARAM_LINEAR_LIMIT_LOWER);
BIND_ENUM_CONSTANT(PARAM_LINEAR_LIMIT_SOFTNESS);
BIND_ENUM_CONSTANT(PARAM_LINEAR_LIMIT_RESTITUTION);
BIND_ENUM_CONSTANT(PARAM_LINEAR_LIMIT_DAMPING);
BIND_ENUM_CONSTANT(PARAM_LINEAR_MOTION_SOFTNESS);
BIND_ENUM_CONSTANT(PARAM_LINEAR_MOTION_RESTITUTION);
BIND_ENUM_CONSTANT(PARAM_LINEAR_MOTION_DAMPING);
BIND_ENUM_CONSTANT(PARAM_LINEAR_ORTHOGONAL_SOFTNESS);
BIND_ENUM_CONSTANT(PARAM_LINEAR_ORTHOGONAL_RESTITUTION);
BIND_ENUM_CONSTANT(PARAM_LINEAR_ORTHOGONAL_DAMPING);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_LIMIT_UPPER);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_LIMIT_LOWER);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_LIMIT_SOFTNESS);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_LIMIT_RESTITUTION);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_LIMIT_DAMPING);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_MOTION_SOFTNESS);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_MOTION_RESTITUTION);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_MOTION_DAMPING);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_ORTHOGONAL_SOFTNESS);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_ORTHOGONAL_RESTITUTION);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_ORTHOGONAL_DAMPING);
BIND_ENUM_CONSTANT(PARAM_MAX);
}
void SliderJoint3D::set_param(Param p_param, real_t p_value) {
ERR_FAIL_INDEX(p_param, PARAM_MAX);
params[p_param] = p_value;
if (is_configured()) {
PhysicsServer3D::get_singleton()->slider_joint_set_param(get_rid(), PhysicsServer3D::SliderJointParam(p_param), p_value);
}
update_gizmos();
}
real_t SliderJoint3D::get_param(Param p_param) const {
ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0);
return params[p_param];
}
void SliderJoint3D::_configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) {
Transform3D gt = get_global_transform();
Transform3D ainv = body_a->get_global_transform().affine_inverse();
Transform3D local_a = ainv * gt;
local_a.orthonormalize();
Transform3D local_b = gt;
if (body_b) {
Transform3D binv = body_b->get_global_transform().affine_inverse();
local_b = binv * gt;
}
local_b.orthonormalize();
PhysicsServer3D::get_singleton()->joint_make_slider(p_joint, body_a->get_rid(), local_a, body_b ? body_b->get_rid() : RID(), local_b);
for (int i = 0; i < PARAM_MAX; i++) {
PhysicsServer3D::get_singleton()->slider_joint_set_param(p_joint, PhysicsServer3D::SliderJointParam(i), params[i]);
}
}
SliderJoint3D::SliderJoint3D() {
params[PARAM_LINEAR_LIMIT_UPPER] = 1.0;
params[PARAM_LINEAR_LIMIT_LOWER] = -1.0;
params[PARAM_LINEAR_LIMIT_SOFTNESS] = 1.0;
params[PARAM_LINEAR_LIMIT_RESTITUTION] = 0.7;
params[PARAM_LINEAR_LIMIT_DAMPING] = 1.0;
params[PARAM_LINEAR_MOTION_SOFTNESS] = 1.0;
params[PARAM_LINEAR_MOTION_RESTITUTION] = 0.7;
params[PARAM_LINEAR_MOTION_DAMPING] = 0; //1.0;
params[PARAM_LINEAR_ORTHOGONAL_SOFTNESS] = 1.0;
params[PARAM_LINEAR_ORTHOGONAL_RESTITUTION] = 0.7;
params[PARAM_LINEAR_ORTHOGONAL_DAMPING] = 1.0;
params[PARAM_ANGULAR_LIMIT_UPPER] = 0;
params[PARAM_ANGULAR_LIMIT_LOWER] = 0;
params[PARAM_ANGULAR_LIMIT_SOFTNESS] = 1.0;
params[PARAM_ANGULAR_LIMIT_RESTITUTION] = 0.7;
params[PARAM_ANGULAR_LIMIT_DAMPING] = 0; //1.0;
params[PARAM_ANGULAR_MOTION_SOFTNESS] = 1.0;
params[PARAM_ANGULAR_MOTION_RESTITUTION] = 0.7;
params[PARAM_ANGULAR_MOTION_DAMPING] = 1.0;
params[PARAM_ANGULAR_ORTHOGONAL_SOFTNESS] = 1.0;
params[PARAM_ANGULAR_ORTHOGONAL_RESTITUTION] = 0.7;
params[PARAM_ANGULAR_ORTHOGONAL_DAMPING] = 1.0;
}

View File

@@ -0,0 +1,79 @@
/**************************************************************************/
/* slider_joint_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/physics/joints/joint_3d.h"
class SliderJoint3D : public Joint3D {
GDCLASS(SliderJoint3D, Joint3D);
public:
enum Param {
PARAM_LINEAR_LIMIT_UPPER = PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER,
PARAM_LINEAR_LIMIT_LOWER = PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER,
PARAM_LINEAR_LIMIT_SOFTNESS = PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS,
PARAM_LINEAR_LIMIT_RESTITUTION = PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION,
PARAM_LINEAR_LIMIT_DAMPING = PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING,
PARAM_LINEAR_MOTION_SOFTNESS = PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS,
PARAM_LINEAR_MOTION_RESTITUTION = PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION,
PARAM_LINEAR_MOTION_DAMPING = PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING,
PARAM_LINEAR_ORTHOGONAL_SOFTNESS = PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS,
PARAM_LINEAR_ORTHOGONAL_RESTITUTION = PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION,
PARAM_LINEAR_ORTHOGONAL_DAMPING = PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING,
PARAM_ANGULAR_LIMIT_UPPER = PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER,
PARAM_ANGULAR_LIMIT_LOWER = PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER,
PARAM_ANGULAR_LIMIT_SOFTNESS = PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS,
PARAM_ANGULAR_LIMIT_RESTITUTION = PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION,
PARAM_ANGULAR_LIMIT_DAMPING = PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING,
PARAM_ANGULAR_MOTION_SOFTNESS = PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS,
PARAM_ANGULAR_MOTION_RESTITUTION = PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION,
PARAM_ANGULAR_MOTION_DAMPING = PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING,
PARAM_ANGULAR_ORTHOGONAL_SOFTNESS = PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS,
PARAM_ANGULAR_ORTHOGONAL_RESTITUTION = PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION,
PARAM_ANGULAR_ORTHOGONAL_DAMPING = PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING,
PARAM_MAX = PhysicsServer3D::SLIDER_JOINT_MAX
};
protected:
real_t params[PARAM_MAX];
virtual void _configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) override;
static void _bind_methods();
public:
void set_param(Param p_param, real_t p_value);
real_t get_param(Param p_param) const;
SliderJoint3D();
};
VARIANT_ENUM_CAST(SliderJoint3D::Param);

View File

@@ -0,0 +1,134 @@
/**************************************************************************/
/* kinematic_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 "kinematic_collision_3d.h"
#include "scene/3d/physics/physics_body_3d.h"
Vector3 KinematicCollision3D::get_travel() const {
return result.travel;
}
Vector3 KinematicCollision3D::get_remainder() const {
return result.remainder;
}
int KinematicCollision3D::get_collision_count() const {
return result.collision_count;
}
real_t KinematicCollision3D::get_depth() const {
return result.collision_depth;
}
Vector3 KinematicCollision3D::get_position(int p_collision_index) const {
ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Vector3());
return result.collisions[p_collision_index].position;
}
Vector3 KinematicCollision3D::get_normal(int p_collision_index) const {
ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Vector3());
return result.collisions[p_collision_index].normal;
}
real_t KinematicCollision3D::get_angle(int p_collision_index, const Vector3 &p_up_direction) const {
ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, 0.0);
ERR_FAIL_COND_V(p_up_direction == Vector3(), 0);
return result.collisions[p_collision_index].get_angle(p_up_direction);
}
Object *KinematicCollision3D::get_local_shape(int p_collision_index) const {
ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, nullptr);
PhysicsBody3D *owner = ObjectDB::get_instance<PhysicsBody3D>(owner_id);
if (!owner) {
return nullptr;
}
uint32_t ownerid = owner->shape_find_owner(result.collisions[p_collision_index].local_shape);
return owner->shape_owner_get_owner(ownerid);
}
Object *KinematicCollision3D::get_collider(int p_collision_index) const {
ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, nullptr);
if (result.collisions[p_collision_index].collider_id.is_valid()) {
return ObjectDB::get_instance(result.collisions[p_collision_index].collider_id);
}
return nullptr;
}
ObjectID KinematicCollision3D::get_collider_id(int p_collision_index) const {
ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, ObjectID());
return result.collisions[p_collision_index].collider_id;
}
RID KinematicCollision3D::get_collider_rid(int p_collision_index) const {
ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, RID());
return result.collisions[p_collision_index].collider;
}
Object *KinematicCollision3D::get_collider_shape(int p_collision_index) const {
Object *collider = get_collider(p_collision_index);
if (collider) {
CollisionObject3D *obj2d = Object::cast_to<CollisionObject3D>(collider);
if (obj2d) {
uint32_t ownerid = obj2d->shape_find_owner(result.collisions[p_collision_index].collider_shape);
return obj2d->shape_owner_get_owner(ownerid);
}
}
return nullptr;
}
int KinematicCollision3D::get_collider_shape_index(int p_collision_index) const {
ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, 0);
return result.collisions[p_collision_index].collider_shape;
}
Vector3 KinematicCollision3D::get_collider_velocity(int p_collision_index) const {
ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Vector3());
return result.collisions[p_collision_index].collider_velocity;
}
void KinematicCollision3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_travel"), &KinematicCollision3D::get_travel);
ClassDB::bind_method(D_METHOD("get_remainder"), &KinematicCollision3D::get_remainder);
ClassDB::bind_method(D_METHOD("get_depth"), &KinematicCollision3D::get_depth);
ClassDB::bind_method(D_METHOD("get_collision_count"), &KinematicCollision3D::get_collision_count);
ClassDB::bind_method(D_METHOD("get_position", "collision_index"), &KinematicCollision3D::get_position, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_normal", "collision_index"), &KinematicCollision3D::get_normal, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_angle", "collision_index", "up_direction"), &KinematicCollision3D::get_angle, DEFVAL(0), DEFVAL(Vector3(0.0, 1.0, 0.0)));
ClassDB::bind_method(D_METHOD("get_local_shape", "collision_index"), &KinematicCollision3D::get_local_shape, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_collider", "collision_index"), &KinematicCollision3D::get_collider, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_collider_id", "collision_index"), &KinematicCollision3D::get_collider_id, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_collider_rid", "collision_index"), &KinematicCollision3D::get_collider_rid, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_collider_shape", "collision_index"), &KinematicCollision3D::get_collider_shape, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_collider_shape_index", "collision_index"), &KinematicCollision3D::get_collider_shape_index, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_collider_velocity", "collision_index"), &KinematicCollision3D::get_collider_velocity, DEFVAL(0));
}

View File

@@ -0,0 +1,62 @@
/**************************************************************************/
/* kinematic_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. */
/**************************************************************************/
#pragma once
#include "core/object/ref_counted.h"
#include "servers/physics_server_3d.h"
class KinematicCollision3D : public RefCounted {
GDCLASS(KinematicCollision3D, RefCounted);
ObjectID owner_id;
friend class PhysicsBody3D;
friend class CharacterBody3D;
PhysicsServer3D::MotionResult result;
protected:
static void _bind_methods();
public:
Vector3 get_travel() const;
Vector3 get_remainder() const;
int get_collision_count() const;
real_t get_depth() const;
Vector3 get_position(int p_collision_index = 0) const;
Vector3 get_normal(int p_collision_index = 0) const;
real_t get_angle(int p_collision_index = 0, const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const;
Object *get_local_shape(int p_collision_index = 0) const;
Object *get_collider(int p_collision_index = 0) const;
ObjectID get_collider_id(int p_collision_index = 0) const;
RID get_collider_rid(int p_collision_index = 0) const;
Object *get_collider_shape(int p_collision_index = 0) const;
int get_collider_shape_index(int p_collision_index = 0) const;
Vector3 get_collider_velocity(int p_collision_index = 0) const;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,306 @@
/**************************************************************************/
/* physical_bone_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/physics/physics_body_3d.h"
#include "scene/3d/skeleton_3d.h"
class PhysicalBoneSimulator3D;
class PhysicalBone3D : public PhysicsBody3D {
GDCLASS(PhysicalBone3D, PhysicsBody3D);
public:
enum DampMode {
DAMP_MODE_COMBINE,
DAMP_MODE_REPLACE,
};
enum JointType {
JOINT_TYPE_NONE,
JOINT_TYPE_PIN,
JOINT_TYPE_CONE,
JOINT_TYPE_HINGE,
JOINT_TYPE_SLIDER,
JOINT_TYPE_6DOF
};
struct JointData {
virtual JointType get_joint_type() { return JOINT_TYPE_NONE; }
/// "j" is used to set the parameter inside the PhysicsServer3D
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
virtual ~JointData() {}
};
struct PinJointData : public JointData {
virtual JointType get_joint_type() { return JOINT_TYPE_PIN; }
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
real_t bias = 0.3;
real_t damping = 1.0;
real_t impulse_clamp = 0.0;
};
struct ConeJointData : public JointData {
virtual JointType get_joint_type() { return JOINT_TYPE_CONE; }
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
real_t swing_span = Math::PI * 0.25;
real_t twist_span = Math::PI;
real_t bias = 0.3;
real_t softness = 0.8;
real_t relaxation = 1.;
};
struct HingeJointData : public JointData {
virtual JointType get_joint_type() { return JOINT_TYPE_HINGE; }
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
bool angular_limit_enabled = false;
real_t angular_limit_upper = Math::PI * 0.5;
real_t angular_limit_lower = -Math::PI * 0.5;
real_t angular_limit_bias = 0.3;
real_t angular_limit_softness = 0.9;
real_t angular_limit_relaxation = 1.;
};
struct SliderJointData : public JointData {
virtual JointType get_joint_type() { return JOINT_TYPE_SLIDER; }
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
real_t linear_limit_upper = 1.0;
real_t linear_limit_lower = -1.0;
real_t linear_limit_softness = 1.0;
real_t linear_limit_restitution = 0.7;
real_t linear_limit_damping = 1.0;
real_t angular_limit_upper = 0.0;
real_t angular_limit_lower = 0.0;
real_t angular_limit_softness = 1.0;
real_t angular_limit_restitution = 0.7;
real_t angular_limit_damping = 1.0;
};
struct SixDOFJointData : public JointData {
struct SixDOFAxisData {
bool linear_limit_enabled = true;
real_t linear_limit_upper = 0.0;
real_t linear_limit_lower = 0.0;
real_t linear_limit_softness = 0.7;
real_t linear_restitution = 0.5;
real_t linear_damping = 1.0;
bool linear_spring_enabled = false;
real_t linear_spring_stiffness = 0.0;
real_t linear_spring_damping = 0.0;
real_t linear_equilibrium_point = 0.0;
bool angular_limit_enabled = true;
real_t angular_limit_upper = 0.0;
real_t angular_limit_lower = 0.0;
real_t angular_limit_softness = 0.5;
real_t angular_restitution = 0.0;
real_t angular_damping = 1.0;
real_t erp = 0.5;
bool angular_spring_enabled = false;
real_t angular_spring_stiffness = 0.0;
real_t angular_spring_damping = 0.0;
real_t angular_equilibrium_point = 0.0;
};
virtual JointType get_joint_type() { return JOINT_TYPE_6DOF; }
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
SixDOFAxisData axis_data[3];
};
private:
#ifdef TOOLS_ENABLED
// if false gizmo move body
bool gizmo_move_joint = false;
#endif
JointData *joint_data = nullptr;
Transform3D joint_offset;
RID joint;
ObjectID simulator_id;
Transform3D body_offset;
Transform3D body_offset_inverse;
bool simulate_physics = false;
bool _internal_simulate_physics = false;
int bone_id = -1;
String bone_name;
real_t bounce = 0.0;
real_t mass = 1.0;
real_t friction = 1.0;
Vector3 linear_velocity;
Vector3 angular_velocity;
real_t gravity_scale = 1.0;
bool can_sleep = true;
bool custom_integrator = false;
DampMode linear_damp_mode = DAMP_MODE_COMBINE;
DampMode angular_damp_mode = DAMP_MODE_COMBINE;
real_t linear_damp = 0.0;
real_t angular_damp = 0.0;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
void _notification(int p_what);
GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState3D *)
static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state);
void _body_state_changed(PhysicsDirectBodyState3D *p_state);
static void _bind_methods();
private:
void _sync_body_state(PhysicsDirectBodyState3D *p_state);
void _update_joint_offset();
void _fix_joint_offset();
void _reload_joint();
void _update_simulator_path();
public:
void _on_bone_parent_changed();
PhysicalBoneSimulator3D *get_simulator() const;
Skeleton3D *get_skeleton() const;
void set_linear_velocity(const Vector3 &p_velocity);
Vector3 get_linear_velocity() const override;
void set_angular_velocity(const Vector3 &p_velocity);
Vector3 get_angular_velocity() const override;
void set_use_custom_integrator(bool p_enable);
bool is_using_custom_integrator();
#ifdef TOOLS_ENABLED
void _set_gizmo_move_joint(bool p_move_joint);
virtual Transform3D get_global_gizmo_transform() const override;
virtual Transform3D get_local_gizmo_transform() const override;
#endif
const JointData *get_joint_data() const;
int get_bone_id() const {
return bone_id;
}
void set_joint_type(JointType p_joint_type);
JointType get_joint_type() const;
void set_joint_offset(const Transform3D &p_offset);
const Transform3D &get_joint_offset() const;
void set_joint_rotation(const Vector3 &p_euler_rad);
Vector3 get_joint_rotation() const;
void set_body_offset(const Transform3D &p_offset);
const Transform3D &get_body_offset() const;
void set_simulate_physics(bool p_simulate);
bool get_simulate_physics();
bool is_simulating_physics();
void set_bone_name(const String &p_name);
const String &get_bone_name() const;
void set_mass(real_t p_mass);
real_t get_mass() const;
void set_friction(real_t p_friction);
real_t get_friction() const;
void set_bounce(real_t p_bounce);
real_t get_bounce() const;
void set_gravity_scale(real_t p_gravity_scale);
real_t get_gravity_scale() const;
void set_linear_damp_mode(DampMode p_mode);
DampMode get_linear_damp_mode() const;
void set_angular_damp_mode(DampMode p_mode);
DampMode get_angular_damp_mode() const;
void set_linear_damp(real_t p_linear_damp);
real_t get_linear_damp() const;
void set_angular_damp(real_t p_angular_damp);
real_t get_angular_damp() const;
void set_can_sleep(bool p_active);
bool is_able_to_sleep() const;
void apply_central_impulse(const Vector3 &p_impulse);
void apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position = Vector3());
void reset_physics_simulation_state();
void reset_to_rest_position();
PhysicalBone3D();
~PhysicalBone3D();
private:
void update_bone_id();
void update_offset();
void _start_physics_simulation();
void _stop_physics_simulation();
};
VARIANT_ENUM_CAST(PhysicalBone3D::JointType);
VARIANT_ENUM_CAST(PhysicalBone3D::DampMode);

View File

@@ -0,0 +1,396 @@
/**************************************************************************/
/* physical_bone_simulator_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 "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))) {
p_old->disconnect(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed));
}
if (p_old->is_connected(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated))) {
p_old->disconnect(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated));
}
}
if (p_new) {
if (!p_new->is_connected(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed))) {
p_new->connect(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed));
}
if (!p_new->is_connected(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated))) {
p_new->connect(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated));
}
}
_bone_list_changed();
}
void PhysicalBoneSimulator3D::_bone_list_changed() {
bones.clear();
Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
for (int i = 0; i < skeleton->get_bone_count(); i++) {
SimulatedBone sb;
sb.parent = skeleton->get_bone_parent(i);
sb.child_bones = skeleton->get_bone_children(i);
bones.push_back(sb);
}
_rebuild_physical_bones_cache();
_pose_updated();
}
void PhysicalBoneSimulator3D::_pose_updated() {
Skeleton3D *skeleton = get_skeleton();
if (!skeleton || simulating) {
return;
}
// If this triggers that means that we likely haven't rebuilt the bone list yet.
if (skeleton->get_bone_count() != bones.size()) {
// NOTE: this is re-entrant and will call _pose_updated again.
_bone_list_changed();
} else {
for (int i = 0; i < skeleton->get_bone_count(); 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();
}
}
void PhysicalBoneSimulator3D::_reset_physical_bones_state() {
for (int i = 0; i < bones.size(); i += 1) {
if (bones[i].physical_bone) {
bones[i].physical_bone->reset_physics_simulation_state();
}
}
}
bool PhysicalBoneSimulator3D::is_simulating_physics() const {
return simulating;
}
int PhysicalBoneSimulator3D::find_bone(const String &p_name) const {
Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return -1;
}
return skeleton->find_bone(p_name);
}
String PhysicalBoneSimulator3D::get_bone_name(int p_bone) const {
Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return String();
}
return skeleton->get_bone_name(p_bone);
}
int PhysicalBoneSimulator3D::get_bone_count() const {
return bones.size();
}
bool PhysicalBoneSimulator3D::is_bone_parent_of(int p_bone, int p_parent_bone_id) const {
Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return false;
}
return skeleton->is_bone_parent_of(p_bone, p_parent_bone_id);
}
void PhysicalBoneSimulator3D::bind_physical_bone_to_bone(int p_bone, PhysicalBone3D *p_physical_bone) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
ERR_FAIL_COND(bones[p_bone].physical_bone);
ERR_FAIL_NULL(p_physical_bone);
bones.write[p_bone].physical_bone = p_physical_bone;
_rebuild_physical_bones_cache();
}
void PhysicalBoneSimulator3D::unbind_physical_bone_from_bone(int p_bone) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
bones.write[p_bone].physical_bone = nullptr;
_rebuild_physical_bones_cache();
}
PhysicalBone3D *PhysicalBoneSimulator3D::get_physical_bone(int p_bone) {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr);
return bones[p_bone].physical_bone;
}
PhysicalBone3D *PhysicalBoneSimulator3D::get_physical_bone_parent(int p_bone) {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr);
if (bones[p_bone].cache_parent_physical_bone) {
return bones[p_bone].cache_parent_physical_bone;
}
return _get_physical_bone_parent(p_bone);
}
PhysicalBone3D *PhysicalBoneSimulator3D::_get_physical_bone_parent(int p_bone) {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr);
const int parent_bone = bones[p_bone].parent;
if (parent_bone < 0) {
return nullptr;
}
PhysicalBone3D *pb = bones[parent_bone].physical_bone;
if (pb) {
return pb;
} else {
return get_physical_bone_parent(parent_bone);
}
}
void PhysicalBoneSimulator3D::_rebuild_physical_bones_cache() {
const int b_size = bones.size();
for (int i = 0; i < b_size; ++i) {
PhysicalBone3D *parent_pb = _get_physical_bone_parent(i);
if (parent_pb != bones[i].cache_parent_physical_bone) {
bones.write[i].cache_parent_physical_bone = parent_pb;
if (bones[i].physical_bone) {
bones[i].physical_bone->_on_bone_parent_changed();
}
}
}
}
#ifndef DISABLE_DEPRECATED
void _pb_stop_simulation_compat(Node *p_node) {
PhysicalBoneSimulator3D *ps = Object::cast_to<PhysicalBoneSimulator3D>(p_node);
if (ps) {
return; // Prevent conflict.
}
for (int i = p_node->get_child_count() - 1; i >= 0; --i) {
_pb_stop_simulation_compat(p_node->get_child(i));
}
PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node);
if (pb) {
pb->set_simulate_physics(false);
}
}
#endif // _DISABLE_DEPRECATED
void _pb_stop_simulation(Node *p_node) {
for (int i = p_node->get_child_count() - 1; i >= 0; --i) {
PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node->get_child(i));
if (!pb) {
continue;
}
_pb_stop_simulation(pb);
}
PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node);
if (pb) {
pb->set_simulate_physics(false);
}
}
void PhysicalBoneSimulator3D::physical_bones_stop_simulation() {
simulating = false;
_reset_physical_bones_state();
#ifndef DISABLE_DEPRECATED
if (is_compat) {
Skeleton3D *sk = get_skeleton();
if (sk) {
_pb_stop_simulation_compat(sk);
}
} else {
_pb_stop_simulation(this);
}
#else
_pb_stop_simulation(this);
#endif // _DISABLE_DEPRECATED
}
#ifndef DISABLE_DEPRECATED
void _pb_start_simulation_compat(const PhysicalBoneSimulator3D *p_simulator, Node *p_node, const Vector<int> &p_sim_bones) {
PhysicalBoneSimulator3D *ps = Object::cast_to<PhysicalBoneSimulator3D>(p_node);
if (ps) {
return; // Prevent conflict.
}
for (int i = p_node->get_child_count() - 1; i >= 0; --i) {
_pb_start_simulation_compat(p_simulator, p_node->get_child(i), p_sim_bones);
}
PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node);
if (pb) {
if (p_sim_bones.is_empty()) { // If no bones are specified, activate ragdoll on full body.
pb->set_simulate_physics(true);
} else {
for (int i = p_sim_bones.size() - 1; i >= 0; --i) {
if (p_sim_bones[i] == pb->get_bone_id() || p_simulator->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) {
pb->set_simulate_physics(true);
break;
}
}
}
}
}
#endif // _DISABLE_DEPRECATED
void _pb_start_simulation(const PhysicalBoneSimulator3D *p_simulator, Node *p_node, const Vector<int> &p_sim_bones) {
for (int i = p_node->get_child_count() - 1; i >= 0; --i) {
PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node->get_child(i));
if (!pb) {
continue;
}
_pb_start_simulation(p_simulator, pb, p_sim_bones);
}
PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node);
if (pb) {
if (p_sim_bones.is_empty()) { // If no bones are specified, activate ragdoll on full body.
pb->set_simulate_physics(true);
} else {
for (int i = p_sim_bones.size() - 1; i >= 0; --i) {
if (p_sim_bones[i] == pb->get_bone_id() || p_simulator->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) {
pb->set_simulate_physics(true);
break;
}
}
}
}
}
void PhysicalBoneSimulator3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) {
_pose_updated();
simulating = true;
_reset_physical_bones_state();
Vector<int> sim_bones;
if (p_bones.size() > 0) {
sim_bones.resize(p_bones.size());
int c = 0;
for (int i = sim_bones.size() - 1; i >= 0; --i) {
int bone_id = find_bone(p_bones[i]);
if (bone_id != -1) {
sim_bones.write[c++] = bone_id;
}
}
sim_bones.resize(c);
}
#ifndef DISABLE_DEPRECATED
if (is_compat) {
Skeleton3D *sk = get_skeleton();
if (sk) {
_pb_start_simulation_compat(this, sk, sim_bones);
}
} else {
_pb_start_simulation(this, this, sim_bones);
}
#else
_pb_start_simulation(this, this, sim_bones);
#endif // _DISABLE_DEPRECATED
}
void _physical_bones_add_remove_collision_exception(bool p_add, Node *p_node, RID p_exception) {
for (int i = p_node->get_child_count() - 1; i >= 0; --i) {
_physical_bones_add_remove_collision_exception(p_add, p_node->get_child(i), p_exception);
}
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_node);
if (co) {
if (p_add) {
PhysicsServer3D::get_singleton()->body_add_collision_exception(co->get_rid(), p_exception);
} else {
PhysicsServer3D::get_singleton()->body_remove_collision_exception(co->get_rid(), p_exception);
}
}
}
void PhysicalBoneSimulator3D::physical_bones_add_collision_exception(RID p_exception) {
_physical_bones_add_remove_collision_exception(true, this, p_exception);
}
void PhysicalBoneSimulator3D::physical_bones_remove_collision_exception(RID p_exception) {
_physical_bones_add_remove_collision_exception(false, this, p_exception);
}
Transform3D PhysicalBoneSimulator3D::get_bone_global_pose(int p_bone) const {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
return bones[p_bone].global_pose;
}
void PhysicalBoneSimulator3D::set_bone_global_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].global_pose = p_pose;
}
void PhysicalBoneSimulator3D::_process_modification(double p_delta) {
Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
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);
}
}
}
void PhysicalBoneSimulator3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_simulating_physics"), &PhysicalBoneSimulator3D::is_simulating_physics);
ClassDB::bind_method(D_METHOD("physical_bones_stop_simulation"), &PhysicalBoneSimulator3D::physical_bones_stop_simulation);
ClassDB::bind_method(D_METHOD("physical_bones_start_simulation", "bones"), &PhysicalBoneSimulator3D::physical_bones_start_simulation_on, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &PhysicalBoneSimulator3D::physical_bones_add_collision_exception);
ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &PhysicalBoneSimulator3D::physical_bones_remove_collision_exception);
}
PhysicalBoneSimulator3D::PhysicalBoneSimulator3D() {
}

View File

@@ -0,0 +1,105 @@
/**************************************************************************/
/* physical_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/skeleton_modifier_3d.h"
class PhysicalBone3D;
class PhysicalBoneSimulator3D : public SkeletonModifier3D {
GDCLASS(PhysicalBoneSimulator3D, SkeletonModifier3D);
bool simulating = false;
struct SimulatedBone {
int parent;
Vector<int> child_bones;
Transform3D global_pose;
PhysicalBone3D *physical_bone = nullptr;
PhysicalBone3D *cache_parent_physical_bone = nullptr;
SimulatedBone() {
parent = -1;
global_pose = Transform3D();
physical_bone = nullptr;
cache_parent_physical_bone = nullptr;
}
};
Vector<SimulatedBone> bones;
/// This is a slow API, so it's cached
PhysicalBone3D *_get_physical_bone_parent(int p_bone);
void _rebuild_physical_bones_cache();
void _reset_physical_bones_state();
protected:
static void _bind_methods();
virtual void _set_active(bool p_active) override;
void _bone_list_changed();
void _pose_updated();
void _bone_pose_updated(Skeleton3D *skeleton, int p_bone_id);
virtual void _process_modification(double p_delta) override;
virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) override;
public:
#ifndef DISABLE_DEPRECATED
bool is_compat = false;
#endif // _DISABLE_DEPRECATED
bool is_simulating_physics() const;
int find_bone(const String &p_name) const;
String get_bone_name(int p_bone) const;
int get_bone_count() const;
bool is_bone_parent_of(int p_bone_id, int p_parent_bone_id) const;
Transform3D get_bone_global_pose(int p_bone) const;
void set_bone_global_pose(int p_bone, const Transform3D &p_pose);
void bind_physical_bone_to_bone(int p_bone, PhysicalBone3D *p_physical_bone);
void unbind_physical_bone_from_bone(int p_bone);
PhysicalBone3D *get_physical_bone(int p_bone);
PhysicalBone3D *get_physical_bone_parent(int p_bone);
void physical_bones_stop_simulation();
void physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones);
void physical_bones_add_collision_exception(RID p_exception);
void physical_bones_remove_collision_exception(RID p_exception);
PhysicalBoneSimulator3D();
};

Some files were not shown because too many files have changed in this diff Show More