feat: godot-engine-source-4.3-stable

This commit is contained in:
Jan van der Weide 2025-01-17 16:36:38 +01:00
parent c59a7dcade
commit 7125d019b5
11149 changed files with 5070401 additions and 0 deletions

8
engine/scene/2d/SCsub Normal file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env python
Import("env")
env.add_source_files(env.scene_sources, "*.cpp")
# Chain load SCsubs
SConscript("physics/SCsub")

View file

@ -0,0 +1,669 @@
/**************************************************************************/
/* animated_sprite_2d.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 "animated_sprite_2d.h"
#include "scene/main/viewport.h"
#ifdef TOOLS_ENABLED
Dictionary AnimatedSprite2D::_edit_get_state() const {
Dictionary state = Node2D::_edit_get_state();
state["offset"] = offset;
return state;
}
void AnimatedSprite2D::_edit_set_state(const Dictionary &p_state) {
Node2D::_edit_set_state(p_state);
set_offset(p_state["offset"]);
}
void AnimatedSprite2D::_edit_set_pivot(const Point2 &p_pivot) {
set_offset(get_offset() - p_pivot);
set_position(get_transform().xform(p_pivot));
}
Point2 AnimatedSprite2D::_edit_get_pivot() const {
return Vector2();
}
bool AnimatedSprite2D::_edit_use_pivot() const {
return true;
}
Rect2 AnimatedSprite2D::_edit_get_rect() const {
return _get_rect();
}
bool AnimatedSprite2D::_edit_use_rect() const {
if (frames.is_null() || !frames->has_animation(animation)) {
return false;
}
if (frame < 0 || frame >= frames->get_frame_count(animation)) {
return false;
}
Ref<Texture2D> t;
if (animation) {
t = frames->get_frame_texture(animation, frame);
}
return t.is_valid();
}
#endif
Rect2 AnimatedSprite2D::get_anchorable_rect() const {
return _get_rect();
}
Rect2 AnimatedSprite2D::_get_rect() const {
if (frames.is_null() || !frames->has_animation(animation)) {
return Rect2();
}
if (frame < 0 || frame >= frames->get_frame_count(animation)) {
return Rect2();
}
Ref<Texture2D> t;
if (animation) {
t = frames->get_frame_texture(animation, frame);
}
if (t.is_null()) {
return Rect2();
}
Size2 s = t->get_size();
Point2 ofs = offset;
if (centered) {
ofs -= s / 2;
}
if (s == Size2(0, 0)) {
s = Size2(1, 1);
}
return Rect2(ofs, s);
}
void AnimatedSprite2D::_validate_property(PropertyInfo &p_property) const {
if (!frames.is_valid()) {
return;
}
if (p_property.name == "animation") {
List<StringName> names;
frames->get_animation_list(&names);
names.sort_custom<StringName::AlphCompare>();
bool current_found = false;
bool is_first_element = true;
for (const StringName &E : names) {
if (!is_first_element) {
p_property.hint_string += ",";
} else {
is_first_element = false;
}
p_property.hint_string += String(E);
if (animation == E) {
current_found = true;
}
}
if (!current_found) {
if (p_property.hint_string.is_empty()) {
p_property.hint_string = String(animation);
} else {
p_property.hint_string = String(animation) + "," + p_property.hint_string;
}
}
return;
}
if (p_property.name == "frame") {
if (playing) {
p_property.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY;
return;
}
p_property.hint = PROPERTY_HINT_RANGE;
if (frames->has_animation(animation) && frames->get_frame_count(animation) > 0) {
p_property.hint_string = "0," + itos(frames->get_frame_count(animation) - 1) + ",1";
} else {
// Avoid an error, `hint_string` is required for `PROPERTY_HINT_RANGE`.
p_property.hint_string = "0,0,1";
}
p_property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
}
void AnimatedSprite2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
if (!Engine::get_singleton()->is_editor_hint() && !frames.is_null() && frames->has_animation(autoplay)) {
play(autoplay);
}
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (frames.is_null() || !frames->has_animation(animation)) {
return;
}
double remaining = get_process_delta_time();
int i = 0;
while (remaining) {
// Animation speed may be changed by animation_finished or frame_changed signals.
double speed = frames->get_animation_speed(animation) * speed_scale * custom_speed_scale * frame_speed_scale;
double abs_speed = Math::abs(speed);
if (speed == 0) {
return; // Do nothing.
}
// Frame count may be changed by animation_finished or frame_changed signals.
int fc = frames->get_frame_count(animation);
int last_frame = fc - 1;
if (!signbit(speed)) {
// Forwards.
if (frame_progress >= 1.0) {
if (frame >= last_frame) {
if (frames->get_animation_loop(animation)) {
frame = 0;
emit_signal("animation_looped");
} else {
frame = last_frame;
pause();
emit_signal(SceneStringName(animation_finished));
return;
}
} else {
frame++;
}
_calc_frame_speed_scale();
frame_progress = 0.0;
queue_redraw();
emit_signal(SceneStringName(frame_changed));
}
double to_process = MIN((1.0 - frame_progress) / abs_speed, remaining);
frame_progress += to_process * abs_speed;
remaining -= to_process;
} else {
// Backwards.
if (frame_progress <= 0) {
if (frame <= 0) {
if (frames->get_animation_loop(animation)) {
frame = last_frame;
emit_signal("animation_looped");
} else {
frame = 0;
pause();
emit_signal(SceneStringName(animation_finished));
return;
}
} else {
frame--;
}
_calc_frame_speed_scale();
frame_progress = 1.0;
queue_redraw();
emit_signal(SceneStringName(frame_changed));
}
double to_process = MIN(frame_progress / abs_speed, remaining);
frame_progress -= to_process * abs_speed;
remaining -= to_process;
}
i++;
if (i > fc) {
return; // Prevents freezing if to_process is each time much less than remaining.
}
}
} break;
case NOTIFICATION_DRAW: {
if (frames.is_null() || !frames->has_animation(animation)) {
return;
}
Ref<Texture2D> texture = frames->get_frame_texture(animation, frame);
if (texture.is_null()) {
return;
}
RID ci = get_canvas_item();
Size2 s = texture->get_size();
Point2 ofs = offset;
if (centered) {
ofs -= s / 2;
}
if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
ofs = (ofs + Point2(0.5, 0.5)).floor();
}
Rect2 dst_rect(ofs, s);
if (hflip) {
dst_rect.size.x = -dst_rect.size.x;
}
if (vflip) {
dst_rect.size.y = -dst_rect.size.y;
}
texture->draw_rect_region(ci, dst_rect, Rect2(Vector2(), texture->get_size()), Color(1, 1, 1), false);
} break;
}
}
void AnimatedSprite2D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) {
if (frames == p_frames) {
return;
}
if (frames.is_valid()) {
frames->disconnect(CoreStringName(changed), callable_mp(this, &AnimatedSprite2D::_res_changed));
}
stop();
frames = p_frames;
if (frames.is_valid()) {
frames->connect(CoreStringName(changed), callable_mp(this, &AnimatedSprite2D::_res_changed));
List<StringName> al;
frames->get_animation_list(&al);
if (al.size() == 0) {
set_animation(StringName());
autoplay = String();
} else {
if (!frames->has_animation(animation)) {
set_animation(al.front()->get());
}
if (!frames->has_animation(autoplay)) {
autoplay = String();
}
}
}
notify_property_list_changed();
queue_redraw();
update_configuration_warnings();
emit_signal("sprite_frames_changed");
}
Ref<SpriteFrames> AnimatedSprite2D::get_sprite_frames() const {
return frames;
}
void AnimatedSprite2D::set_frame(int p_frame) {
set_frame_and_progress(p_frame, signbit(get_playing_speed()) ? 1.0 : 0.0);
}
int AnimatedSprite2D::get_frame() const {
return frame;
}
void AnimatedSprite2D::set_frame_progress(real_t p_progress) {
frame_progress = p_progress;
}
real_t AnimatedSprite2D::get_frame_progress() const {
return frame_progress;
}
void AnimatedSprite2D::set_frame_and_progress(int p_frame, real_t p_progress) {
if (frames.is_null()) {
return;
}
bool has_animation = frames->has_animation(animation);
int end_frame = has_animation ? MAX(0, frames->get_frame_count(animation) - 1) : 0;
bool is_changed = frame != p_frame;
if (p_frame < 0) {
frame = 0;
} else if (has_animation && p_frame > end_frame) {
frame = end_frame;
} else {
frame = p_frame;
}
_calc_frame_speed_scale();
frame_progress = p_progress;
if (!is_changed) {
return; // No change, don't redraw.
}
queue_redraw();
emit_signal(SceneStringName(frame_changed));
}
void AnimatedSprite2D::set_speed_scale(float p_speed_scale) {
speed_scale = p_speed_scale;
}
float AnimatedSprite2D::get_speed_scale() const {
return speed_scale;
}
float AnimatedSprite2D::get_playing_speed() const {
if (!playing) {
return 0;
}
return speed_scale * custom_speed_scale;
}
void AnimatedSprite2D::set_centered(bool p_center) {
if (centered == p_center) {
return;
}
centered = p_center;
queue_redraw();
item_rect_changed();
}
bool AnimatedSprite2D::is_centered() const {
return centered;
}
void AnimatedSprite2D::set_offset(const Point2 &p_offset) {
if (offset == p_offset) {
return;
}
offset = p_offset;
queue_redraw();
item_rect_changed();
}
Point2 AnimatedSprite2D::get_offset() const {
return offset;
}
void AnimatedSprite2D::set_flip_h(bool p_flip) {
if (hflip == p_flip) {
return;
}
hflip = p_flip;
queue_redraw();
}
bool AnimatedSprite2D::is_flipped_h() const {
return hflip;
}
void AnimatedSprite2D::set_flip_v(bool p_flip) {
if (vflip == p_flip) {
return;
}
vflip = p_flip;
queue_redraw();
}
bool AnimatedSprite2D::is_flipped_v() const {
return vflip;
}
void AnimatedSprite2D::_res_changed() {
set_frame_and_progress(frame, frame_progress);
queue_redraw();
notify_property_list_changed();
}
bool AnimatedSprite2D::is_playing() const {
return playing;
}
void AnimatedSprite2D::set_autoplay(const String &p_name) {
if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) {
WARN_PRINT("Setting autoplay after the node has been added to the scene has no effect.");
}
autoplay = p_name;
}
String AnimatedSprite2D::get_autoplay() const {
return autoplay;
}
void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool p_from_end) {
StringName name = p_name;
if (name == StringName()) {
name = animation;
}
ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name));
ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name));
if (frames->get_frame_count(name) == 0) {
return;
}
playing = true;
custom_speed_scale = p_custom_scale;
if (name != animation) {
animation = name;
int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
if (p_from_end) {
set_frame_and_progress(end_frame, 1.0);
} else {
set_frame_and_progress(0, 0.0);
}
emit_signal(SceneStringName(animation_changed));
} else {
int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
bool is_backward = signbit(speed_scale * custom_speed_scale);
if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) {
set_frame_and_progress(end_frame, 1.0);
} else if (!p_from_end && !is_backward && frame == end_frame && frame_progress >= 1.0) {
set_frame_and_progress(0, 0.0);
}
}
set_process_internal(true);
notify_property_list_changed();
queue_redraw();
}
void AnimatedSprite2D::play_backwards(const StringName &p_name) {
play(p_name, -1, true);
}
void AnimatedSprite2D::_stop_internal(bool p_reset) {
playing = false;
if (p_reset) {
custom_speed_scale = 1.0;
set_frame_and_progress(0, 0.0);
}
notify_property_list_changed();
set_process_internal(false);
}
void AnimatedSprite2D::pause() {
_stop_internal(false);
}
void AnimatedSprite2D::stop() {
_stop_internal(true);
}
double AnimatedSprite2D::_get_frame_duration() {
if (frames.is_valid() && frames->has_animation(animation)) {
return frames->get_frame_duration(animation, frame);
}
return 1.0;
}
void AnimatedSprite2D::_calc_frame_speed_scale() {
frame_speed_scale = 1.0 / _get_frame_duration();
}
void AnimatedSprite2D::set_animation(const StringName &p_name) {
if (animation == p_name) {
return;
}
animation = p_name;
emit_signal(SceneStringName(animation_changed));
if (frames == nullptr) {
animation = StringName();
stop();
ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name));
}
int frame_count = frames->get_frame_count(animation);
if (animation == StringName() || frame_count == 0) {
stop();
return;
} else if (!frames->get_animation_names().has(animation)) {
animation = StringName();
stop();
ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name));
}
if (signbit(get_playing_speed())) {
set_frame_and_progress(frame_count - 1, 1.0);
} else {
set_frame_and_progress(0, 0.0);
}
notify_property_list_changed();
queue_redraw();
}
StringName AnimatedSprite2D::get_animation() const {
return animation;
}
PackedStringArray AnimatedSprite2D::get_configuration_warnings() const {
PackedStringArray warnings = Node2D::get_configuration_warnings();
if (frames.is_null()) {
warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Sprite Frames\" property in order for AnimatedSprite2D to display frames."));
}
return warnings;
}
#ifdef TOOLS_ENABLED
void AnimatedSprite2D::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
const String pf = p_function;
if (p_idx == 0 && frames.is_valid()) {
if (pf == "play" || pf == "play_backwards" || pf == "set_animation" || pf == "set_autoplay") {
List<StringName> al;
frames->get_animation_list(&al);
for (const StringName &name : al) {
r_options->push_back(String(name).quote());
}
}
}
Node2D::get_argument_options(p_function, p_idx, r_options);
}
#endif
#ifndef DISABLE_DEPRECATED
bool AnimatedSprite2D::_set(const StringName &p_name, const Variant &p_value) {
if ((p_name == SNAME("frames"))) {
set_sprite_frames(p_value);
return true;
}
return false;
}
#endif
void AnimatedSprite2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_sprite_frames", "sprite_frames"), &AnimatedSprite2D::set_sprite_frames);
ClassDB::bind_method(D_METHOD("get_sprite_frames"), &AnimatedSprite2D::get_sprite_frames);
ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimatedSprite2D::set_animation);
ClassDB::bind_method(D_METHOD("get_animation"), &AnimatedSprite2D::get_animation);
ClassDB::bind_method(D_METHOD("set_autoplay", "name"), &AnimatedSprite2D::set_autoplay);
ClassDB::bind_method(D_METHOD("get_autoplay"), &AnimatedSprite2D::get_autoplay);
ClassDB::bind_method(D_METHOD("is_playing"), &AnimatedSprite2D::is_playing);
ClassDB::bind_method(D_METHOD("play", "name", "custom_speed", "from_end"), &AnimatedSprite2D::play, DEFVAL(StringName()), DEFVAL(1.0), DEFVAL(false));
ClassDB::bind_method(D_METHOD("play_backwards", "name"), &AnimatedSprite2D::play_backwards, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("pause"), &AnimatedSprite2D::pause);
ClassDB::bind_method(D_METHOD("stop"), &AnimatedSprite2D::stop);
ClassDB::bind_method(D_METHOD("set_centered", "centered"), &AnimatedSprite2D::set_centered);
ClassDB::bind_method(D_METHOD("is_centered"), &AnimatedSprite2D::is_centered);
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &AnimatedSprite2D::set_offset);
ClassDB::bind_method(D_METHOD("get_offset"), &AnimatedSprite2D::get_offset);
ClassDB::bind_method(D_METHOD("set_flip_h", "flip_h"), &AnimatedSprite2D::set_flip_h);
ClassDB::bind_method(D_METHOD("is_flipped_h"), &AnimatedSprite2D::is_flipped_h);
ClassDB::bind_method(D_METHOD("set_flip_v", "flip_v"), &AnimatedSprite2D::set_flip_v);
ClassDB::bind_method(D_METHOD("is_flipped_v"), &AnimatedSprite2D::is_flipped_v);
ClassDB::bind_method(D_METHOD("set_frame", "frame"), &AnimatedSprite2D::set_frame);
ClassDB::bind_method(D_METHOD("get_frame"), &AnimatedSprite2D::get_frame);
ClassDB::bind_method(D_METHOD("set_frame_progress", "progress"), &AnimatedSprite2D::set_frame_progress);
ClassDB::bind_method(D_METHOD("get_frame_progress"), &AnimatedSprite2D::get_frame_progress);
ClassDB::bind_method(D_METHOD("set_frame_and_progress", "frame", "progress"), &AnimatedSprite2D::set_frame_and_progress);
ClassDB::bind_method(D_METHOD("set_speed_scale", "speed_scale"), &AnimatedSprite2D::set_speed_scale);
ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimatedSprite2D::get_speed_scale);
ClassDB::bind_method(D_METHOD("get_playing_speed"), &AnimatedSprite2D::get_playing_speed);
ADD_SIGNAL(MethodInfo("sprite_frames_changed"));
ADD_SIGNAL(MethodInfo("animation_changed"));
ADD_SIGNAL(MethodInfo("frame_changed"));
ADD_SIGNAL(MethodInfo("animation_looped"));
ADD_SIGNAL(MethodInfo("animation_finished"));
ADD_GROUP("Animation", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "sprite_frames", PROPERTY_HINT_RESOURCE_TYPE, "SpriteFrames"), "set_sprite_frames", "get_sprite_frames");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation", PROPERTY_HINT_ENUM, ""), "set_animation", "get_animation");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "autoplay", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_autoplay", "get_autoplay");
ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "frame_progress", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_frame_progress", "get_frame_progress");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale"), "set_speed_scale", "get_speed_scale");
ADD_GROUP("Offset", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "is_flipped_h");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "is_flipped_v");
}
AnimatedSprite2D::AnimatedSprite2D() {
}

View file

@ -0,0 +1,137 @@
/**************************************************************************/
/* animated_sprite_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef ANIMATED_SPRITE_2D_H
#define ANIMATED_SPRITE_2D_H
#include "scene/2d/node_2d.h"
#include "scene/resources/sprite_frames.h"
class AnimatedSprite2D : public Node2D {
GDCLASS(AnimatedSprite2D, Node2D);
Ref<SpriteFrames> frames;
String autoplay;
bool playing = false;
StringName animation = "default";
int frame = 0;
float speed_scale = 1.0;
float custom_speed_scale = 1.0;
bool centered = true;
Point2 offset;
real_t frame_speed_scale = 1.0;
real_t frame_progress = 0.0;
bool hflip = false;
bool vflip = false;
void _res_changed();
double _get_frame_duration();
void _calc_frame_speed_scale();
void _stop_internal(bool p_reset);
Rect2 _get_rect() const;
protected:
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
#endif
static void _bind_methods();
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
public:
#ifdef TOOLS_ENABLED
virtual Dictionary _edit_get_state() const override;
virtual void _edit_set_state(const Dictionary &p_state) override;
virtual void _edit_set_pivot(const Point2 &p_pivot) override;
virtual Point2 _edit_get_pivot() const override;
virtual bool _edit_use_pivot() const override;
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
#endif
virtual Rect2 get_anchorable_rect() const override;
void set_sprite_frames(const Ref<SpriteFrames> &p_frames);
Ref<SpriteFrames> get_sprite_frames() const;
void play(const StringName &p_name = StringName(), float p_custom_scale = 1.0, bool p_from_end = false);
void play_backwards(const StringName &p_name = StringName());
void pause();
void stop();
bool is_playing() const;
void set_animation(const StringName &p_name);
StringName get_animation() const;
void set_autoplay(const String &p_name);
String get_autoplay() const;
void set_frame(int p_frame);
int get_frame() const;
void set_frame_progress(real_t p_progress);
real_t get_frame_progress() const;
void set_frame_and_progress(int p_frame, real_t p_progress);
void set_speed_scale(float p_speed_scale);
float get_speed_scale() const;
float get_playing_speed() const;
void set_centered(bool p_center);
bool is_centered() const;
void set_offset(const Point2 &p_offset);
Point2 get_offset() const;
void set_flip_h(bool p_flip);
bool is_flipped_h() const;
void set_flip_v(bool p_flip);
bool is_flipped_v() const;
PackedStringArray get_configuration_warnings() const override;
#ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif
AnimatedSprite2D();
};
#endif // ANIMATED_SPRITE_2D_H

View file

@ -0,0 +1,116 @@
/**************************************************************************/
/* audio_listener_2d.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_2d.h"
bool AudioListener2D::_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 AudioListener2D::_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 AudioListener2D::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("current")));
}
void AudioListener2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (!is_part_of_edited_scene() && current) {
make_current();
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (!is_part_of_edited_scene()) {
if (is_current()) {
clear_current();
current = true; // Keep it true.
} else {
current = false;
}
}
} break;
}
}
void AudioListener2D::make_current() {
current = true;
if (!is_inside_tree()) {
return;
}
get_viewport()->_audio_listener_2d_set(this);
}
void AudioListener2D::clear_current() {
current = false;
if (!is_inside_tree()) {
return;
}
get_viewport()->_audio_listener_2d_remove(this);
}
bool AudioListener2D::is_current() const {
if (is_inside_tree() && !is_part_of_edited_scene()) {
return get_viewport()->get_audio_listener_2d() == this;
} else {
return current;
}
}
void AudioListener2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("make_current"), &AudioListener2D::make_current);
ClassDB::bind_method(D_METHOD("clear_current"), &AudioListener2D::clear_current);
ClassDB::bind_method(D_METHOD("is_current"), &AudioListener2D::is_current);
}
AudioListener2D::AudioListener2D() {
set_hide_clip_children(true);
}

View file

@ -0,0 +1,61 @@
/**************************************************************************/
/* audio_listener_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef AUDIO_LISTENER_2D_H
#define AUDIO_LISTENER_2D_H
#include "scene/2d/node_2d.h"
#include "scene/main/window.h"
class AudioListener2D : public Node2D {
GDCLASS(AudioListener2D, Node2D);
private:
bool current = false;
friend class Viewport;
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);
static void _bind_methods();
public:
void make_current();
void clear_current();
bool is_current() const;
AudioListener2D();
};
#endif // AUDIO_LISTENER_2D_H

View file

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

View file

@ -0,0 +1,440 @@
/**************************************************************************/
/* audio_stream_player_2d.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_2d.h"
#include "audio_stream_player_2d.compat.inc"
#include "core/config/project_settings.h"
#include "scene/2d/audio_listener_2d.h"
#include "scene/2d/physics/area_2d.h"
#include "scene/audio/audio_stream_player_internal.h"
#include "scene/main/viewport.h"
#include "scene/resources/world_2d.h"
#include "servers/audio/audio_stream.h"
#include "servers/audio_server.h"
void AudioStreamPlayer2D::_notification(int p_what) {
internal->notification(p_what);
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
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_INTERNAL_PHYSICS_PROCESS: {
// Update anything related to position first, if possible of course.
if (setplay.get() > 0 || (internal->active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) || force_update_panning) {
force_update_panning = false;
_update_panning();
}
if (setplayback.is_valid() && setplay.get() >= 0) {
internal->active.set();
AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), internal->pitch_scale);
setplayback.unref();
setplay.set(-1);
}
if (!internal->stream_playbacks.is_empty() && internal->active.is_set()) {
internal->process();
}
internal->ensure_playback_limit();
} break;
}
}
// Interacts with PhysicsServer2D, so can only be called during _physics_process.
StringName AudioStreamPlayer2D::_get_actual_bus() {
Vector2 global_pos = get_global_position();
//check if any area is diverting sound into a bus
Ref<World2D> world_2d = get_world_2d();
ERR_FAIL_COND_V(world_2d.is_null(), SceneStringName(Master));
PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space());
ERR_FAIL_NULL_V(space_state, SceneStringName(Master));
PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS];
PhysicsDirectSpaceState2D::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++) {
Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider);
if (!area2d) {
continue;
}
if (!area2d->is_overriding_audio_bus()) {
continue;
}
return area2d->get_audio_bus_name();
}
return internal->bus;
}
// Interacts with PhysicsServer2D, so can only be called during _physics_process
void AudioStreamPlayer2D::_update_panning() {
if (!internal->active.is_set() || internal->stream.is_null()) {
return;
}
Ref<World2D> world_2d = get_world_2d();
ERR_FAIL_COND(world_2d.is_null());
Vector2 global_pos = get_global_position();
HashSet<Viewport *> viewports = world_2d->get_viewports();
volume_vector.resize(4);
volume_vector.write[0] = AudioFrame(0, 0);
volume_vector.write[1] = AudioFrame(0, 0);
volume_vector.write[2] = AudioFrame(0, 0);
volume_vector.write[3] = AudioFrame(0, 0);
StringName actual_bus = _get_actual_bus();
for (Viewport *vp : viewports) {
if (!vp->is_audio_listener_2d()) {
continue;
}
//compute matrix to convert to screen
Vector2 screen_size = vp->get_visible_rect().size;
Vector2 listener_in_global;
Vector2 relative_to_listener;
//screen in global is used for attenuation
AudioListener2D *listener = vp->get_audio_listener_2d();
Transform2D full_canvas_transform = vp->get_global_canvas_transform() * vp->get_canvas_transform();
if (listener) {
listener_in_global = listener->get_global_position();
relative_to_listener = (global_pos - listener_in_global).rotated(-listener->get_global_rotation());
relative_to_listener *= full_canvas_transform.get_scale(); // Default listener scales with canvas size, do the same here.
} else {
listener_in_global = full_canvas_transform.affine_inverse().xform(screen_size * 0.5);
relative_to_listener = full_canvas_transform.xform(global_pos) - screen_size * 0.5;
}
float dist = global_pos.distance_to(listener_in_global); // Distance to listener, or screen if none.
if (dist > max_distance) {
continue; // Can't hear this sound in this viewport.
}
float multiplier = Math::pow(1.0f - dist / max_distance, attenuation);
multiplier *= Math::db_to_linear(internal->volume_db); // Also apply player volume!
float pan = relative_to_listener.x / screen_size.x;
// Don't let the panning effect extend (too far) beyond the screen.
pan = CLAMP(pan, -1, 1);
// Bake in a constant factor here to allow the project setting defaults for 2d and 3d to be normalized to 1.0.
pan *= panning_strength * cached_global_panning_strength * 0.5f;
pan = CLAMP(pan + 0.5, 0.0, 1.0);
float l = 1.0 - pan;
float r = pan;
const AudioFrame &prev_sample = volume_vector[0];
AudioFrame new_sample = AudioFrame(l, r) * multiplier;
volume_vector.write[0] = AudioFrame(MAX(prev_sample[0], new_sample[0]), MAX(prev_sample[1], new_sample[1]));
}
for (const Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
AudioServer::get_singleton()->set_playback_bus_exclusive(playback, actual_bus, volume_vector);
}
for (const Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
AudioServer::get_singleton()->set_playback_pitch_scale(playback, internal->pitch_scale);
if (playback->get_is_sample() && playback->get_sample_playback().is_valid()) {
Ref<AudioSamplePlayback> sample_playback = playback->get_sample_playback();
AudioServer::get_singleton()->update_sample_playback_pitch_scale(sample_playback, internal->pitch_scale);
}
}
last_mix_count = AudioServer::get_singleton()->get_mix_count();
}
void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) {
internal->set_stream(p_stream);
}
Ref<AudioStream> AudioStreamPlayer2D::get_stream() const {
return internal->stream;
}
void AudioStreamPlayer2D::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 AudioStreamPlayer2D::get_volume_db() const {
return internal->volume_db;
}
void AudioStreamPlayer2D::set_pitch_scale(float p_pitch_scale) {
internal->set_pitch_scale(p_pitch_scale);
}
float AudioStreamPlayer2D::get_pitch_scale() const {
return internal->pitch_scale;
}
void AudioStreamPlayer2D::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 AudioStreamPlayer2D::seek(float p_seconds) {
internal->seek(p_seconds);
}
void AudioStreamPlayer2D::stop() {
setplay.set(-1);
internal->stop();
}
bool AudioStreamPlayer2D::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 AudioStreamPlayer2D::get_playback_position() {
return internal->get_playback_position();
}
void AudioStreamPlayer2D::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 AudioStreamPlayer2D::get_bus() const {
return internal->get_bus();
}
void AudioStreamPlayer2D::set_autoplay(bool p_enable) {
internal->autoplay = p_enable;
}
bool AudioStreamPlayer2D::is_autoplay_enabled() const {
return internal->autoplay;
}
void AudioStreamPlayer2D::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
bool AudioStreamPlayer2D::_is_active() const {
return internal->is_active();
}
void AudioStreamPlayer2D::_validate_property(PropertyInfo &p_property) const {
internal->validate_property(p_property);
}
void AudioStreamPlayer2D::set_max_distance(float p_pixels) {
ERR_FAIL_COND(p_pixels <= 0.0);
max_distance = p_pixels;
}
float AudioStreamPlayer2D::get_max_distance() const {
return max_distance;
}
void AudioStreamPlayer2D::set_attenuation(float p_curve) {
attenuation = p_curve;
}
float AudioStreamPlayer2D::get_attenuation() const {
return attenuation;
}
void AudioStreamPlayer2D::set_area_mask(uint32_t p_mask) {
area_mask = p_mask;
}
uint32_t AudioStreamPlayer2D::get_area_mask() const {
return area_mask;
}
void AudioStreamPlayer2D::set_stream_paused(bool p_pause) {
internal->set_stream_paused(p_pause);
}
bool AudioStreamPlayer2D::get_stream_paused() const {
return internal->get_stream_paused();
}
bool AudioStreamPlayer2D::has_stream_playback() {
return internal->has_stream_playback();
}
Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() {
return internal->get_stream_playback();
}
void AudioStreamPlayer2D::set_max_polyphony(int p_max_polyphony) {
internal->set_max_polyphony(p_max_polyphony);
}
int AudioStreamPlayer2D::get_max_polyphony() const {
return internal->max_polyphony;
}
void AudioStreamPlayer2D::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 AudioStreamPlayer2D::get_panning_strength() const {
return panning_strength;
}
AudioServer::PlaybackType AudioStreamPlayer2D::get_playback_type() const {
return internal->get_playback_type();
}
void AudioStreamPlayer2D::set_playback_type(AudioServer::PlaybackType p_playback_type) {
internal->set_playback_type(p_playback_type);
}
bool AudioStreamPlayer2D::_set(const StringName &p_name, const Variant &p_value) {
return internal->set(p_name, p_value);
}
bool AudioStreamPlayer2D::_get(const StringName &p_name, Variant &r_ret) const {
return internal->get(p_name, r_ret);
}
void AudioStreamPlayer2D::_get_property_list(List<PropertyInfo> *p_list) const {
internal->get_property_list(p_list);
}
void AudioStreamPlayer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer2D::set_stream);
ClassDB::bind_method(D_METHOD("get_stream"), &AudioStreamPlayer2D::get_stream);
ClassDB::bind_method(D_METHOD("set_volume_db", "volume_db"), &AudioStreamPlayer2D::set_volume_db);
ClassDB::bind_method(D_METHOD("get_volume_db"), &AudioStreamPlayer2D::get_volume_db);
ClassDB::bind_method(D_METHOD("set_pitch_scale", "pitch_scale"), &AudioStreamPlayer2D::set_pitch_scale);
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioStreamPlayer2D::get_pitch_scale);
ClassDB::bind_method(D_METHOD("play", "from_position"), &AudioStreamPlayer2D::play, DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("seek", "to_position"), &AudioStreamPlayer2D::seek);
ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayer2D::stop);
ClassDB::bind_method(D_METHOD("is_playing"), &AudioStreamPlayer2D::is_playing);
ClassDB::bind_method(D_METHOD("get_playback_position"), &AudioStreamPlayer2D::get_playback_position);
ClassDB::bind_method(D_METHOD("set_bus", "bus"), &AudioStreamPlayer2D::set_bus);
ClassDB::bind_method(D_METHOD("get_bus"), &AudioStreamPlayer2D::get_bus);
ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer2D::set_autoplay);
ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer2D::is_autoplay_enabled);
ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer2D::_set_playing);
ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer2D::_is_active);
ClassDB::bind_method(D_METHOD("set_max_distance", "pixels"), &AudioStreamPlayer2D::set_max_distance);
ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer2D::get_max_distance);
ClassDB::bind_method(D_METHOD("set_attenuation", "curve"), &AudioStreamPlayer2D::set_attenuation);
ClassDB::bind_method(D_METHOD("get_attenuation"), &AudioStreamPlayer2D::get_attenuation);
ClassDB::bind_method(D_METHOD("set_area_mask", "mask"), &AudioStreamPlayer2D::set_area_mask);
ClassDB::bind_method(D_METHOD("get_area_mask"), &AudioStreamPlayer2D::get_area_mask);
ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer2D::set_stream_paused);
ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer2D::get_stream_paused);
ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer2D::set_max_polyphony);
ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer2D::get_max_polyphony);
ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer2D::set_panning_strength);
ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer2D::get_panning_strength);
ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer2D::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback);
ClassDB::bind_method(D_METHOD("set_playback_type", "playback_type"), &AudioStreamPlayer2D::set_playback_type);
ClassDB::bind_method(D_METHOD("get_playback_type"), &AudioStreamPlayer2D::get_playback_type);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "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, "1,4096,1,or_greater,exp,suffix:px"), "set_max_distance", "get_max_distance");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_attenuation", "get_attenuation");
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_2D_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_SIGNAL(MethodInfo("finished"));
}
AudioStreamPlayer2D::AudioStreamPlayer2D() {
internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer2D::play), true));
cached_global_panning_strength = GLOBAL_GET("audio/general/2d_panning_strength");
set_hide_clip_children(true);
}
AudioStreamPlayer2D::~AudioStreamPlayer2D() {
memdelete(internal);
}

View file

@ -0,0 +1,150 @@
/**************************************************************************/
/* audio_stream_player_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef AUDIO_STREAM_PLAYER_2D_H
#define AUDIO_STREAM_PLAYER_2D_H
#include "scene/2d/node_2d.h"
#include "servers/audio_server.h"
struct AudioFrame;
class AudioStream;
class AudioStreamPlayback;
class AudioStreamPlayerInternal;
class AudioStreamPlayer2D : public Node2D {
GDCLASS(AudioStreamPlayer2D, Node2D);
private:
enum {
MAX_OUTPUTS = 8,
MAX_INTERSECT_AREAS = 32
};
struct Output {
AudioFrame vol;
int bus_index = 0;
Viewport *viewport = nullptr; //pointer only used for reference to previous mix
};
AudioStreamPlayerInternal *internal = nullptr;
SafeNumeric<float> setplay{ -1.0 };
Ref<AudioStreamPlayback> setplayback;
Vector<AudioFrame> volume_vector;
uint64_t last_mix_count = -1;
bool force_update_panning = false;
AudioServer::PlaybackType playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT;
void _set_playing(bool p_enable);
bool _is_active() const;
StringName _get_actual_bus();
void _update_panning();
static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->force_update_panning = true; }
uint32_t area_mask = 1;
float max_distance = 2000.0;
float attenuation = 1.0;
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_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_autoplay(bool p_enable);
bool is_autoplay_enabled() const;
void set_max_distance(float p_pixels);
float get_max_distance() const;
void set_attenuation(float p_curve);
float get_attenuation() const;
void set_area_mask(uint32_t p_mask);
uint32_t get_area_mask() const;
void set_stream_paused(bool p_pause);
bool get_stream_paused() const;
void set_max_polyphony(int p_max_polyphony);
int get_max_polyphony() 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);
AudioStreamPlayer2D();
~AudioStreamPlayer2D();
};
#endif // AUDIO_STREAM_PLAYER_2D_H

View file

@ -0,0 +1,109 @@
/**************************************************************************/
/* back_buffer_copy.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 "back_buffer_copy.h"
void BackBufferCopy::_update_copy_mode() {
switch (copy_mode) {
case COPY_MODE_DISABLED: {
RS::get_singleton()->canvas_item_set_copy_to_backbuffer(get_canvas_item(), false, Rect2());
} break;
case COPY_MODE_RECT: {
RS::get_singleton()->canvas_item_set_copy_to_backbuffer(get_canvas_item(), true, rect);
} break;
case COPY_MODE_VIEWPORT: {
RS::get_singleton()->canvas_item_set_copy_to_backbuffer(get_canvas_item(), true, Rect2());
} break;
}
}
#ifdef TOOLS_ENABLED
Rect2 BackBufferCopy::_edit_get_rect() const {
return rect;
}
bool BackBufferCopy::_edit_use_rect() const {
return true;
}
#endif
Rect2 BackBufferCopy::get_anchorable_rect() const {
return rect;
}
void BackBufferCopy::set_rect(const Rect2 &p_rect) {
rect = p_rect;
_update_copy_mode();
item_rect_changed();
}
Rect2 BackBufferCopy::get_rect() const {
return rect;
}
void BackBufferCopy::set_copy_mode(CopyMode p_mode) {
copy_mode = p_mode;
_update_copy_mode();
notify_property_list_changed();
}
BackBufferCopy::CopyMode BackBufferCopy::get_copy_mode() const {
return copy_mode;
}
void BackBufferCopy::_validate_property(PropertyInfo &p_property) const {
if (copy_mode != COPY_MODE_RECT && p_property.name == "rect") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
void BackBufferCopy::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_rect", "rect"), &BackBufferCopy::set_rect);
ClassDB::bind_method(D_METHOD("get_rect"), &BackBufferCopy::get_rect);
ClassDB::bind_method(D_METHOD("set_copy_mode", "copy_mode"), &BackBufferCopy::set_copy_mode);
ClassDB::bind_method(D_METHOD("get_copy_mode"), &BackBufferCopy::get_copy_mode);
ADD_PROPERTY(PropertyInfo(Variant::INT, "copy_mode", PROPERTY_HINT_ENUM, "Disabled,Rect,Viewport"), "set_copy_mode", "get_copy_mode");
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "rect", PROPERTY_HINT_NONE, "suffix:px"), "set_rect", "get_rect");
BIND_ENUM_CONSTANT(COPY_MODE_DISABLED);
BIND_ENUM_CONSTANT(COPY_MODE_RECT);
BIND_ENUM_CONSTANT(COPY_MODE_VIEWPORT);
}
BackBufferCopy::BackBufferCopy() {
_update_copy_mode();
set_hide_clip_children(true);
}
BackBufferCopy::~BackBufferCopy() {
}

View file

@ -0,0 +1,75 @@
/**************************************************************************/
/* back_buffer_copy.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef BACK_BUFFER_COPY_H
#define BACK_BUFFER_COPY_H
#include "scene/2d/node_2d.h"
class BackBufferCopy : public Node2D {
GDCLASS(BackBufferCopy, Node2D);
public:
enum CopyMode {
COPY_MODE_DISABLED,
COPY_MODE_RECT,
COPY_MODE_VIEWPORT
};
private:
Rect2 rect = Rect2(-100, -100, 200, 200);
CopyMode copy_mode = COPY_MODE_RECT;
void _update_copy_mode();
protected:
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
public:
#ifdef TOOLS_ENABLED
Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
#endif
void set_rect(const Rect2 &p_rect);
Rect2 get_rect() const;
Rect2 get_anchorable_rect() const override;
void set_copy_mode(CopyMode p_mode);
CopyMode get_copy_mode() const;
BackBufferCopy();
~BackBufferCopy();
};
VARIANT_ENUM_CAST(BackBufferCopy::CopyMode);
#endif // BACK_BUFFER_COPY_H

View file

@ -0,0 +1,960 @@
/**************************************************************************/
/* camera_2d.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_2d.h"
#include "core/config/project_settings.h"
#include "scene/main/window.h"
bool Camera2D::_is_editing_in_editor() const {
#ifdef TOOLS_ENABLED
return is_part_of_edited_scene();
#else
return false;
#endif // TOOLS_ENABLED
}
void Camera2D::_update_scroll() {
if (!is_inside_tree() || !viewport) {
return;
}
if (_is_editing_in_editor()) {
queue_redraw();
return;
}
if (is_current()) {
ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id));
Size2 screen_size = _get_camera_screen_size();
Transform2D xform;
if (is_physics_interpolated_and_enabled()) {
xform = _interpolation_data.xform_prev.interpolate_with(_interpolation_data.xform_curr, Engine::get_singleton()->get_physics_interpolation_fraction());
camera_screen_center = xform.affine_inverse().xform(0.5 * screen_size);
} else {
xform = get_camera_transform();
}
viewport->set_canvas_transform(xform);
Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5) : Point2());
Point2 adj_screen_pos = camera_screen_center - (screen_size * 0.5);
// TODO: Remove xform and screen_offset when ParallaxBackground/ParallaxLayer is removed.
get_tree()->call_group(group_name, SNAME("_camera_moved"), xform, screen_offset, adj_screen_pos);
}
}
#ifdef TOOLS_ENABLED
void Camera2D::_project_settings_changed() {
if (screen_drawing_enabled) {
queue_redraw();
}
}
#endif
void Camera2D::_update_process_callback() {
if (is_physics_interpolated_and_enabled()) {
set_process_internal(is_current());
set_physics_process_internal(is_current());
#ifdef TOOLS_ENABLED
if (process_callback == CAMERA2D_PROCESS_IDLE) {
WARN_PRINT_ONCE("Camera2D overridden to physics process mode due to use of physics interpolation.");
}
#endif
} else if (_is_editing_in_editor()) {
set_process_internal(false);
set_physics_process_internal(false);
} else {
if (process_callback == CAMERA2D_PROCESS_IDLE) {
set_process_internal(true);
set_physics_process_internal(false);
} else {
set_process_internal(false);
set_physics_process_internal(true);
}
}
}
void Camera2D::set_zoom(const Vector2 &p_zoom) {
// Setting zoom to zero causes 'affine_invert' issues
ERR_FAIL_COND_MSG(Math::is_zero_approx(p_zoom.x) || Math::is_zero_approx(p_zoom.y), "Zoom level must be different from 0 (can be negative).");
zoom = p_zoom;
zoom_scale = Vector2(1, 1) / zoom;
Point2 old_smoothed_camera_pos = smoothed_camera_pos;
_update_scroll();
smoothed_camera_pos = old_smoothed_camera_pos;
};
Vector2 Camera2D::get_zoom() const {
return zoom;
};
Transform2D Camera2D::get_camera_transform() {
if (!get_tree()) {
return Transform2D();
}
ERR_FAIL_COND_V(custom_viewport && !ObjectDB::get_instance(custom_viewport_id), Transform2D());
Size2 screen_size = _get_camera_screen_size();
Point2 new_camera_pos = get_global_position();
Point2 ret_camera_pos;
if (!first) {
if (anchor_mode == ANCHOR_MODE_DRAG_CENTER) {
if (drag_horizontal_enabled && !_is_editing_in_editor() && !drag_horizontal_offset_changed) {
camera_pos.x = MIN(camera_pos.x, (new_camera_pos.x + screen_size.x * 0.5 * zoom_scale.x * drag_margin[SIDE_LEFT]));
camera_pos.x = MAX(camera_pos.x, (new_camera_pos.x - screen_size.x * 0.5 * zoom_scale.x * drag_margin[SIDE_RIGHT]));
} else {
if (drag_horizontal_offset < 0) {
camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[SIDE_RIGHT] * drag_horizontal_offset;
} else {
camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[SIDE_LEFT] * drag_horizontal_offset;
}
drag_horizontal_offset_changed = false;
}
if (drag_vertical_enabled && !_is_editing_in_editor() && !drag_vertical_offset_changed) {
camera_pos.y = MIN(camera_pos.y, (new_camera_pos.y + screen_size.y * 0.5 * zoom_scale.y * drag_margin[SIDE_TOP]));
camera_pos.y = MAX(camera_pos.y, (new_camera_pos.y - screen_size.y * 0.5 * zoom_scale.y * drag_margin[SIDE_BOTTOM]));
} else {
if (drag_vertical_offset < 0) {
camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[SIDE_BOTTOM] * drag_vertical_offset;
} else {
camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[SIDE_TOP] * drag_vertical_offset;
}
drag_vertical_offset_changed = false;
}
} else if (anchor_mode == ANCHOR_MODE_FIXED_TOP_LEFT) {
camera_pos = new_camera_pos;
}
Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5 * zoom_scale) : Point2());
Rect2 screen_rect(-screen_offset + camera_pos, screen_size * zoom_scale);
if (limit_smoothing_enabled) {
if (screen_rect.position.x < limit[SIDE_LEFT]) {
camera_pos.x -= screen_rect.position.x - limit[SIDE_LEFT];
}
if (screen_rect.position.x + screen_rect.size.x > limit[SIDE_RIGHT]) {
camera_pos.x -= screen_rect.position.x + screen_rect.size.x - limit[SIDE_RIGHT];
}
if (screen_rect.position.y + screen_rect.size.y > limit[SIDE_BOTTOM]) {
camera_pos.y -= screen_rect.position.y + screen_rect.size.y - limit[SIDE_BOTTOM];
}
if (screen_rect.position.y < limit[SIDE_TOP]) {
camera_pos.y -= screen_rect.position.y - limit[SIDE_TOP];
}
}
// FIXME: There is a bug here, introduced before physics interpolation.
// Smoothing occurs rather confusingly during the call to get_camera_transform().
// It may be called MULTIPLE TIMES on certain frames,
// therefore smoothing is not currently applied only once per frame / tick,
// which will result in some haphazard results.
if (position_smoothing_enabled && !_is_editing_in_editor()) {
bool physics_process = (process_callback == CAMERA2D_PROCESS_PHYSICS) || is_physics_interpolated_and_enabled();
real_t delta = physics_process ? get_physics_process_delta_time() : get_process_delta_time();
real_t c = position_smoothing_speed * delta;
smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos;
ret_camera_pos = smoothed_camera_pos;
//camera_pos=camera_pos*(1.0-position_smoothing_speed)+new_camera_pos*position_smoothing_speed;
} else {
ret_camera_pos = smoothed_camera_pos = camera_pos;
}
} else {
ret_camera_pos = smoothed_camera_pos = camera_pos = new_camera_pos;
first = false;
}
Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5 * zoom_scale) : Point2());
if (!ignore_rotation) {
if (rotation_smoothing_enabled && !_is_editing_in_editor()) {
real_t step = rotation_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time());
camera_angle = Math::lerp_angle(camera_angle, get_global_rotation(), step);
} else {
camera_angle = get_global_rotation();
}
screen_offset = screen_offset.rotated(camera_angle);
}
Rect2 screen_rect(-screen_offset + ret_camera_pos, screen_size * zoom_scale);
if (!position_smoothing_enabled || !limit_smoothing_enabled) {
if (screen_rect.position.x < limit[SIDE_LEFT]) {
screen_rect.position.x = limit[SIDE_LEFT];
}
if (screen_rect.position.x + screen_rect.size.x > limit[SIDE_RIGHT]) {
screen_rect.position.x = limit[SIDE_RIGHT] - screen_rect.size.x;
}
if (screen_rect.position.y + screen_rect.size.y > limit[SIDE_BOTTOM]) {
screen_rect.position.y = limit[SIDE_BOTTOM] - screen_rect.size.y;
}
if (screen_rect.position.y < limit[SIDE_TOP]) {
screen_rect.position.y = limit[SIDE_TOP];
}
}
if (offset != Vector2()) {
screen_rect.position += offset;
}
Transform2D xform;
xform.scale_basis(zoom_scale);
if (!ignore_rotation) {
xform.set_rotation(camera_angle);
}
xform.set_origin(screen_rect.position);
camera_screen_center = xform.xform(0.5 * screen_size);
return xform.affine_inverse();
}
void Camera2D::_ensure_update_interpolation_data() {
// The "curr -> previous" update can either occur
// on NOTIFICATION_INTERNAL_PHYSICS_PROCESS, OR
// on NOTIFICATION_TRANSFORM_CHANGED,
// if NOTIFICATION_TRANSFORM_CHANGED takes place earlier than
// NOTIFICATION_INTERNAL_PHYSICS_PROCESS on a tick.
// This is to ensure that the data keeps flowing, but the new data
// doesn't overwrite before prev has been set.
// Keep the data flowing.
uint64_t tick = Engine::get_singleton()->get_physics_frames();
if (_interpolation_data.last_update_physics_tick != tick) {
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
_interpolation_data.last_update_physics_tick = tick;
}
}
void Camera2D::_notification(int p_what) {
switch (p_what) {
#ifdef TOOLS_ENABLED
case NOTIFICATION_READY: {
if (Engine::get_singleton()->is_editor_hint() && is_part_of_edited_scene()) {
ProjectSettings::get_singleton()->connect(SNAME("settings_changed"), callable_mp(this, &Camera2D::_project_settings_changed));
}
} break;
#endif
case NOTIFICATION_INTERNAL_PROCESS: {
_update_scroll();
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (is_physics_interpolated_and_enabled()) {
_ensure_update_interpolation_data();
_interpolation_data.xform_curr = get_camera_transform();
} else {
_update_scroll();
}
} break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
// Force the limits etc. to update.
_interpolation_data.xform_curr = get_camera_transform();
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
} break;
case NOTIFICATION_PAUSED: {
if (is_physics_interpolated_and_enabled()) {
_update_scroll();
}
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if ((!position_smoothing_enabled && !is_physics_interpolated_and_enabled()) || _is_editing_in_editor()) {
_update_scroll();
}
if (is_physics_interpolated_and_enabled()) {
_ensure_update_interpolation_data();
_interpolation_data.xform_curr = get_camera_transform();
}
} break;
case NOTIFICATION_ENTER_TREE: {
ERR_FAIL_COND(!is_inside_tree());
if (custom_viewport && ObjectDB::get_instance(custom_viewport_id)) {
viewport = custom_viewport;
} else {
viewport = get_viewport();
}
canvas = get_canvas();
RID vp = viewport->get_viewport_rid();
group_name = "__cameras_" + itos(vp.get_id());
canvas_group_name = "__cameras_c" + itos(canvas.get_id());
add_to_group(group_name);
add_to_group(canvas_group_name);
if (!_is_editing_in_editor() && enabled && !viewport->get_camera_2d()) {
make_current();
}
_update_process_callback();
first = true;
_update_scroll();
// Note that NOTIFICATION_RESET_PHYSICS_INTERPOLATION
// is automatically called before this because Camera2D is inherited
// from CanvasItem. However, the camera transform is not up to date
// until this point, so we do an extra manual reset.
if (is_physics_interpolated_and_enabled()) {
_interpolation_data.xform_curr = get_camera_transform();
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
}
} break;
case NOTIFICATION_EXIT_TREE: {
remove_from_group(group_name);
remove_from_group(canvas_group_name);
if (is_current()) {
clear_current();
}
viewport = nullptr;
just_exited_tree = true;
callable_mp(this, &Camera2D::_reset_just_exited).call_deferred();
} break;
#ifdef TOOLS_ENABLED
case NOTIFICATION_DRAW: {
if (!is_inside_tree() || !_is_editing_in_editor()) {
break;
}
if (screen_drawing_enabled) {
Color area_axis_color(1, 0.4, 1, 0.63);
real_t area_axis_width = -1;
if (is_current()) {
area_axis_width = 3;
}
Transform2D inv_camera_transform = get_camera_transform().affine_inverse();
Size2 screen_size = _get_camera_screen_size();
Vector2 screen_endpoints[4] = {
inv_camera_transform.xform(Vector2(0, 0)),
inv_camera_transform.xform(Vector2(screen_size.width, 0)),
inv_camera_transform.xform(Vector2(screen_size.width, screen_size.height)),
inv_camera_transform.xform(Vector2(0, screen_size.height))
};
Transform2D inv_transform = get_global_transform().affine_inverse(); // undo global space
for (int i = 0; i < 4; i++) {
draw_line(inv_transform.xform(screen_endpoints[i]), inv_transform.xform(screen_endpoints[(i + 1) % 4]), area_axis_color, area_axis_width);
}
}
if (limit_drawing_enabled) {
Color limit_drawing_color(1, 1, 0.25, 0.63);
real_t limit_drawing_width = -1;
if (is_current()) {
limit_drawing_width = 3;
}
Vector2 camera_origin = get_global_position();
Vector2 camera_scale = get_global_scale().abs();
Vector2 limit_points[4] = {
(Vector2(limit[SIDE_LEFT], limit[SIDE_TOP]) - camera_origin) / camera_scale,
(Vector2(limit[SIDE_RIGHT], limit[SIDE_TOP]) - camera_origin) / camera_scale,
(Vector2(limit[SIDE_RIGHT], limit[SIDE_BOTTOM]) - camera_origin) / camera_scale,
(Vector2(limit[SIDE_LEFT], limit[SIDE_BOTTOM]) - camera_origin) / camera_scale
};
for (int i = 0; i < 4; i++) {
draw_line(limit_points[i], limit_points[(i + 1) % 4], limit_drawing_color, limit_drawing_width);
}
}
if (margin_drawing_enabled) {
Color margin_drawing_color(0.25, 1, 1, 0.63);
real_t margin_drawing_width = -1;
if (is_current()) {
margin_drawing_width = 3;
}
Transform2D inv_camera_transform = get_camera_transform().affine_inverse();
Size2 screen_size = _get_camera_screen_size();
Vector2 margin_endpoints[4] = {
inv_camera_transform.xform(Vector2((screen_size.width / 2) - ((screen_size.width / 2) * drag_margin[SIDE_LEFT]), (screen_size.height / 2) - ((screen_size.height / 2) * drag_margin[SIDE_TOP]))),
inv_camera_transform.xform(Vector2((screen_size.width / 2) + ((screen_size.width / 2) * drag_margin[SIDE_RIGHT]), (screen_size.height / 2) - ((screen_size.height / 2) * drag_margin[SIDE_TOP]))),
inv_camera_transform.xform(Vector2((screen_size.width / 2) + ((screen_size.width / 2) * drag_margin[SIDE_RIGHT]), (screen_size.height / 2) + ((screen_size.height / 2) * drag_margin[SIDE_BOTTOM]))),
inv_camera_transform.xform(Vector2((screen_size.width / 2) - ((screen_size.width / 2) * drag_margin[SIDE_LEFT]), (screen_size.height / 2) + ((screen_size.height / 2) * drag_margin[SIDE_BOTTOM])))
};
Transform2D inv_transform = get_global_transform().affine_inverse(); // undo global space
for (int i = 0; i < 4; i++) {
draw_line(inv_transform.xform(margin_endpoints[i]), inv_transform.xform(margin_endpoints[(i + 1) % 4]), margin_drawing_color, margin_drawing_width);
}
}
} break;
#endif
}
}
void Camera2D::set_offset(const Vector2 &p_offset) {
offset = p_offset;
Point2 old_smoothed_camera_pos = smoothed_camera_pos;
_update_scroll();
smoothed_camera_pos = old_smoothed_camera_pos;
}
Vector2 Camera2D::get_offset() const {
return offset;
}
void Camera2D::set_anchor_mode(AnchorMode p_anchor_mode) {
anchor_mode = p_anchor_mode;
_update_scroll();
}
Camera2D::AnchorMode Camera2D::get_anchor_mode() const {
return anchor_mode;
}
void Camera2D::set_ignore_rotation(bool p_ignore) {
ignore_rotation = p_ignore;
Point2 old_smoothed_camera_pos = smoothed_camera_pos;
// Reset back to zero so it matches the camera rotation when ignore_rotation is enabled.
if (ignore_rotation) {
camera_angle = 0.0;
}
_update_scroll();
smoothed_camera_pos = old_smoothed_camera_pos;
}
bool Camera2D::is_ignoring_rotation() const {
return ignore_rotation;
}
void Camera2D::set_process_callback(Camera2DProcessCallback p_mode) {
if (process_callback == p_mode) {
return;
}
process_callback = p_mode;
_update_process_callback();
}
void Camera2D::set_enabled(bool p_enabled) {
enabled = p_enabled;
if (!is_inside_tree()) {
return;
}
if (enabled && !viewport->get_camera_2d()) {
make_current();
} else if (!enabled && is_current()) {
clear_current();
}
}
bool Camera2D::is_enabled() const {
return enabled;
}
Camera2D::Camera2DProcessCallback Camera2D::get_process_callback() const {
return process_callback;
}
void Camera2D::_make_current(Object *p_which) {
if (!is_inside_tree() || !viewport) {
return;
}
if (custom_viewport && !ObjectDB::get_instance(custom_viewport_id)) {
return;
}
queue_redraw();
bool was_current = viewport->get_camera_2d() == this;
bool is_current = p_which == this;
if (is_current) {
viewport->_camera_2d_set(this);
} else if (was_current) {
viewport->_camera_2d_set(nullptr);
}
if (is_current != was_current) {
_update_process_callback();
}
}
void Camera2D::_update_process_internal_for_smoothing() {
bool is_not_in_scene_or_editor = !(is_inside_tree() && _is_editing_in_editor());
bool is_any_smoothing_valid = position_smoothing_speed > 0 || rotation_smoothing_speed > 0;
bool enable = is_any_smoothing_valid && is_not_in_scene_or_editor;
set_process_internal(enable);
}
void Camera2D::make_current() {
ERR_FAIL_COND(!enabled || !is_inside_tree());
get_tree()->call_group(group_name, "_make_current", this);
if (just_exited_tree) {
// If camera exited the scene tree in the same frame, group call will skip it, so this needs to be called manually.
_make_current(this);
}
_update_scroll();
_update_process_callback();
}
void Camera2D::clear_current() {
ERR_FAIL_COND(!is_current());
if (!viewport || !viewport->is_inside_tree()) {
return;
}
if (!custom_viewport || ObjectDB::get_instance(custom_viewport_id)) {
viewport->assign_next_enabled_camera_2d(group_name);
}
_update_process_callback();
}
bool Camera2D::is_current() const {
if (!viewport) {
return false;
}
if (!custom_viewport || ObjectDB::get_instance(custom_viewport_id)) {
return viewport->get_camera_2d() == this;
}
return false;
}
void Camera2D::set_limit(Side p_side, int p_limit) {
ERR_FAIL_INDEX((int)p_side, 4);
limit[p_side] = p_limit;
Point2 old_smoothed_camera_pos = smoothed_camera_pos;
_update_scroll();
smoothed_camera_pos = old_smoothed_camera_pos;
}
int Camera2D::get_limit(Side p_side) const {
ERR_FAIL_INDEX_V((int)p_side, 4, 0);
return limit[p_side];
}
void Camera2D::set_limit_smoothing_enabled(bool enable) {
limit_smoothing_enabled = enable;
_update_scroll();
}
bool Camera2D::is_limit_smoothing_enabled() const {
return limit_smoothing_enabled;
}
void Camera2D::set_drag_margin(Side p_side, real_t p_drag_margin) {
ERR_FAIL_INDEX((int)p_side, 4);
drag_margin[p_side] = p_drag_margin;
queue_redraw();
}
real_t Camera2D::get_drag_margin(Side p_side) const {
ERR_FAIL_INDEX_V((int)p_side, 4, 0);
return drag_margin[p_side];
}
Vector2 Camera2D::get_camera_position() const {
return camera_pos;
}
void Camera2D::force_update_scroll() {
_update_scroll();
}
void Camera2D::reset_smoothing() {
_update_scroll();
smoothed_camera_pos = camera_pos;
}
void Camera2D::align() {
ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id));
Size2 screen_size = _get_camera_screen_size();
Point2 current_camera_pos = get_global_position();
if (anchor_mode == ANCHOR_MODE_DRAG_CENTER) {
if (drag_horizontal_offset < 0) {
camera_pos.x = current_camera_pos.x + screen_size.x * 0.5 * drag_margin[SIDE_RIGHT] * drag_horizontal_offset;
} else {
camera_pos.x = current_camera_pos.x + screen_size.x * 0.5 * drag_margin[SIDE_LEFT] * drag_horizontal_offset;
}
if (drag_vertical_offset < 0) {
camera_pos.y = current_camera_pos.y + screen_size.y * 0.5 * drag_margin[SIDE_TOP] * drag_vertical_offset;
} else {
camera_pos.y = current_camera_pos.y + screen_size.y * 0.5 * drag_margin[SIDE_BOTTOM] * drag_vertical_offset;
}
} else if (anchor_mode == ANCHOR_MODE_FIXED_TOP_LEFT) {
camera_pos = current_camera_pos;
}
_update_scroll();
}
void Camera2D::set_position_smoothing_speed(real_t p_speed) {
position_smoothing_speed = MAX(0, p_speed);
_update_process_internal_for_smoothing();
}
real_t Camera2D::get_position_smoothing_speed() const {
return position_smoothing_speed;
}
void Camera2D::set_rotation_smoothing_speed(real_t p_speed) {
rotation_smoothing_speed = MAX(0, p_speed);
_update_process_internal_for_smoothing();
}
real_t Camera2D::get_rotation_smoothing_speed() const {
return rotation_smoothing_speed;
}
void Camera2D::set_rotation_smoothing_enabled(bool p_enabled) {
rotation_smoothing_enabled = p_enabled;
notify_property_list_changed();
}
bool Camera2D::is_rotation_smoothing_enabled() const {
return rotation_smoothing_enabled;
}
Point2 Camera2D::get_camera_screen_center() const {
return camera_screen_center;
}
Size2 Camera2D::_get_camera_screen_size() const {
if (_is_editing_in_editor()) {
return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));
}
return get_viewport_rect().size;
}
void Camera2D::set_drag_horizontal_enabled(bool p_enabled) {
drag_horizontal_enabled = p_enabled;
}
bool Camera2D::is_drag_horizontal_enabled() const {
return drag_horizontal_enabled;
}
void Camera2D::set_drag_vertical_enabled(bool p_enabled) {
drag_vertical_enabled = p_enabled;
}
bool Camera2D::is_drag_vertical_enabled() const {
return drag_vertical_enabled;
}
void Camera2D::set_drag_vertical_offset(real_t p_offset) {
drag_vertical_offset = p_offset;
drag_vertical_offset_changed = true;
Point2 old_smoothed_camera_pos = smoothed_camera_pos;
_update_scroll();
smoothed_camera_pos = old_smoothed_camera_pos;
}
real_t Camera2D::get_drag_vertical_offset() const {
return drag_vertical_offset;
}
void Camera2D::set_drag_horizontal_offset(real_t p_offset) {
drag_horizontal_offset = p_offset;
drag_horizontal_offset_changed = true;
Point2 old_smoothed_camera_pos = smoothed_camera_pos;
_update_scroll();
smoothed_camera_pos = old_smoothed_camera_pos;
}
real_t Camera2D::get_drag_horizontal_offset() const {
return drag_horizontal_offset;
}
void Camera2D::_set_old_smoothing(real_t p_enable) {
//compatibility
if (p_enable > 0) {
position_smoothing_enabled = true;
set_position_smoothing_speed(p_enable);
}
}
void Camera2D::set_position_smoothing_enabled(bool p_enabled) {
position_smoothing_enabled = p_enabled;
notify_property_list_changed();
}
bool Camera2D::is_position_smoothing_enabled() const {
return position_smoothing_enabled;
}
void Camera2D::set_custom_viewport(Node *p_viewport) {
ERR_FAIL_NULL(p_viewport);
if (is_inside_tree()) {
remove_from_group(group_name);
remove_from_group(canvas_group_name);
}
custom_viewport = Object::cast_to<Viewport>(p_viewport);
if (custom_viewport) {
custom_viewport_id = custom_viewport->get_instance_id();
} else {
custom_viewport_id = ObjectID();
}
if (is_inside_tree()) {
if (custom_viewport) {
viewport = custom_viewport;
} else {
viewport = get_viewport();
}
RID vp = viewport->get_viewport_rid();
group_name = "__cameras_" + itos(vp.get_id());
canvas_group_name = "__cameras_c" + itos(canvas.get_id());
add_to_group(group_name);
add_to_group(canvas_group_name);
}
}
Node *Camera2D::get_custom_viewport() const {
return custom_viewport;
}
void Camera2D::set_screen_drawing_enabled(bool enable) {
screen_drawing_enabled = enable;
#ifdef TOOLS_ENABLED
queue_redraw();
#endif
}
bool Camera2D::is_screen_drawing_enabled() const {
return screen_drawing_enabled;
}
void Camera2D::set_limit_drawing_enabled(bool enable) {
limit_drawing_enabled = enable;
#ifdef TOOLS_ENABLED
queue_redraw();
#endif
}
bool Camera2D::is_limit_drawing_enabled() const {
return limit_drawing_enabled;
}
void Camera2D::set_margin_drawing_enabled(bool enable) {
margin_drawing_enabled = enable;
#ifdef TOOLS_ENABLED
queue_redraw();
#endif
}
bool Camera2D::is_margin_drawing_enabled() const {
return margin_drawing_enabled;
}
void Camera2D::_validate_property(PropertyInfo &p_property) const {
if (!position_smoothing_enabled && p_property.name == "position_smoothing_speed") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
if (!rotation_smoothing_enabled && p_property.name == "rotation_smoothing_speed") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
void Camera2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &Camera2D::set_offset);
ClassDB::bind_method(D_METHOD("get_offset"), &Camera2D::get_offset);
ClassDB::bind_method(D_METHOD("set_anchor_mode", "anchor_mode"), &Camera2D::set_anchor_mode);
ClassDB::bind_method(D_METHOD("get_anchor_mode"), &Camera2D::get_anchor_mode);
ClassDB::bind_method(D_METHOD("set_ignore_rotation", "ignore"), &Camera2D::set_ignore_rotation);
ClassDB::bind_method(D_METHOD("is_ignoring_rotation"), &Camera2D::is_ignoring_rotation);
ClassDB::bind_method(D_METHOD("_update_scroll"), &Camera2D::_update_scroll);
ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &Camera2D::set_process_callback);
ClassDB::bind_method(D_METHOD("get_process_callback"), &Camera2D::get_process_callback);
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &Camera2D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &Camera2D::is_enabled);
ClassDB::bind_method(D_METHOD("make_current"), &Camera2D::make_current);
ClassDB::bind_method(D_METHOD("is_current"), &Camera2D::is_current);
ClassDB::bind_method(D_METHOD("_make_current"), &Camera2D::_make_current);
ClassDB::bind_method(D_METHOD("set_limit", "margin", "limit"), &Camera2D::set_limit);
ClassDB::bind_method(D_METHOD("get_limit", "margin"), &Camera2D::get_limit);
ClassDB::bind_method(D_METHOD("set_limit_smoothing_enabled", "limit_smoothing_enabled"), &Camera2D::set_limit_smoothing_enabled);
ClassDB::bind_method(D_METHOD("is_limit_smoothing_enabled"), &Camera2D::is_limit_smoothing_enabled);
ClassDB::bind_method(D_METHOD("set_drag_vertical_enabled", "enabled"), &Camera2D::set_drag_vertical_enabled);
ClassDB::bind_method(D_METHOD("is_drag_vertical_enabled"), &Camera2D::is_drag_vertical_enabled);
ClassDB::bind_method(D_METHOD("set_drag_horizontal_enabled", "enabled"), &Camera2D::set_drag_horizontal_enabled);
ClassDB::bind_method(D_METHOD("is_drag_horizontal_enabled"), &Camera2D::is_drag_horizontal_enabled);
ClassDB::bind_method(D_METHOD("set_drag_vertical_offset", "offset"), &Camera2D::set_drag_vertical_offset);
ClassDB::bind_method(D_METHOD("get_drag_vertical_offset"), &Camera2D::get_drag_vertical_offset);
ClassDB::bind_method(D_METHOD("set_drag_horizontal_offset", "offset"), &Camera2D::set_drag_horizontal_offset);
ClassDB::bind_method(D_METHOD("get_drag_horizontal_offset"), &Camera2D::get_drag_horizontal_offset);
ClassDB::bind_method(D_METHOD("set_drag_margin", "margin", "drag_margin"), &Camera2D::set_drag_margin);
ClassDB::bind_method(D_METHOD("get_drag_margin", "margin"), &Camera2D::get_drag_margin);
ClassDB::bind_method(D_METHOD("get_target_position"), &Camera2D::get_camera_position);
ClassDB::bind_method(D_METHOD("get_screen_center_position"), &Camera2D::get_camera_screen_center);
ClassDB::bind_method(D_METHOD("set_zoom", "zoom"), &Camera2D::set_zoom);
ClassDB::bind_method(D_METHOD("get_zoom"), &Camera2D::get_zoom);
ClassDB::bind_method(D_METHOD("set_custom_viewport", "viewport"), &Camera2D::set_custom_viewport);
ClassDB::bind_method(D_METHOD("get_custom_viewport"), &Camera2D::get_custom_viewport);
ClassDB::bind_method(D_METHOD("set_position_smoothing_speed", "position_smoothing_speed"), &Camera2D::set_position_smoothing_speed);
ClassDB::bind_method(D_METHOD("get_position_smoothing_speed"), &Camera2D::get_position_smoothing_speed);
ClassDB::bind_method(D_METHOD("set_position_smoothing_enabled", "position_smoothing_speed"), &Camera2D::set_position_smoothing_enabled);
ClassDB::bind_method(D_METHOD("is_position_smoothing_enabled"), &Camera2D::is_position_smoothing_enabled);
ClassDB::bind_method(D_METHOD("set_rotation_smoothing_enabled", "enabled"), &Camera2D::set_rotation_smoothing_enabled);
ClassDB::bind_method(D_METHOD("is_rotation_smoothing_enabled"), &Camera2D::is_rotation_smoothing_enabled);
ClassDB::bind_method(D_METHOD("set_rotation_smoothing_speed", "speed"), &Camera2D::set_rotation_smoothing_speed);
ClassDB::bind_method(D_METHOD("get_rotation_smoothing_speed"), &Camera2D::get_rotation_smoothing_speed);
ClassDB::bind_method(D_METHOD("force_update_scroll"), &Camera2D::force_update_scroll);
ClassDB::bind_method(D_METHOD("reset_smoothing"), &Camera2D::reset_smoothing);
ClassDB::bind_method(D_METHOD("align"), &Camera2D::align);
ClassDB::bind_method(D_METHOD("_set_old_smoothing", "follow_smoothing"), &Camera2D::_set_old_smoothing);
ClassDB::bind_method(D_METHOD("set_screen_drawing_enabled", "screen_drawing_enabled"), &Camera2D::set_screen_drawing_enabled);
ClassDB::bind_method(D_METHOD("is_screen_drawing_enabled"), &Camera2D::is_screen_drawing_enabled);
ClassDB::bind_method(D_METHOD("set_limit_drawing_enabled", "limit_drawing_enabled"), &Camera2D::set_limit_drawing_enabled);
ClassDB::bind_method(D_METHOD("is_limit_drawing_enabled"), &Camera2D::is_limit_drawing_enabled);
ClassDB::bind_method(D_METHOD("set_margin_drawing_enabled", "margin_drawing_enabled"), &Camera2D::set_margin_drawing_enabled);
ClassDB::bind_method(D_METHOD("is_margin_drawing_enabled"), &Camera2D::is_margin_drawing_enabled);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "anchor_mode", PROPERTY_HINT_ENUM, "Fixed Top Left,Drag Center"), "set_anchor_mode", "get_anchor_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_rotation"), "set_ignore_rotation", "is_ignoring_rotation");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "zoom", PROPERTY_HINT_LINK), "set_zoom", "get_zoom");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport", PROPERTY_USAGE_NONE), "set_custom_viewport", "get_custom_viewport");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_callback", "get_process_callback");
ADD_GROUP("Limit", "limit_");
ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_left", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_LEFT);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_top", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_TOP);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_right", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_RIGHT);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_bottom", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_BOTTOM);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "limit_smoothed"), "set_limit_smoothing_enabled", "is_limit_smoothing_enabled");
ADD_GROUP("Position Smoothing", "position_smoothing_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "position_smoothing_enabled"), "set_position_smoothing_enabled", "is_position_smoothing_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "position_smoothing_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_position_smoothing_speed", "get_position_smoothing_speed");
ADD_GROUP("Rotation Smoothing", "rotation_smoothing_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotation_smoothing_enabled"), "set_rotation_smoothing_enabled", "is_rotation_smoothing_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation_smoothing_speed"), "set_rotation_smoothing_speed", "get_rotation_smoothing_speed");
ADD_GROUP("Drag", "drag_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_horizontal_enabled"), "set_drag_horizontal_enabled", "is_drag_horizontal_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_vertical_enabled"), "set_drag_vertical_enabled", "is_drag_vertical_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "drag_horizontal_offset", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_drag_horizontal_offset", "get_drag_horizontal_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "drag_vertical_offset", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_drag_vertical_offset", "get_drag_vertical_offset");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_left_margin", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", SIDE_LEFT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_top_margin", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", SIDE_TOP);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_right_margin", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", SIDE_RIGHT);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_bottom_margin", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", SIDE_BOTTOM);
ADD_GROUP("Editor", "editor_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_draw_screen"), "set_screen_drawing_enabled", "is_screen_drawing_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_draw_limits"), "set_limit_drawing_enabled", "is_limit_drawing_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_draw_drag_margin"), "set_margin_drawing_enabled", "is_margin_drawing_enabled");
BIND_ENUM_CONSTANT(ANCHOR_MODE_FIXED_TOP_LEFT);
BIND_ENUM_CONSTANT(ANCHOR_MODE_DRAG_CENTER);
BIND_ENUM_CONSTANT(CAMERA2D_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(CAMERA2D_PROCESS_IDLE);
}
Camera2D::Camera2D() {
limit[SIDE_LEFT] = -10000000;
limit[SIDE_TOP] = -10000000;
limit[SIDE_RIGHT] = 10000000;
limit[SIDE_BOTTOM] = 10000000;
drag_margin[SIDE_LEFT] = 0.2;
drag_margin[SIDE_TOP] = 0.2;
drag_margin[SIDE_RIGHT] = 0.2;
drag_margin[SIDE_BOTTOM] = 0.2;
set_notify_transform(true);
set_hide_clip_children(true);
}

206
engine/scene/2d/camera_2d.h Normal file
View file

@ -0,0 +1,206 @@
/**************************************************************************/
/* camera_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CAMERA_2D_H
#define CAMERA_2D_H
#include "scene/2d/node_2d.h"
class Camera2D : public Node2D {
GDCLASS(Camera2D, Node2D);
public:
enum AnchorMode {
ANCHOR_MODE_FIXED_TOP_LEFT,
ANCHOR_MODE_DRAG_CENTER
};
enum Camera2DProcessCallback {
CAMERA2D_PROCESS_PHYSICS,
CAMERA2D_PROCESS_IDLE
};
protected:
Point2 camera_pos;
Point2 smoothed_camera_pos;
bool first = true;
bool just_exited_tree = false;
ObjectID custom_viewport_id; // to check validity
Viewport *custom_viewport = nullptr;
Viewport *viewport = nullptr;
StringName group_name;
StringName canvas_group_name;
RID canvas;
Vector2 offset;
Vector2 zoom = Vector2(1, 1);
Vector2 zoom_scale = Vector2(1, 1);
AnchorMode anchor_mode = ANCHOR_MODE_DRAG_CENTER;
bool ignore_rotation = true;
bool enabled = true;
real_t position_smoothing_speed = 5.0;
bool position_smoothing_enabled = false;
real_t camera_angle = 0.0;
real_t rotation_smoothing_speed = 5.0;
bool rotation_smoothing_enabled = false;
int limit[4];
bool limit_smoothing_enabled = false;
real_t drag_margin[4];
bool drag_horizontal_enabled = false;
bool drag_vertical_enabled = false;
real_t drag_horizontal_offset = 0.0;
real_t drag_vertical_offset = 0.0;
bool drag_horizontal_offset_changed = false;
bool drag_vertical_offset_changed = false;
Point2 camera_screen_center;
bool _is_editing_in_editor() const;
void _update_process_callback();
void _update_scroll();
#ifdef TOOLS_ENABLED
void _project_settings_changed();
#endif
void _make_current(Object *p_which);
void _reset_just_exited() { just_exited_tree = false; }
void _set_old_smoothing(real_t p_enable);
void _update_process_internal_for_smoothing();
bool screen_drawing_enabled = true;
bool limit_drawing_enabled = false;
bool margin_drawing_enabled = false;
Camera2DProcessCallback process_callback = CAMERA2D_PROCESS_IDLE;
struct InterpolationData {
Transform2D xform_curr;
Transform2D xform_prev;
uint32_t last_update_physics_tick = UINT32_MAX; // Ensure tick 0 is detected as a change.
} _interpolation_data;
void _ensure_update_interpolation_data();
Size2 _get_camera_screen_size() const;
protected:
virtual Transform2D get_camera_transform();
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
public:
void set_offset(const Vector2 &p_offset);
Vector2 get_offset() const;
void set_anchor_mode(AnchorMode p_anchor_mode);
AnchorMode get_anchor_mode() const;
void set_ignore_rotation(bool p_ignore);
bool is_ignoring_rotation() const;
void set_limit(Side p_side, int p_limit);
int get_limit(Side p_side) const;
void set_limit_smoothing_enabled(bool enable);
bool is_limit_smoothing_enabled() const;
void set_drag_horizontal_enabled(bool p_enabled);
bool is_drag_horizontal_enabled() const;
void set_drag_vertical_enabled(bool p_enabled);
bool is_drag_vertical_enabled() const;
void set_drag_margin(Side p_side, real_t p_drag_margin);
real_t get_drag_margin(Side p_side) const;
void set_drag_horizontal_offset(real_t p_offset);
real_t get_drag_horizontal_offset() const;
void set_drag_vertical_offset(real_t p_offset);
real_t get_drag_vertical_offset() const;
void set_position_smoothing_enabled(bool p_enabled);
bool is_position_smoothing_enabled() const;
void set_position_smoothing_speed(real_t p_speed);
real_t get_position_smoothing_speed() const;
void set_rotation_smoothing_speed(real_t p_speed);
real_t get_rotation_smoothing_speed() const;
void set_rotation_smoothing_enabled(bool p_enabled);
bool is_rotation_smoothing_enabled() const;
void set_process_callback(Camera2DProcessCallback p_mode);
Camera2DProcessCallback get_process_callback() const;
void set_enabled(bool p_enabled);
bool is_enabled() const;
void make_current();
void clear_current();
bool is_current() const;
void set_zoom(const Vector2 &p_zoom);
Vector2 get_zoom() const;
Point2 get_camera_screen_center() const;
void set_custom_viewport(Node *p_viewport);
Node *get_custom_viewport() const;
Vector2 get_camera_position() const;
void force_update_scroll();
void reset_smoothing();
void align();
void set_screen_drawing_enabled(bool enable);
bool is_screen_drawing_enabled() const;
void set_limit_drawing_enabled(bool enable);
bool is_limit_drawing_enabled() const;
void set_margin_drawing_enabled(bool enable);
bool is_margin_drawing_enabled() const;
Camera2D();
};
VARIANT_ENUM_CAST(Camera2D::AnchorMode);
VARIANT_ENUM_CAST(Camera2D::Camera2DProcessCallback);
#endif // CAMERA_2D_H

View file

@ -0,0 +1,88 @@
/**************************************************************************/
/* canvas_group.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 "canvas_group.h"
void CanvasGroup::set_fit_margin(real_t p_fit_margin) {
ERR_FAIL_COND(p_fit_margin < 0.0);
fit_margin = p_fit_margin;
RS::get_singleton()->canvas_item_set_canvas_group_mode(get_canvas_item(), RS::CANVAS_GROUP_MODE_TRANSPARENT, clear_margin, true, fit_margin, use_mipmaps);
queue_redraw();
}
real_t CanvasGroup::get_fit_margin() const {
return fit_margin;
}
void CanvasGroup::set_clear_margin(real_t p_clear_margin) {
ERR_FAIL_COND(p_clear_margin < 0.0);
clear_margin = p_clear_margin;
RS::get_singleton()->canvas_item_set_canvas_group_mode(get_canvas_item(), RS::CANVAS_GROUP_MODE_TRANSPARENT, clear_margin, true, fit_margin, use_mipmaps);
queue_redraw();
}
real_t CanvasGroup::get_clear_margin() const {
return clear_margin;
}
void CanvasGroup::set_use_mipmaps(bool p_use_mipmaps) {
use_mipmaps = p_use_mipmaps;
RS::get_singleton()->canvas_item_set_canvas_group_mode(get_canvas_item(), RS::CANVAS_GROUP_MODE_TRANSPARENT, clear_margin, true, fit_margin, use_mipmaps);
}
bool CanvasGroup::is_using_mipmaps() const {
return use_mipmaps;
}
void CanvasGroup::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_fit_margin", "fit_margin"), &CanvasGroup::set_fit_margin);
ClassDB::bind_method(D_METHOD("get_fit_margin"), &CanvasGroup::get_fit_margin);
ClassDB::bind_method(D_METHOD("set_clear_margin", "clear_margin"), &CanvasGroup::set_clear_margin);
ClassDB::bind_method(D_METHOD("get_clear_margin"), &CanvasGroup::get_clear_margin);
ClassDB::bind_method(D_METHOD("set_use_mipmaps", "use_mipmaps"), &CanvasGroup::set_use_mipmaps);
ClassDB::bind_method(D_METHOD("is_using_mipmaps"), &CanvasGroup::is_using_mipmaps);
ADD_GROUP("Tweaks", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fit_margin", PROPERTY_HINT_RANGE, "0,1024,1.0,or_greater,suffix:px"), "set_fit_margin", "get_fit_margin");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "clear_margin", PROPERTY_HINT_RANGE, "0,1024,1.0,or_greater,suffix:px"), "set_clear_margin", "get_clear_margin");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_mipmaps"), "set_use_mipmaps", "is_using_mipmaps");
}
CanvasGroup::CanvasGroup() {
set_fit_margin(10.0); //sets things
}
CanvasGroup::~CanvasGroup() {
RS::get_singleton()->canvas_item_set_canvas_group_mode(get_canvas_item(), RS::CANVAS_GROUP_MODE_DISABLED);
}

View file

@ -0,0 +1,59 @@
/**************************************************************************/
/* canvas_group.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CANVAS_GROUP_H
#define CANVAS_GROUP_H
#include "scene/2d/node_2d.h"
class CanvasGroup : public Node2D {
GDCLASS(CanvasGroup, Node2D)
real_t fit_margin = 10.0;
real_t clear_margin = 10.0;
bool use_mipmaps = false;
protected:
static void _bind_methods();
public:
void set_fit_margin(real_t p_fit_margin);
real_t get_fit_margin() const;
void set_clear_margin(real_t p_clear_margin);
real_t get_clear_margin() const;
void set_use_mipmaps(bool p_use_mipmaps);
bool is_using_mipmaps() const;
CanvasGroup();
~CanvasGroup();
};
#endif // CANVAS_GROUP_H

View file

@ -0,0 +1,135 @@
/**************************************************************************/
/* canvas_modulate.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 "canvas_modulate.h"
void CanvasModulate::_on_in_canvas_visibility_changed(bool p_new_visibility) {
RID canvas = get_canvas();
StringName group_name = "_canvas_modulate_" + itos(canvas.get_id());
ERR_FAIL_COND_MSG(p_new_visibility == is_in_group(group_name), vformat("CanvasModulate becoming %s in the canvas already %s in the modulate group. Buggy logic, please report.", p_new_visibility ? "visible" : "invisible", p_new_visibility ? "was" : "was not"));
if (p_new_visibility) {
bool has_active_canvas_modulate = get_tree()->has_group(group_name); // Group would be removed if empty; otherwise one CanvasModulate within must be active.
add_to_group(group_name);
if (!has_active_canvas_modulate) {
is_active = true;
RS::get_singleton()->canvas_set_modulate(canvas, color);
}
} else {
remove_from_group(group_name);
if (is_active) {
is_active = false;
CanvasModulate *new_active = Object::cast_to<CanvasModulate>(get_tree()->get_first_node_in_group(group_name));
if (new_active) {
new_active->is_active = true;
RS::get_singleton()->canvas_set_modulate(canvas, new_active->color);
} else {
RS::get_singleton()->canvas_set_modulate(canvas, Color(1, 1, 1, 1));
}
}
}
update_configuration_warnings();
}
void CanvasModulate::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_CANVAS: {
is_in_canvas = true;
bool visible_in_tree = is_visible_in_tree();
if (visible_in_tree) {
_on_in_canvas_visibility_changed(true);
}
was_visible_in_tree = visible_in_tree;
} break;
case NOTIFICATION_EXIT_CANVAS: {
is_in_canvas = false;
if (was_visible_in_tree) {
_on_in_canvas_visibility_changed(false);
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_in_canvas) {
return;
}
bool visible_in_tree = is_visible_in_tree();
if (visible_in_tree == was_visible_in_tree) {
return;
}
_on_in_canvas_visibility_changed(visible_in_tree);
was_visible_in_tree = visible_in_tree;
} break;
}
}
void CanvasModulate::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_color", "color"), &CanvasModulate::set_color);
ClassDB::bind_method(D_METHOD("get_color"), &CanvasModulate::get_color);
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
}
void CanvasModulate::set_color(const Color &p_color) {
color = p_color;
if (is_active) {
RS::get_singleton()->canvas_set_modulate(get_canvas(), color);
}
}
Color CanvasModulate::get_color() const {
return color;
}
PackedStringArray CanvasModulate::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (is_in_canvas && is_visible_in_tree()) {
List<Node *> nodes;
get_tree()->get_nodes_in_group("_canvas_modulate_" + itos(get_canvas().get_id()), &nodes);
if (nodes.size() > 1) {
warnings.push_back(RTR("Only one visible CanvasModulate is allowed per canvas.\nWhen there are more than one, only one of them will be active. Which one is undefined."));
}
}
return warnings;
}
CanvasModulate::CanvasModulate() {
}
CanvasModulate::~CanvasModulate() {
}

View file

@ -0,0 +1,63 @@
/**************************************************************************/
/* canvas_modulate.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CANVAS_MODULATE_H
#define CANVAS_MODULATE_H
#include "scene/2d/node_2d.h"
class CanvasModulate : public Node2D {
GDCLASS(CanvasModulate, Node2D);
Color color = Color(1, 1, 1, 1);
// CanvasModulate is in canvas-specific modulate group when both in canvas and visible in tree.
// Exactly one CanvasModulate in each such non-empty group is active.
bool is_in_canvas = false;
bool was_visible_in_tree = false; // Relevant only when in canvas.
bool is_active = false;
void _on_in_canvas_visibility_changed(bool p_new_visibility);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_color(const Color &p_color);
Color get_color() const;
PackedStringArray get_configuration_warnings() const override;
CanvasModulate();
~CanvasModulate();
};
#endif // CANVAS_MODULATE_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,300 @@
/**************************************************************************/
/* cpu_particles_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CPU_PARTICLES_2D_H
#define CPU_PARTICLES_2D_H
#include "scene/2d/node_2d.h"
class CPUParticles2D : public Node2D {
private:
GDCLASS(CPUParticles2D, Node2D);
public:
enum DrawOrder {
DRAW_ORDER_INDEX,
DRAW_ORDER_LIFETIME,
};
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, // Unused, but exposed for consistency with 3D.
PARTICLE_FLAG_DISABLE_Z, // Unused, but exposed for consistency with 3D.
PARTICLE_FLAG_MAX
};
enum EmissionShape {
EMISSION_SHAPE_POINT,
EMISSION_SHAPE_SPHERE,
EMISSION_SHAPE_SPHERE_SURFACE,
EMISSION_SHAPE_RECTANGLE,
EMISSION_SHAPE_POINTS,
EMISSION_SHAPE_DIRECTED_POINTS,
EMISSION_SHAPE_MAX
};
private:
bool emitting = false;
bool active = false;
struct Particle {
Transform2D transform;
Color color;
real_t custom[4] = {};
real_t rotation = 0.0;
Vector2 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 do_redraw = false;
RID mesh;
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;
Vector2 axis;
bool operator()(int p_a, int p_b) const {
return axis.dot(particles[p_a].transform[2]) < axis.dot(particles[p_b].transform[2]);
}
};
//
bool one_shot = false;
double lifetime = 1.0;
double pre_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;
bool local_coords = false;
int fixed_fps = 0;
bool fractional_delta = true;
Transform2D inv_emission_transform;
DrawOrder draw_order = DRAW_ORDER_INDEX;
Ref<Texture2D> texture;
////////
Vector2 direction = Vector2(1, 0);
real_t spread = 45.0;
real_t parameters_min[PARAM_MAX] = {};
real_t parameters_max[PARAM_MAX] = {};
Ref<Curve> curve_parameters[PARAM_MAX];
Color color;
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;
Vector2 emission_rect_extents = Vector2(1, 1);
Vector<Vector2> emission_points;
Vector<Vector2> emission_normals;
Vector<Color> emission_colors;
int emission_point_count = 0;
Ref<Curve> scale_curve_x;
Ref<Curve> scale_curve_y;
bool split_scale = false;
Vector2 gravity = Vector2(0, 980);
void _update_internal();
void _particles_process(double p_delta);
void _update_particle_data_buffer();
Mutex update_mutex;
void _update_render_thread();
void _update_mesh_texture();
void _set_do_redraw(bool p_do_redraw);
void _texture_changed();
protected:
static void _bind_methods();
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
public:
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_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;
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_texture(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture() const;
///////////////////
void set_direction(Vector2 p_direction);
Vector2 get_direction() const;
void set_spread(real_t p_spread);
real_t get_spread() 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_rect_extents(Vector2 p_extents);
void set_emission_points(const Vector<Vector2> &p_points);
void set_emission_normals(const Vector<Vector2> &p_normals);
void set_emission_colors(const Vector<Color> &p_colors);
void set_scale_curve_x(Ref<Curve> p_scale_curve);
void set_scale_curve_y(Ref<Curve> p_scale_curve);
void set_split_scale(bool p_split_scale);
EmissionShape get_emission_shape() const;
real_t get_emission_sphere_radius() const;
Vector2 get_emission_rect_extents() const;
Vector<Vector2> get_emission_points() const;
Vector<Vector2> get_emission_normals() const;
Vector<Color> get_emission_colors() const;
Ref<Curve> get_scale_curve_x() const;
Ref<Curve> get_scale_curve_y() const;
bool get_split_scale();
void set_gravity(const Vector2 &p_gravity);
Vector2 get_gravity() const;
PackedStringArray get_configuration_warnings() const override;
void restart();
void convert_from_particles(Node *p_particles);
CPUParticles2D();
~CPUParticles2D();
};
VARIANT_ENUM_CAST(CPUParticles2D::DrawOrder)
VARIANT_ENUM_CAST(CPUParticles2D::Parameter)
VARIANT_ENUM_CAST(CPUParticles2D::ParticleFlags)
VARIANT_ENUM_CAST(CPUParticles2D::EmissionShape)
#endif // CPU_PARTICLES_2D_H

View file

@ -0,0 +1,887 @@
/**************************************************************************/
/* gpu_particles_2d.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_2d.h"
#include "scene/2d/cpu_particles_2d.h"
#include "scene/resources/atlas_texture.h"
#include "scene/resources/curve_texture.h"
#include "scene/resources/gradient_texture.h"
#include "scene/resources/particle_process_material.h"
#ifdef TOOLS_ENABLED
#include "core/config/engine.h"
#endif
void GPUParticles2D::set_emitting(bool p_emitting) {
// Do not return even if `p_emitting == emitting` because `emitting` is just an approximation.
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);
}
}
emitting = p_emitting;
RS::get_singleton()->particles_set_emitting(particles, p_emitting);
}
void GPUParticles2D::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 GPUParticles2D::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 GPUParticles2D::set_one_shot(bool p_enable) {
one_shot = p_enable;
RS::get_singleton()->particles_set_one_shot(particles, one_shot);
if (is_emitting()) {
set_process_internal(true);
if (!one_shot) {
RenderingServer::get_singleton()->particles_restart(particles);
}
}
if (!one_shot) {
set_process_internal(false);
}
}
void GPUParticles2D::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 GPUParticles2D::set_explosiveness_ratio(real_t p_ratio) {
explosiveness_ratio = p_ratio;
RS::get_singleton()->particles_set_explosiveness_ratio(particles, explosiveness_ratio);
}
void GPUParticles2D::set_randomness_ratio(real_t p_ratio) {
randomness_ratio = p_ratio;
RS::get_singleton()->particles_set_randomness_ratio(particles, randomness_ratio);
}
void GPUParticles2D::set_visibility_rect(const Rect2 &p_visibility_rect) {
visibility_rect = p_visibility_rect;
AABB aabb;
aabb.position.x = p_visibility_rect.position.x;
aabb.position.y = p_visibility_rect.position.y;
aabb.size.x = p_visibility_rect.size.x;
aabb.size.y = p_visibility_rect.size.y;
RS::get_singleton()->particles_set_custom_aabb(particles, aabb);
queue_redraw();
}
void GPUParticles2D::set_use_local_coordinates(bool p_enable) {
local_coords = p_enable;
RS::get_singleton()->particles_set_use_local_coordinates(particles, local_coords);
set_notify_transform(!p_enable);
if (!p_enable && is_inside_tree()) {
_update_particle_emission_transform();
}
}
void GPUParticles2D::_update_particle_emission_transform() {
Transform2D xf2d = get_global_transform();
Transform3D xf;
xf.basis.set_column(0, Vector3(xf2d.columns[0].x, xf2d.columns[0].y, 0));
xf.basis.set_column(1, Vector3(xf2d.columns[1].x, xf2d.columns[1].y, 0));
xf.set_origin(Vector3(xf2d.get_origin().x, xf2d.get_origin().y, 0));
RS::get_singleton()->particles_set_emission_transform(particles, xf);
}
void GPUParticles2D::set_process_material(const Ref<Material> &p_material) {
process_material = p_material;
Ref<ParticleProcessMaterial> pm = p_material;
if (pm.is_valid() && !pm->get_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_DISABLE_Z) && pm->get_gravity() == Vector3(0, -9.8, 0)) {
// Likely a new (3D) material, modify it to match 2D space
pm->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_DISABLE_Z, true);
pm->set_gravity(Vector3(0, 98, 0));
}
RID material_rid;
if (process_material.is_valid()) {
material_rid = process_material->get_rid();
}
RS::get_singleton()->particles_set_process_material(particles, material_rid);
update_configuration_warnings();
}
void GPUParticles2D::set_trail_enabled(bool p_enabled) {
trail_enabled = p_enabled;
RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime);
queue_redraw();
update_configuration_warnings();
RS::get_singleton()->particles_set_transform_align(particles, p_enabled ? RS::PARTICLES_TRANSFORM_ALIGN_Y_TO_VELOCITY : RS::PARTICLES_TRANSFORM_ALIGN_DISABLED);
}
void GPUParticles2D::set_trail_lifetime(double p_seconds) {
ERR_FAIL_COND(p_seconds < 0.01);
trail_lifetime = p_seconds;
RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime);
queue_redraw();
}
void GPUParticles2D::set_trail_sections(int p_sections) {
ERR_FAIL_COND(p_sections < 2);
ERR_FAIL_COND(p_sections > 128);
trail_sections = p_sections;
queue_redraw();
}
void GPUParticles2D::set_trail_section_subdivisions(int p_subdivisions) {
ERR_FAIL_COND(p_subdivisions < 1);
ERR_FAIL_COND(p_subdivisions > 1024);
trail_section_subdivisions = p_subdivisions;
queue_redraw();
}
void GPUParticles2D::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);
}
#ifdef TOOLS_ENABLED
void GPUParticles2D::set_show_visibility_rect(bool p_show_visibility_rect) {
show_visibility_rect = p_show_visibility_rect;
queue_redraw();
}
#endif
bool GPUParticles2D::is_trail_enabled() const {
return trail_enabled;
}
double GPUParticles2D::get_trail_lifetime() const {
return trail_lifetime;
}
void GPUParticles2D::_update_collision_size() {
real_t csize = collision_base_size;
if (texture.is_valid()) {
csize *= (texture->get_width() + texture->get_height()) / 4.0; //half size since its a radius
}
RS::get_singleton()->particles_set_collision_base_size(particles, csize);
}
void GPUParticles2D::set_collision_base_size(real_t p_size) {
collision_base_size = p_size;
_update_collision_size();
}
real_t GPUParticles2D::get_collision_base_size() const {
return collision_base_size;
}
void GPUParticles2D::set_speed_scale(double p_scale) {
speed_scale = p_scale;
RS::get_singleton()->particles_set_speed_scale(particles, p_scale);
}
bool GPUParticles2D::is_emitting() const {
return emitting;
}
int GPUParticles2D::get_amount() const {
return amount;
}
double GPUParticles2D::get_lifetime() const {
return lifetime;
}
int GPUParticles2D::get_trail_sections() const {
return trail_sections;
}
int GPUParticles2D::get_trail_section_subdivisions() const {
return trail_section_subdivisions;
}
bool GPUParticles2D::get_one_shot() const {
return one_shot;
}
double GPUParticles2D::get_pre_process_time() const {
return pre_process_time;
}
real_t GPUParticles2D::get_explosiveness_ratio() const {
return explosiveness_ratio;
}
real_t GPUParticles2D::get_randomness_ratio() const {
return randomness_ratio;
}
Rect2 GPUParticles2D::get_visibility_rect() const {
return visibility_rect;
}
bool GPUParticles2D::get_use_local_coordinates() const {
return local_coords;
}
Ref<Material> GPUParticles2D::get_process_material() const {
return process_material;
}
double GPUParticles2D::get_speed_scale() const {
return speed_scale;
}
void GPUParticles2D::set_draw_order(DrawOrder p_order) {
draw_order = p_order;
RS::get_singleton()->particles_set_draw_order(particles, RS::ParticlesDrawOrder(p_order));
}
GPUParticles2D::DrawOrder GPUParticles2D::get_draw_order() const {
return draw_order;
}
void GPUParticles2D::set_fixed_fps(int p_count) {
fixed_fps = p_count;
RS::get_singleton()->particles_set_fixed_fps(particles, p_count);
}
int GPUParticles2D::get_fixed_fps() const {
return fixed_fps;
}
void GPUParticles2D::set_fractional_delta(bool p_enable) {
fractional_delta = p_enable;
RS::get_singleton()->particles_set_fractional_delta(particles, p_enable);
}
bool GPUParticles2D::get_fractional_delta() const {
return fractional_delta;
}
void GPUParticles2D::set_interpolate(bool p_enable) {
interpolate = p_enable;
RS::get_singleton()->particles_set_interpolate(particles, p_enable);
}
bool GPUParticles2D::get_interpolate() const {
return interpolate;
}
float GPUParticles2D::get_interp_to_end() const {
return interp_to_end_factor;
}
PackedStringArray GPUParticles2D::get_configuration_warnings() const {
PackedStringArray warnings = Node2D::get_configuration_warnings();
if (process_material.is_null()) {
warnings.push_back(RTR("A material to process the particles is not assigned, so no behavior is imprinted."));
} else {
CanvasItemMaterial *mat = Object::cast_to<CanvasItemMaterial>(get_material().ptr());
if (get_material().is_null() || (mat && !mat->get_particles_animation())) {
const ParticleProcessMaterial *process = Object::cast_to<ParticleProcessMaterial>(process_material.ptr());
if (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("Particles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled."));
}
}
}
if (trail_enabled && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("Particle trails are only available when using the Forward+ or Mobile rendering backends."));
}
if (sub_emitter != NodePath() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("Particle sub-emitters are not available when using the GL Compatibility rendering backend."));
}
return warnings;
}
Rect2 GPUParticles2D::capture_rect() const {
AABB aabb = RS::get_singleton()->particles_get_current_aabb(particles);
Rect2 r;
r.position.x = aabb.position.x;
r.position.y = aabb.position.y;
r.size.x = aabb.size.x;
r.size.y = aabb.size.y;
return r;
}
void GPUParticles2D::set_texture(const Ref<Texture2D> &p_texture) {
if (texture.is_valid()) {
texture->disconnect_changed(callable_mp(this, &GPUParticles2D::_texture_changed));
}
texture = p_texture;
if (texture.is_valid()) {
texture->connect_changed(callable_mp(this, &GPUParticles2D::_texture_changed));
}
_update_collision_size();
queue_redraw();
}
Ref<Texture2D> GPUParticles2D::get_texture() const {
return texture;
}
void GPUParticles2D::_validate_property(PropertyInfo &p_property) const {
}
void GPUParticles2D::emit_particle(const Transform2D &p_transform2d, const Vector2 &p_velocity2d, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) {
Transform3D emit_transform;
emit_transform.basis.set_column(0, Vector3(p_transform2d.columns[0].x, p_transform2d.columns[0].y, 0));
emit_transform.basis.set_column(1, Vector3(p_transform2d.columns[1].x, p_transform2d.columns[1].y, 0));
emit_transform.set_origin(Vector3(p_transform2d.get_origin().x, p_transform2d.get_origin().y, 0));
Vector3 velocity = Vector3(p_velocity2d.x, p_velocity2d.y, 0);
RS::get_singleton()->particles_emit(particles, emit_transform, velocity, p_color, p_custom, p_emit_flags);
}
void GPUParticles2D::_attach_sub_emitter() {
Node *n = get_node_or_null(sub_emitter);
if (n) {
GPUParticles2D *sen = Object::cast_to<GPUParticles2D>(n);
if (sen && sen != this) {
RS::get_singleton()->particles_set_subemitter(particles, sen->particles);
}
}
}
void GPUParticles2D::_texture_changed() {
// Changes to the texture need to trigger an update to make
// the editor redraw the sprite with the updated texture.
if (texture.is_valid()) {
queue_redraw();
}
}
void GPUParticles2D::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 GPUParticles2D::get_sub_emitter() const {
return sub_emitter;
}
void GPUParticles2D::set_amount_ratio(float p_ratio) {
amount_ratio = p_ratio;
RenderingServer::get_singleton()->particles_set_amount_ratio(particles, p_ratio);
}
float GPUParticles2D::get_amount_ratio() const {
return amount_ratio;
}
void GPUParticles2D::restart() {
RS::get_singleton()->particles_restart(particles);
RS::get_singleton()->particles_set_emitting(particles, true);
emitting = true;
active = true;
signal_canceled = false;
time = 0;
emission_time = lifetime;
active_time = lifetime * (2 - explosiveness_ratio);
if (one_shot) {
set_process_internal(true);
}
}
void GPUParticles2D::convert_from_particles(Node *p_particles) {
CPUParticles2D *cpu_particles = Object::cast_to<CPUParticles2D>(p_particles);
ERR_FAIL_NULL_MSG(cpu_particles, "Only CPUParticles2D nodes can be converted to GPUParticles2D.");
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_texture(cpu_particles->get_texture());
Ref<Material> mat = cpu_particles->get_material();
if (mat.is_valid()) {
set_material(mat);
}
Ref<ParticleProcessMaterial> proc_mat = memnew(ParticleProcessMaterial);
set_process_material(proc_mat);
Vector2 dir = cpu_particles->get_direction();
proc_mat->set_direction(Vector3(dir.x, dir.y, 0));
proc_mat->set_spread(cpu_particles->get_spread());
proc_mat->set_color(cpu_particles->get_color());
Ref<Gradient> color_grad = cpu_particles->get_color_ramp();
if (color_grad.is_valid()) {
Ref<GradientTexture1D> tex = memnew(GradientTexture1D);
tex->set_gradient(color_grad);
proc_mat->set_color_ramp(tex);
}
Ref<Gradient> color_init_grad = cpu_particles->get_color_initial_ramp();
if (color_init_grad.is_valid()) {
Ref<GradientTexture1D> tex = memnew(GradientTexture1D);
tex->set_gradient(color_init_grad);
proc_mat->set_color_initial_ramp(tex);
}
proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, cpu_particles->get_particle_flag(CPUParticles2D::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY));
proc_mat->set_emission_shape(ParticleProcessMaterial::EmissionShape(cpu_particles->get_emission_shape()));
proc_mat->set_emission_sphere_radius(cpu_particles->get_emission_sphere_radius());
Vector2 rect_extents = cpu_particles->get_emission_rect_extents();
proc_mat->set_emission_box_extents(Vector3(rect_extents.x, rect_extents.y, 0));
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());
proc_mat->set_param_texture(ParticleProcessMaterial::PARAM_SCALE, scale3D);
}
Vector2 gravity = cpu_particles->get_gravity();
proc_mat->set_gravity(Vector3(gravity.x, gravity.y, 0));
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(CPUParticles2D::m_param)); \
{ \
Ref<Curve> curve = cpu_particles->get_param_curve(CPUParticles2D::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(CPUParticles2D::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 GPUParticles2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
RID texture_rid;
Size2 size;
if (texture.is_valid()) {
texture_rid = texture->get_rid();
size = texture->get_size();
} else {
size = Size2(1, 1);
}
if (trail_enabled) {
RS::get_singleton()->mesh_clear(mesh);
PackedVector2Array points;
PackedVector2Array uvs;
PackedInt32Array bone_indices;
PackedFloat32Array bone_weights;
PackedInt32Array indices;
int total_segments = trail_sections * trail_section_subdivisions;
real_t depth = size.height * trail_sections;
for (int j = 0; j <= total_segments; j++) {
real_t v = j;
v /= total_segments;
real_t y = depth * v;
y = (depth * 0.5) - y;
int bone = j / trail_section_subdivisions;
real_t blend = 1.0 - real_t(j % trail_section_subdivisions) / real_t(trail_section_subdivisions);
real_t s = size.width;
points.push_back(Vector2(-s * 0.5, 0));
points.push_back(Vector2(+s * 0.5, 0));
uvs.push_back(Vector2(0, v));
uvs.push_back(Vector2(1, v));
for (int i = 0; i < 2; i++) {
bone_indices.push_back(bone);
bone_indices.push_back(MIN(trail_sections, bone + 1));
bone_indices.push_back(0);
bone_indices.push_back(0);
bone_weights.push_back(blend);
bone_weights.push_back(1.0 - blend);
bone_weights.push_back(0);
bone_weights.push_back(0);
}
if (j > 0) {
int base = j * 2 - 2;
indices.push_back(base + 0);
indices.push_back(base + 1);
indices.push_back(base + 2);
indices.push_back(base + 1);
indices.push_back(base + 3);
indices.push_back(base + 2);
}
}
Array arr;
arr.resize(RS::ARRAY_MAX);
arr[RS::ARRAY_VERTEX] = points;
arr[RS::ARRAY_TEX_UV] = uvs;
arr[RS::ARRAY_BONES] = bone_indices;
arr[RS::ARRAY_WEIGHTS] = bone_weights;
arr[RS::ARRAY_INDEX] = indices;
RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
Vector<Transform3D> xforms;
for (int i = 0; i <= trail_sections; i++) {
Transform3D xform;
/*
xform.origin.y = depth / 2.0 - size.height * real_t(i);
xform.origin.y = -xform.origin.y; //bind is an inverse transform, so negate y */
xforms.push_back(xform);
}
RS::get_singleton()->particles_set_trail_bind_poses(particles, xforms);
} else {
RS::get_singleton()->mesh_clear(mesh);
Vector<Vector2> points = {
Vector2(-size.x / 2.0, -size.y / 2.0),
Vector2(size.x / 2.0, -size.y / 2.0),
Vector2(size.x / 2.0, size.y / 2.0),
Vector2(-size.x / 2.0, size.y / 2.0)
};
Vector<Vector2> uvs;
AtlasTexture *atlas_texure = Object::cast_to<AtlasTexture>(*texture);
if (atlas_texure && atlas_texure->get_atlas().is_valid()) {
Rect2 region_rect = atlas_texure->get_region();
Size2 atlas_size = atlas_texure->get_atlas()->get_size();
uvs.push_back(Vector2(region_rect.position.x / atlas_size.x, region_rect.position.y / atlas_size.y));
uvs.push_back(Vector2((region_rect.position.x + region_rect.size.x) / atlas_size.x, region_rect.position.y / atlas_size.y));
uvs.push_back(Vector2((region_rect.position.x + region_rect.size.x) / atlas_size.x, (region_rect.position.y + region_rect.size.y) / atlas_size.y));
uvs.push_back(Vector2(region_rect.position.x / atlas_size.x, (region_rect.position.y + region_rect.size.y) / atlas_size.y));
} else {
uvs.push_back(Vector2(0, 0));
uvs.push_back(Vector2(1, 0));
uvs.push_back(Vector2(1, 1));
uvs.push_back(Vector2(0, 1));
}
Vector<int> indices = { 0, 1, 2, 0, 2, 3 };
Array arr;
arr.resize(RS::ARRAY_MAX);
arr[RS::ARRAY_VERTEX] = points;
arr[RS::ARRAY_TEX_UV] = uvs;
arr[RS::ARRAY_INDEX] = indices;
RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
RS::get_singleton()->particles_set_trail_bind_poses(particles, Vector<Transform3D>());
}
RS::get_singleton()->canvas_item_add_particles(get_canvas_item(), particles, texture_rid);
#ifdef TOOLS_ENABLED
if (show_visibility_rect) {
draw_rect(visibility_rect, Color(0, 0.7, 0.9, 0.4), false);
}
#endif
} break;
case NOTIFICATION_ENTER_TREE: {
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);
}
set_process_internal(true);
previous_position = get_global_position();
} break;
case NOTIFICATION_EXIT_TREE: {
RS::get_singleton()->particles_set_subemitter(particles, RID());
} break;
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_TRANSFORM_CHANGED: {
_update_particle_emission_transform();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
const Vector3 velocity = Vector3((get_global_position() - previous_position).x, (get_global_position() - previous_position).y, 0.0) /
get_process_delta_time();
if (velocity != previous_velocity) {
RS::get_singleton()->particles_set_emitter_velocity(particles, velocity);
previous_velocity = velocity;
}
previous_position = get_global_position();
if (one_shot) {
time += get_process_delta_time();
if (time > emission_time) {
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;
}
}
void GPUParticles2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emitting", "emitting"), &GPUParticles2D::set_emitting);
ClassDB::bind_method(D_METHOD("set_amount", "amount"), &GPUParticles2D::set_amount);
ClassDB::bind_method(D_METHOD("set_lifetime", "secs"), &GPUParticles2D::set_lifetime);
ClassDB::bind_method(D_METHOD("set_one_shot", "secs"), &GPUParticles2D::set_one_shot);
ClassDB::bind_method(D_METHOD("set_pre_process_time", "secs"), &GPUParticles2D::set_pre_process_time);
ClassDB::bind_method(D_METHOD("set_explosiveness_ratio", "ratio"), &GPUParticles2D::set_explosiveness_ratio);
ClassDB::bind_method(D_METHOD("set_randomness_ratio", "ratio"), &GPUParticles2D::set_randomness_ratio);
ClassDB::bind_method(D_METHOD("set_visibility_rect", "visibility_rect"), &GPUParticles2D::set_visibility_rect);
ClassDB::bind_method(D_METHOD("set_use_local_coordinates", "enable"), &GPUParticles2D::set_use_local_coordinates);
ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &GPUParticles2D::set_fixed_fps);
ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &GPUParticles2D::set_fractional_delta);
ClassDB::bind_method(D_METHOD("set_interpolate", "enable"), &GPUParticles2D::set_interpolate);
ClassDB::bind_method(D_METHOD("set_process_material", "material"), &GPUParticles2D::set_process_material);
ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &GPUParticles2D::set_speed_scale);
ClassDB::bind_method(D_METHOD("set_collision_base_size", "size"), &GPUParticles2D::set_collision_base_size);
ClassDB::bind_method(D_METHOD("set_interp_to_end", "interp"), &GPUParticles2D::set_interp_to_end);
ClassDB::bind_method(D_METHOD("is_emitting"), &GPUParticles2D::is_emitting);
ClassDB::bind_method(D_METHOD("get_amount"), &GPUParticles2D::get_amount);
ClassDB::bind_method(D_METHOD("get_lifetime"), &GPUParticles2D::get_lifetime);
ClassDB::bind_method(D_METHOD("get_one_shot"), &GPUParticles2D::get_one_shot);
ClassDB::bind_method(D_METHOD("get_pre_process_time"), &GPUParticles2D::get_pre_process_time);
ClassDB::bind_method(D_METHOD("get_explosiveness_ratio"), &GPUParticles2D::get_explosiveness_ratio);
ClassDB::bind_method(D_METHOD("get_randomness_ratio"), &GPUParticles2D::get_randomness_ratio);
ClassDB::bind_method(D_METHOD("get_visibility_rect"), &GPUParticles2D::get_visibility_rect);
ClassDB::bind_method(D_METHOD("get_use_local_coordinates"), &GPUParticles2D::get_use_local_coordinates);
ClassDB::bind_method(D_METHOD("get_fixed_fps"), &GPUParticles2D::get_fixed_fps);
ClassDB::bind_method(D_METHOD("get_fractional_delta"), &GPUParticles2D::get_fractional_delta);
ClassDB::bind_method(D_METHOD("get_interpolate"), &GPUParticles2D::get_interpolate);
ClassDB::bind_method(D_METHOD("get_process_material"), &GPUParticles2D::get_process_material);
ClassDB::bind_method(D_METHOD("get_speed_scale"), &GPUParticles2D::get_speed_scale);
ClassDB::bind_method(D_METHOD("get_collision_base_size"), &GPUParticles2D::get_collision_base_size);
ClassDB::bind_method(D_METHOD("get_interp_to_end"), &GPUParticles2D::get_interp_to_end);
ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &GPUParticles2D::set_draw_order);
ClassDB::bind_method(D_METHOD("get_draw_order"), &GPUParticles2D::get_draw_order);
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &GPUParticles2D::set_texture);
ClassDB::bind_method(D_METHOD("get_texture"), &GPUParticles2D::get_texture);
ClassDB::bind_method(D_METHOD("capture_rect"), &GPUParticles2D::capture_rect);
ClassDB::bind_method(D_METHOD("restart"), &GPUParticles2D::restart);
ClassDB::bind_method(D_METHOD("set_sub_emitter", "path"), &GPUParticles2D::set_sub_emitter);
ClassDB::bind_method(D_METHOD("get_sub_emitter"), &GPUParticles2D::get_sub_emitter);
ClassDB::bind_method(D_METHOD("emit_particle", "xform", "velocity", "color", "custom", "flags"), &GPUParticles2D::emit_particle);
ClassDB::bind_method(D_METHOD("set_trail_enabled", "enabled"), &GPUParticles2D::set_trail_enabled);
ClassDB::bind_method(D_METHOD("set_trail_lifetime", "secs"), &GPUParticles2D::set_trail_lifetime);
ClassDB::bind_method(D_METHOD("is_trail_enabled"), &GPUParticles2D::is_trail_enabled);
ClassDB::bind_method(D_METHOD("get_trail_lifetime"), &GPUParticles2D::get_trail_lifetime);
ClassDB::bind_method(D_METHOD("set_trail_sections", "sections"), &GPUParticles2D::set_trail_sections);
ClassDB::bind_method(D_METHOD("get_trail_sections"), &GPUParticles2D::get_trail_sections);
ClassDB::bind_method(D_METHOD("set_trail_section_subdivisions", "subdivisions"), &GPUParticles2D::set_trail_section_subdivisions);
ClassDB::bind_method(D_METHOD("get_trail_section_subdivisions"), &GPUParticles2D::get_trail_section_subdivisions);
ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &GPUParticles2D::convert_from_particles);
ClassDB::bind_method(D_METHOD("set_amount_ratio", "ratio"), &GPUParticles2D::set_amount_ratio);
ClassDB::bind_method(D_METHOD("get_amount_ratio"), &GPUParticles2D::get_amount_ratio);
ADD_SIGNAL(MethodInfo("finished"));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false.
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
ADD_PROPERTY(PropertyInfo(Variant::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, "GPUParticles2D"), "set_sub_emitter", "get_sub_emitter");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ParticleProcessMaterial,ShaderMaterial"), "set_process_material", "get_process_material");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
ADD_GROUP("Time", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,suffix:s"), "set_lifetime", "get_lifetime");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,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::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_PROPERTY(PropertyInfo(Variant::FLOAT, "interp_to_end", PROPERTY_HINT_RANGE, "0.00,1.0,0.001"), "set_interp_to_end", "get_interp_to_end");
ADD_GROUP("Collision", "collision_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_collision_base_size", "get_collision_base_size");
ADD_GROUP("Drawing", "");
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "visibility_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_visibility_rect", "get_visibility_rect");
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"), "set_draw_order", "get_draw_order");
ADD_GROUP("Trails", "trail_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "trail_enabled"), "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_PROPERTY(PropertyInfo(Variant::INT, "trail_sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_trail_sections", "get_trail_sections");
ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_section_subdivisions", PROPERTY_HINT_RANGE, "1,1024,1"), "set_trail_section_subdivisions", "get_trail_section_subdivisions");
BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX);
BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME);
BIND_ENUM_CONSTANT(DRAW_ORDER_REVERSE_LIFETIME);
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);
}
GPUParticles2D::GPUParticles2D() {
particles = RS::get_singleton()->particles_create();
RS::get_singleton()->particles_set_mode(particles, RS::PARTICLES_MODE_2D);
mesh = RS::get_singleton()->mesh_create();
RS::get_singleton()->particles_set_draw_passes(particles, 1);
RS::get_singleton()->particles_set_draw_pass_mesh(particles, 0, mesh);
one_shot = false; // Needed so that set_emitting doesn't access uninitialized values
set_emitting(true);
set_one_shot(false);
set_amount(8);
set_amount_ratio(1.0);
set_lifetime(1);
set_fixed_fps(0);
set_fractional_delta(true);
set_interpolate(true);
set_pre_process_time(0);
set_explosiveness_ratio(0);
set_randomness_ratio(0);
set_visibility_rect(Rect2(Vector2(-100, -100), Vector2(200, 200)));
set_use_local_coordinates(false);
set_draw_order(DRAW_ORDER_LIFETIME);
set_speed_scale(1);
set_fixed_fps(30);
set_collision_base_size(collision_base_size);
}
GPUParticles2D::~GPUParticles2D() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(particles);
RS::get_singleton()->free(mesh);
}

View file

@ -0,0 +1,190 @@
/**************************************************************************/
/* gpu_particles_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GPU_PARTICLES_2D_H
#define GPU_PARTICLES_2D_H
#include "scene/2d/node_2d.h"
class GPUParticles2D : public Node2D {
private:
GDCLASS(GPUParticles2D, Node2D);
public:
enum DrawOrder {
DRAW_ORDER_INDEX,
DRAW_ORDER_LIFETIME,
DRAW_ORDER_REVERSE_LIFETIME,
};
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;
Rect2 visibility_rect;
bool local_coords = false;
int fixed_fps = 0;
bool fractional_delta = false;
bool interpolate = true;
float interp_to_end_factor = 0;
Vector3 previous_velocity;
Vector2 previous_position;
#ifdef TOOLS_ENABLED
bool show_visibility_rect = false;
#endif
Ref<Material> process_material;
DrawOrder draw_order = DRAW_ORDER_LIFETIME;
Ref<Texture2D> texture;
void _update_particle_emission_transform();
NodePath sub_emitter;
real_t collision_base_size = 1.0;
bool trail_enabled = false;
double trail_lifetime = 0.3;
int trail_sections = 8;
int trail_section_subdivisions = 4;
double time = 0.0;
double emission_time = 0.0;
double active_time = 0.0;
RID mesh;
void _attach_sub_emitter();
void _texture_changed();
protected:
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
void _update_collision_size();
public:
void set_emitting(bool p_emitting);
void set_amount(int p_amount);
void set_lifetime(double p_lifetime);
void set_one_shot(bool p_enable);
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_rect(const Rect2 &p_visibility_rect);
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_trail_sections(int p_sections);
void set_trail_section_subdivisions(int p_subdivisions);
void set_interp_to_end(float p_interp);
#ifdef TOOLS_ENABLED
void set_show_visibility_rect(bool p_show_visibility_rect);
#endif
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;
Rect2 get_visibility_rect() 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;
int get_trail_sections() const;
int get_trail_section_subdivisions() const;
float get_interp_to_end() 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_texture(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture() const;
void set_amount_ratio(float p_ratio);
float get_amount_ratio() const;
PackedStringArray get_configuration_warnings() const override;
void set_sub_emitter(const NodePath &p_path);
NodePath get_sub_emitter() const;
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 Transform2D &p_transform, const Vector2 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags);
void restart();
Rect2 capture_rect() const;
void convert_from_particles(Node *p_particles);
GPUParticles2D();
~GPUParticles2D();
};
VARIANT_ENUM_CAST(GPUParticles2D::DrawOrder)
VARIANT_ENUM_CAST(GPUParticles2D::EmitFlags)
#endif // GPU_PARTICLES_2D_H

View file

@ -0,0 +1,498 @@
/**************************************************************************/
/* light_2d.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_2d.h"
void Light2D::owner_changed_notify() {
// For cases where owner changes _after_ entering tree (as example, editor editing).
_update_light_visibility();
}
void Light2D::_update_light_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()->canvas_light_set_enabled(canvas_light, enabled && is_visible_in_tree() && editor_ok);
}
void Light2D::set_enabled(bool p_enabled) {
enabled = p_enabled;
_update_light_visibility();
}
bool Light2D::is_enabled() const {
return enabled;
}
void Light2D::set_editor_only(bool p_editor_only) {
editor_only = p_editor_only;
_update_light_visibility();
}
bool Light2D::is_editor_only() const {
return editor_only;
}
void Light2D::set_color(const Color &p_color) {
color = p_color;
RS::get_singleton()->canvas_light_set_color(canvas_light, color);
}
Color Light2D::get_color() const {
return color;
}
void Light2D::set_height(real_t p_height) {
height = p_height;
RS::get_singleton()->canvas_light_set_height(canvas_light, height);
}
real_t Light2D::get_height() const {
return height;
}
void Light2D::set_energy(real_t p_energy) {
energy = p_energy;
RS::get_singleton()->canvas_light_set_energy(canvas_light, energy);
}
real_t Light2D::get_energy() const {
return energy;
}
void Light2D::set_z_range_min(int p_min_z) {
z_min = p_min_z;
RS::get_singleton()->canvas_light_set_z_range(canvas_light, z_min, z_max);
}
int Light2D::get_z_range_min() const {
return z_min;
}
void Light2D::set_z_range_max(int p_max_z) {
z_max = p_max_z;
RS::get_singleton()->canvas_light_set_z_range(canvas_light, z_min, z_max);
}
int Light2D::get_z_range_max() const {
return z_max;
}
void Light2D::set_layer_range_min(int p_min_layer) {
layer_min = p_min_layer;
RS::get_singleton()->canvas_light_set_layer_range(canvas_light, layer_min, layer_max);
}
int Light2D::get_layer_range_min() const {
return layer_min;
}
void Light2D::set_layer_range_max(int p_max_layer) {
layer_max = p_max_layer;
RS::get_singleton()->canvas_light_set_layer_range(canvas_light, layer_min, layer_max);
}
int Light2D::get_layer_range_max() const {
return layer_max;
}
void Light2D::set_item_cull_mask(int p_mask) {
item_mask = p_mask;
RS::get_singleton()->canvas_light_set_item_cull_mask(canvas_light, item_mask);
}
int Light2D::get_item_cull_mask() const {
return item_mask;
}
void Light2D::set_item_shadow_cull_mask(int p_mask) {
item_shadow_mask = p_mask;
RS::get_singleton()->canvas_light_set_item_shadow_cull_mask(canvas_light, item_shadow_mask);
}
int Light2D::get_item_shadow_cull_mask() const {
return item_shadow_mask;
}
void Light2D::set_shadow_enabled(bool p_enabled) {
shadow = p_enabled;
RS::get_singleton()->canvas_light_set_shadow_enabled(canvas_light, shadow);
notify_property_list_changed();
}
bool Light2D::is_shadow_enabled() const {
return shadow;
}
void Light2D::set_shadow_filter(ShadowFilter p_filter) {
ERR_FAIL_INDEX(p_filter, SHADOW_FILTER_MAX);
shadow_filter = p_filter;
RS::get_singleton()->canvas_light_set_shadow_filter(canvas_light, RS::CanvasLightShadowFilter(p_filter));
notify_property_list_changed();
}
Light2D::ShadowFilter Light2D::get_shadow_filter() const {
return shadow_filter;
}
void Light2D::set_shadow_color(const Color &p_shadow_color) {
shadow_color = p_shadow_color;
RS::get_singleton()->canvas_light_set_shadow_color(canvas_light, shadow_color);
}
Color Light2D::get_shadow_color() const {
return shadow_color;
}
void Light2D::set_blend_mode(BlendMode p_mode) {
blend_mode = p_mode;
RS::get_singleton()->canvas_light_set_blend_mode(_get_light(), RS::CanvasLightBlendMode(p_mode));
}
Light2D::BlendMode Light2D::get_blend_mode() const {
return blend_mode;
}
void Light2D::_physics_interpolated_changed() {
RenderingServer::get_singleton()->canvas_light_set_interpolated(canvas_light, is_physics_interpolated());
}
void Light2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_CANVAS: {
RS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, get_canvas());
_update_light_visibility();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
RS::get_singleton()->canvas_light_set_transform(canvas_light, get_global_transform());
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
_update_light_visibility();
} break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
if (is_visible_in_tree() && is_physics_interpolated()) {
// Explicitly make sure the transform is up to date in RenderingServer before
// resetting. This is necessary because NOTIFICATION_TRANSFORM_CHANGED
// is normally deferred, and a client change to transform will not always be sent
// before the reset, so we need to guarantee this.
RS::get_singleton()->canvas_light_set_transform(canvas_light, get_global_transform());
RS::get_singleton()->canvas_light_reset_physics_interpolation(canvas_light);
}
} break;
case NOTIFICATION_EXIT_CANVAS: {
RS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, RID());
_update_light_visibility();
} break;
}
}
void Light2D::set_shadow_smooth(real_t p_amount) {
shadow_smooth = p_amount;
RS::get_singleton()->canvas_light_set_shadow_smooth(canvas_light, shadow_smooth);
}
real_t Light2D::get_shadow_smooth() const {
return shadow_smooth;
}
void Light2D::_validate_property(PropertyInfo &p_property) const {
if (!shadow && (p_property.name == "shadow_color" || p_property.name == "shadow_filter" || p_property.name == "shadow_filter_smooth" || p_property.name == "shadow_item_cull_mask")) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
if (shadow && p_property.name == "shadow_filter_smooth" && shadow_filter == SHADOW_FILTER_NONE) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
void Light2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &Light2D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &Light2D::is_enabled);
ClassDB::bind_method(D_METHOD("set_editor_only", "editor_only"), &Light2D::set_editor_only);
ClassDB::bind_method(D_METHOD("is_editor_only"), &Light2D::is_editor_only);
ClassDB::bind_method(D_METHOD("set_color", "color"), &Light2D::set_color);
ClassDB::bind_method(D_METHOD("get_color"), &Light2D::get_color);
ClassDB::bind_method(D_METHOD("set_energy", "energy"), &Light2D::set_energy);
ClassDB::bind_method(D_METHOD("get_energy"), &Light2D::get_energy);
ClassDB::bind_method(D_METHOD("set_z_range_min", "z"), &Light2D::set_z_range_min);
ClassDB::bind_method(D_METHOD("get_z_range_min"), &Light2D::get_z_range_min);
ClassDB::bind_method(D_METHOD("set_z_range_max", "z"), &Light2D::set_z_range_max);
ClassDB::bind_method(D_METHOD("get_z_range_max"), &Light2D::get_z_range_max);
ClassDB::bind_method(D_METHOD("set_layer_range_min", "layer"), &Light2D::set_layer_range_min);
ClassDB::bind_method(D_METHOD("get_layer_range_min"), &Light2D::get_layer_range_min);
ClassDB::bind_method(D_METHOD("set_layer_range_max", "layer"), &Light2D::set_layer_range_max);
ClassDB::bind_method(D_METHOD("get_layer_range_max"), &Light2D::get_layer_range_max);
ClassDB::bind_method(D_METHOD("set_item_cull_mask", "item_cull_mask"), &Light2D::set_item_cull_mask);
ClassDB::bind_method(D_METHOD("get_item_cull_mask"), &Light2D::get_item_cull_mask);
ClassDB::bind_method(D_METHOD("set_item_shadow_cull_mask", "item_shadow_cull_mask"), &Light2D::set_item_shadow_cull_mask);
ClassDB::bind_method(D_METHOD("get_item_shadow_cull_mask"), &Light2D::get_item_shadow_cull_mask);
ClassDB::bind_method(D_METHOD("set_shadow_enabled", "enabled"), &Light2D::set_shadow_enabled);
ClassDB::bind_method(D_METHOD("is_shadow_enabled"), &Light2D::is_shadow_enabled);
ClassDB::bind_method(D_METHOD("set_shadow_smooth", "smooth"), &Light2D::set_shadow_smooth);
ClassDB::bind_method(D_METHOD("get_shadow_smooth"), &Light2D::get_shadow_smooth);
ClassDB::bind_method(D_METHOD("set_shadow_filter", "filter"), &Light2D::set_shadow_filter);
ClassDB::bind_method(D_METHOD("get_shadow_filter"), &Light2D::get_shadow_filter);
ClassDB::bind_method(D_METHOD("set_shadow_color", "shadow_color"), &Light2D::set_shadow_color);
ClassDB::bind_method(D_METHOD("get_shadow_color"), &Light2D::get_shadow_color);
ClassDB::bind_method(D_METHOD("set_blend_mode", "mode"), &Light2D::set_blend_mode);
ClassDB::bind_method(D_METHOD("get_blend_mode"), &Light2D::get_blend_mode);
ClassDB::bind_method(D_METHOD("set_height", "height"), &Light2D::set_height);
ClassDB::bind_method(D_METHOD("get_height"), &Light2D::get_height);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "is_editor_only");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_energy", "get_energy");
ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Add,Subtract,Mix"), "set_blend_mode", "get_blend_mode");
ADD_GROUP("Range", "range_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "range_z_min", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_range_min", "get_z_range_min");
ADD_PROPERTY(PropertyInfo(Variant::INT, "range_z_max", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_range_max", "get_z_range_max");
ADD_PROPERTY(PropertyInfo(Variant::INT, "range_layer_min", PROPERTY_HINT_RANGE, "-512,512,1"), "set_layer_range_min", "get_layer_range_min");
ADD_PROPERTY(PropertyInfo(Variant::INT, "range_layer_max", PROPERTY_HINT_RANGE, "-512,512,1"), "set_layer_range_max", "get_layer_range_max");
ADD_PROPERTY(PropertyInfo(Variant::INT, "range_item_cull_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_item_cull_mask", "get_item_cull_mask");
ADD_GROUP("Shadow", "shadow_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_enabled"), "set_shadow_enabled", "is_shadow_enabled");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "shadow_color"), "set_shadow_color", "get_shadow_color");
ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_filter", PROPERTY_HINT_ENUM, "None (Fast),PCF5 (Average),PCF13 (Slow)"), "set_shadow_filter", "get_shadow_filter");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "shadow_filter_smooth", PROPERTY_HINT_RANGE, "0,64,0.1"), "set_shadow_smooth", "get_shadow_smooth");
ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_item_cull_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_item_shadow_cull_mask", "get_item_shadow_cull_mask");
BIND_ENUM_CONSTANT(SHADOW_FILTER_NONE);
BIND_ENUM_CONSTANT(SHADOW_FILTER_PCF5);
BIND_ENUM_CONSTANT(SHADOW_FILTER_PCF13);
BIND_ENUM_CONSTANT(BLEND_MODE_ADD);
BIND_ENUM_CONSTANT(BLEND_MODE_SUB);
BIND_ENUM_CONSTANT(BLEND_MODE_MIX);
}
Light2D::Light2D() {
canvas_light = RenderingServer::get_singleton()->canvas_light_create();
set_notify_transform(true);
}
Light2D::~Light2D() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RenderingServer::get_singleton()->free(canvas_light);
}
//////////////////////////////
#ifdef TOOLS_ENABLED
Dictionary PointLight2D::_edit_get_state() const {
Dictionary state = Node2D::_edit_get_state();
state["offset"] = get_texture_offset();
return state;
}
void PointLight2D::_edit_set_state(const Dictionary &p_state) {
Node2D::_edit_set_state(p_state);
set_texture_offset(p_state["offset"]);
}
void PointLight2D::_edit_set_pivot(const Point2 &p_pivot) {
set_position(get_transform().xform(p_pivot));
set_texture_offset(get_texture_offset() - p_pivot);
}
Point2 PointLight2D::_edit_get_pivot() const {
return Vector2();
}
bool PointLight2D::_edit_use_pivot() const {
return true;
}
Rect2 PointLight2D::_edit_get_rect() const {
if (texture.is_null()) {
return Rect2();
}
Size2 s = texture->get_size() * _scale;
return Rect2(texture_offset - s / 2.0, s);
}
bool PointLight2D::_edit_use_rect() const {
return !texture.is_null();
}
#endif
Rect2 PointLight2D::get_anchorable_rect() const {
if (texture.is_null()) {
return Rect2();
}
Size2 s = texture->get_size() * _scale;
return Rect2(texture_offset - s / 2.0, s);
}
void PointLight2D::set_texture(const Ref<Texture2D> &p_texture) {
texture = p_texture;
if (texture.is_valid()) {
RS::get_singleton()->canvas_light_set_texture(_get_light(), texture->get_rid());
} else {
RS::get_singleton()->canvas_light_set_texture(_get_light(), RID());
}
update_configuration_warnings();
}
Ref<Texture2D> PointLight2D::get_texture() const {
return texture;
}
void PointLight2D::set_texture_offset(const Vector2 &p_offset) {
texture_offset = p_offset;
RS::get_singleton()->canvas_light_set_texture_offset(_get_light(), texture_offset);
item_rect_changed();
}
Vector2 PointLight2D::get_texture_offset() const {
return texture_offset;
}
PackedStringArray PointLight2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (!texture.is_valid()) {
warnings.push_back(RTR("A texture with the shape of the light must be supplied to the \"Texture\" property."));
}
return warnings;
}
void PointLight2D::set_texture_scale(real_t p_scale) {
_scale = p_scale;
// Avoid having 0 scale values, can lead to errors in physics and rendering.
if (_scale == 0) {
_scale = CMP_EPSILON;
}
RS::get_singleton()->canvas_light_set_texture_scale(_get_light(), _scale);
item_rect_changed();
}
real_t PointLight2D::get_texture_scale() const {
return _scale;
}
#ifndef DISABLE_DEPRECATED
bool PointLight2D::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "mode" && p_value.is_num()) { // Compatibility with Godot 3.x.
set_blend_mode((BlendMode)(int)p_value);
return true;
}
return false;
}
#endif // DISABLE_DEPRECATED
void PointLight2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &PointLight2D::set_texture);
ClassDB::bind_method(D_METHOD("get_texture"), &PointLight2D::get_texture);
ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &PointLight2D::set_texture_offset);
ClassDB::bind_method(D_METHOD("get_texture_offset"), &PointLight2D::get_texture_offset);
ClassDB::bind_method(D_METHOD("set_texture_scale", "texture_scale"), &PointLight2D::set_texture_scale);
ClassDB::bind_method(D_METHOD("get_texture_scale"), &PointLight2D::get_texture_scale);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_texture_offset", "get_texture_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texture_scale", PROPERTY_HINT_RANGE, "0.01,50,0.01"), "set_texture_scale", "get_texture_scale");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1024,1,or_greater,suffix:px"), "set_height", "get_height");
}
PointLight2D::PointLight2D() {
RS::get_singleton()->canvas_light_set_mode(_get_light(), RS::CANVAS_LIGHT_MODE_POINT);
set_hide_clip_children(true);
}
//////////
void DirectionalLight2D::set_max_distance(real_t p_distance) {
max_distance = p_distance;
RS::get_singleton()->canvas_light_set_directional_distance(_get_light(), max_distance);
}
real_t DirectionalLight2D::get_max_distance() const {
return max_distance;
}
void DirectionalLight2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_max_distance", "pixels"), &DirectionalLight2D::set_max_distance);
ClassDB::bind_method(D_METHOD("get_max_distance"), &DirectionalLight2D::get_max_distance);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_height", "get_height");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,16384.0,1.0,or_greater,suffix:px"), "set_max_distance", "get_max_distance");
}
DirectionalLight2D::DirectionalLight2D() {
RS::get_singleton()->canvas_light_set_mode(_get_light(), RS::CANVAS_LIGHT_MODE_DIRECTIONAL);
set_max_distance(max_distance); // Update RenderingServer.
set_hide_clip_children(true);
}

198
engine/scene/2d/light_2d.h Normal file
View file

@ -0,0 +1,198 @@
/**************************************************************************/
/* light_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef LIGHT_2D_H
#define LIGHT_2D_H
#include "scene/2d/node_2d.h"
class Light2D : public Node2D {
GDCLASS(Light2D, Node2D);
public:
enum ShadowFilter {
SHADOW_FILTER_NONE,
SHADOW_FILTER_PCF5,
SHADOW_FILTER_PCF13,
SHADOW_FILTER_MAX
};
enum BlendMode {
BLEND_MODE_ADD,
BLEND_MODE_SUB,
BLEND_MODE_MIX,
};
private:
RID canvas_light;
bool enabled = true;
bool editor_only = false;
bool shadow = false;
Color color = Color(1, 1, 1);
Color shadow_color = Color(0, 0, 0, 0);
real_t height = 0.0;
real_t energy = 1.0;
int z_min = -1024;
int z_max = 1024;
int layer_min = 0;
int layer_max = 0;
int item_mask = 1;
int item_shadow_mask = 1;
real_t shadow_smooth = 0.0;
Ref<Texture2D> texture;
Vector2 texture_offset;
ShadowFilter shadow_filter = SHADOW_FILTER_NONE;
BlendMode blend_mode = BLEND_MODE_ADD;
void _update_light_visibility();
virtual void owner_changed_notify() override;
virtual void _physics_interpolated_changed() override;
protected:
_FORCE_INLINE_ RID _get_light() const { return canvas_light; }
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
public:
void set_enabled(bool p_enabled);
bool is_enabled() const;
void set_editor_only(bool p_editor_only);
bool is_editor_only() const;
void set_color(const Color &p_color);
Color get_color() const;
void set_height(real_t p_height);
real_t get_height() const;
void set_energy(real_t p_energy);
real_t get_energy() const;
void set_z_range_min(int p_min_z);
int get_z_range_min() const;
void set_z_range_max(int p_max_z);
int get_z_range_max() const;
void set_layer_range_min(int p_min_layer);
int get_layer_range_min() const;
void set_layer_range_max(int p_max_layer);
int get_layer_range_max() const;
void set_item_cull_mask(int p_mask);
int get_item_cull_mask() const;
void set_item_shadow_cull_mask(int p_mask);
int get_item_shadow_cull_mask() const;
void set_shadow_enabled(bool p_enabled);
bool is_shadow_enabled() const;
void set_shadow_filter(ShadowFilter p_filter);
ShadowFilter get_shadow_filter() const;
void set_shadow_color(const Color &p_shadow_color);
Color get_shadow_color() const;
void set_shadow_smooth(real_t p_amount);
real_t get_shadow_smooth() const;
void set_blend_mode(BlendMode p_mode);
BlendMode get_blend_mode() const;
Light2D();
~Light2D();
};
VARIANT_ENUM_CAST(Light2D::ShadowFilter);
VARIANT_ENUM_CAST(Light2D::BlendMode);
class PointLight2D : public Light2D {
GDCLASS(PointLight2D, Light2D);
private:
real_t _scale = 1.0;
Ref<Texture2D> texture;
Vector2 texture_offset;
protected:
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
#endif // DISABLE_DEPRECATED
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Dictionary _edit_get_state() const override;
virtual void _edit_set_state(const Dictionary &p_state) override;
virtual void _edit_set_pivot(const Point2 &p_pivot) override;
virtual Point2 _edit_get_pivot() const override;
virtual bool _edit_use_pivot() const override;
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
#endif
virtual Rect2 get_anchorable_rect() const override;
void set_texture(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture() const;
void set_texture_offset(const Vector2 &p_offset);
Vector2 get_texture_offset() const;
void set_texture_scale(real_t p_scale);
real_t get_texture_scale() const;
PackedStringArray get_configuration_warnings() const override;
PointLight2D();
};
class DirectionalLight2D : public Light2D {
GDCLASS(DirectionalLight2D, Light2D);
real_t max_distance = 10000.0;
protected:
static void _bind_methods();
public:
void set_max_distance(real_t p_distance);
real_t get_max_distance() const;
DirectionalLight2D();
};
#endif // LIGHT_2D_H

View file

@ -0,0 +1,313 @@
/**************************************************************************/
/* light_occluder_2d.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_occluder_2d.h"
#include "core/config/engine.h"
#include "core/math/geometry_2d.h"
#define LINE_GRAB_WIDTH 8
#ifdef TOOLS_ENABLED
Rect2 OccluderPolygon2D::_edit_get_rect() const {
if (rect_cache_dirty) {
if (closed) {
const Vector2 *r = polygon.ptr();
item_rect = Rect2();
for (int i = 0; i < polygon.size(); i++) {
Vector2 pos = r[i];
if (i == 0) {
item_rect.position = pos;
} else {
item_rect.expand_to(pos);
}
}
rect_cache_dirty = false;
} else {
if (polygon.size() == 0) {
item_rect = Rect2();
} else {
Vector2 d = Vector2(LINE_GRAB_WIDTH, LINE_GRAB_WIDTH);
item_rect = Rect2(polygon[0] - d, 2 * d);
for (int i = 1; i < polygon.size(); i++) {
item_rect.expand_to(polygon[i] - d);
item_rect.expand_to(polygon[i] + d);
}
}
}
}
return item_rect;
}
bool OccluderPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
if (closed) {
return Geometry2D::is_point_in_polygon(p_point, Variant(polygon));
} else {
const real_t d = LINE_GRAB_WIDTH / 2 + p_tolerance;
const Vector2 *points = polygon.ptr();
for (int i = 0; i < polygon.size() - 1; i++) {
Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, &points[i]);
if (p.distance_to(p_point) <= d) {
return true;
}
}
return false;
}
}
#endif
void OccluderPolygon2D::set_polygon(const Vector<Vector2> &p_polygon) {
polygon = p_polygon;
rect_cache_dirty = true;
RS::get_singleton()->canvas_occluder_polygon_set_shape(occ_polygon, p_polygon, closed);
emit_changed();
}
Vector<Vector2> OccluderPolygon2D::get_polygon() const {
return polygon;
}
void OccluderPolygon2D::set_closed(bool p_closed) {
if (closed == p_closed) {
return;
}
closed = p_closed;
if (polygon.size()) {
RS::get_singleton()->canvas_occluder_polygon_set_shape(occ_polygon, polygon, closed);
}
emit_changed();
}
bool OccluderPolygon2D::is_closed() const {
return closed;
}
void OccluderPolygon2D::set_cull_mode(CullMode p_mode) {
cull = p_mode;
RS::get_singleton()->canvas_occluder_polygon_set_cull_mode(occ_polygon, RS::CanvasOccluderPolygonCullMode(p_mode));
}
OccluderPolygon2D::CullMode OccluderPolygon2D::get_cull_mode() const {
return cull;
}
RID OccluderPolygon2D::get_rid() const {
return occ_polygon;
}
void OccluderPolygon2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_closed", "closed"), &OccluderPolygon2D::set_closed);
ClassDB::bind_method(D_METHOD("is_closed"), &OccluderPolygon2D::is_closed);
ClassDB::bind_method(D_METHOD("set_cull_mode", "cull_mode"), &OccluderPolygon2D::set_cull_mode);
ClassDB::bind_method(D_METHOD("get_cull_mode"), &OccluderPolygon2D::get_cull_mode);
ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &OccluderPolygon2D::set_polygon);
ClassDB::bind_method(D_METHOD("get_polygon"), &OccluderPolygon2D::get_polygon);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "closed"), "set_closed", "is_closed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mode", PROPERTY_HINT_ENUM, "Disabled,ClockWise,CounterClockWise"), "set_cull_mode", "get_cull_mode");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");
BIND_ENUM_CONSTANT(CULL_DISABLED);
BIND_ENUM_CONSTANT(CULL_CLOCKWISE);
BIND_ENUM_CONSTANT(CULL_COUNTER_CLOCKWISE);
}
OccluderPolygon2D::OccluderPolygon2D() {
occ_polygon = RS::get_singleton()->canvas_occluder_polygon_create();
}
OccluderPolygon2D::~OccluderPolygon2D() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(occ_polygon);
}
void LightOccluder2D::_poly_changed() {
#ifdef DEBUG_ENABLED
queue_redraw();
#endif
}
void LightOccluder2D::_physics_interpolated_changed() {
RenderingServer::get_singleton()->canvas_light_occluder_set_interpolated(occluder, is_physics_interpolated());
}
void LightOccluder2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_CANVAS: {
RS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform());
RS::get_singleton()->canvas_light_occluder_set_enabled(occluder, is_visible_in_tree());
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform());
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
RS::get_singleton()->canvas_light_occluder_set_enabled(occluder, is_visible_in_tree());
} break;
case NOTIFICATION_DRAW: {
if (Engine::get_singleton()->is_editor_hint()) {
if (occluder_polygon.is_valid()) {
Vector<Vector2> poly = occluder_polygon->get_polygon();
if (poly.size()) {
if (occluder_polygon->is_closed()) {
Vector<Color> color;
color.push_back(Color(0, 0, 0, 0.6));
draw_polygon(Variant(poly), color);
} else {
int ps = poly.size();
const Vector2 *r = poly.ptr();
for (int i = 0; i < ps - 1; i++) {
draw_line(r[i], r[i + 1], Color(0, 0, 0, 0.6), 3);
}
}
}
}
}
} break;
case NOTIFICATION_EXIT_CANVAS: {
RS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, RID());
} break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
if (is_visible_in_tree() && is_physics_interpolated()) {
// Explicitly make sure the transform is up to date in RenderingServer before
// resetting. This is necessary because NOTIFICATION_TRANSFORM_CHANGED
// is normally deferred, and a client change to transform will not always be sent
// before the reset, so we need to guarantee this.
RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform());
RS::get_singleton()->canvas_light_occluder_reset_physics_interpolation(occluder);
}
} break;
}
}
#ifdef TOOLS_ENABLED
Rect2 LightOccluder2D::_edit_get_rect() const {
return occluder_polygon.is_valid() ? occluder_polygon->_edit_get_rect() : Rect2();
}
bool LightOccluder2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return occluder_polygon.is_valid() ? occluder_polygon->_edit_is_selected_on_click(p_point, p_tolerance) : false;
}
#endif
void LightOccluder2D::set_occluder_polygon(const Ref<OccluderPolygon2D> &p_polygon) {
#ifdef DEBUG_ENABLED
if (occluder_polygon.is_valid()) {
occluder_polygon->disconnect_changed(callable_mp(this, &LightOccluder2D::_poly_changed));
}
#endif
occluder_polygon = p_polygon;
if (occluder_polygon.is_valid()) {
RS::get_singleton()->canvas_light_occluder_set_polygon(occluder, occluder_polygon->get_rid());
} else {
RS::get_singleton()->canvas_light_occluder_set_polygon(occluder, RID());
}
#ifdef DEBUG_ENABLED
if (occluder_polygon.is_valid()) {
occluder_polygon->connect_changed(callable_mp(this, &LightOccluder2D::_poly_changed));
}
queue_redraw();
#endif
}
Ref<OccluderPolygon2D> LightOccluder2D::get_occluder_polygon() const {
return occluder_polygon;
}
void LightOccluder2D::set_occluder_light_mask(int p_mask) {
mask = p_mask;
RS::get_singleton()->canvas_light_occluder_set_light_mask(occluder, p_mask);
}
int LightOccluder2D::get_occluder_light_mask() const {
return mask;
}
PackedStringArray LightOccluder2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (!occluder_polygon.is_valid()) {
warnings.push_back(RTR("An occluder polygon must be set (or drawn) for this occluder to take effect."));
}
if (occluder_polygon.is_valid() && occluder_polygon->get_polygon().size() == 0) {
warnings.push_back(RTR("The occluder polygon for this occluder is empty. Please draw a polygon."));
}
return warnings;
}
void LightOccluder2D::set_as_sdf_collision(bool p_enable) {
sdf_collision = p_enable;
RS::get_singleton()->canvas_light_occluder_set_as_sdf_collision(occluder, sdf_collision);
}
bool LightOccluder2D::is_set_as_sdf_collision() const {
return sdf_collision;
}
void LightOccluder2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_occluder_polygon", "polygon"), &LightOccluder2D::set_occluder_polygon);
ClassDB::bind_method(D_METHOD("get_occluder_polygon"), &LightOccluder2D::get_occluder_polygon);
ClassDB::bind_method(D_METHOD("set_occluder_light_mask", "mask"), &LightOccluder2D::set_occluder_light_mask);
ClassDB::bind_method(D_METHOD("get_occluder_light_mask"), &LightOccluder2D::get_occluder_light_mask);
ClassDB::bind_method(D_METHOD("set_as_sdf_collision", "enable"), &LightOccluder2D::set_as_sdf_collision);
ClassDB::bind_method(D_METHOD("is_set_as_sdf_collision"), &LightOccluder2D::is_set_as_sdf_collision);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "occluder", PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D"), "set_occluder_polygon", "get_occluder_polygon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sdf_collision"), "set_as_sdf_collision", "is_set_as_sdf_collision");
ADD_PROPERTY(PropertyInfo(Variant::INT, "occluder_light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_occluder_light_mask", "get_occluder_light_mask");
}
LightOccluder2D::LightOccluder2D() {
occluder = RS::get_singleton()->canvas_light_occluder_create();
set_notify_transform(true);
set_as_sdf_collision(true);
}
LightOccluder2D::~LightOccluder2D() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(occluder);
}

View file

@ -0,0 +1,116 @@
/**************************************************************************/
/* light_occluder_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef LIGHT_OCCLUDER_2D_H
#define LIGHT_OCCLUDER_2D_H
#include "scene/2d/node_2d.h"
class OccluderPolygon2D : public Resource {
GDCLASS(OccluderPolygon2D, Resource);
public:
enum CullMode {
CULL_DISABLED,
CULL_CLOCKWISE,
CULL_COUNTER_CLOCKWISE
};
private:
RID occ_polygon;
Vector<Vector2> polygon;
bool closed = true;
CullMode cull = CULL_DISABLED;
mutable Rect2 item_rect;
mutable bool rect_cache_dirty = true;
protected:
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
#endif
void set_polygon(const Vector<Vector2> &p_polygon);
Vector<Vector2> get_polygon() const;
void set_closed(bool p_closed);
bool is_closed() const;
void set_cull_mode(CullMode p_mode);
CullMode get_cull_mode() const;
virtual RID get_rid() const override;
OccluderPolygon2D();
~OccluderPolygon2D();
};
VARIANT_ENUM_CAST(OccluderPolygon2D::CullMode);
class LightOccluder2D : public Node2D {
GDCLASS(LightOccluder2D, Node2D);
RID occluder;
int mask = 1;
Ref<OccluderPolygon2D> occluder_polygon;
bool sdf_collision = false;
void _poly_changed();
virtual void _physics_interpolated_changed() override;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#endif
void set_occluder_polygon(const Ref<OccluderPolygon2D> &p_polygon);
Ref<OccluderPolygon2D> get_occluder_polygon() const;
void set_occluder_light_mask(int p_mask);
int get_occluder_light_mask() const;
void set_as_sdf_collision(bool p_enable);
bool is_set_as_sdf_collision() const;
PackedStringArray get_configuration_warnings() const override;
LightOccluder2D();
~LightOccluder2D();
};
#endif // LIGHT_OCCLUDER_2D_H

420
engine/scene/2d/line_2d.cpp Normal file
View file

@ -0,0 +1,420 @@
/**************************************************************************/
/* line_2d.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 "line_2d.h"
#include "core/math/geometry_2d.h"
#include "line_builder.h"
Line2D::Line2D() {
}
#ifdef TOOLS_ENABLED
Rect2 Line2D::_edit_get_rect() const {
if (_points.size() == 0) {
return Rect2(0, 0, 0, 0);
}
Vector2 d = Vector2(_width, _width);
Rect2 bounding_rect = Rect2(_points[0] - d, 2 * d);
for (int i = 1; i < _points.size(); i++) {
bounding_rect.expand_to(_points[i] - d);
bounding_rect.expand_to(_points[i] + d);
}
return bounding_rect;
}
bool Line2D::_edit_use_rect() const {
return true;
}
bool Line2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
const real_t d = _width / 2 + p_tolerance;
const Vector2 *points = _points.ptr();
for (int i = 0; i < _points.size() - 1; i++) {
Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, &points[i]);
if (p_point.distance_to(p) <= d) {
return true;
}
}
if (_closed && _points.size() > 2) {
const Vector2 closing_segment[2] = { points[0], points[_points.size() - 1] };
Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, closing_segment);
if (p_point.distance_to(p) <= d) {
return true;
}
}
return false;
}
#endif
void Line2D::set_points(const Vector<Vector2> &p_points) {
_points = p_points;
queue_redraw();
}
void Line2D::set_closed(bool p_closed) {
_closed = p_closed;
queue_redraw();
}
bool Line2D::is_closed() const {
return _closed;
}
void Line2D::set_width(float p_width) {
if (p_width < 0.0) {
p_width = 0.0;
}
_width = p_width;
queue_redraw();
}
float Line2D::get_width() const {
return _width;
}
void Line2D::set_curve(const Ref<Curve> &p_curve) {
if (_curve.is_valid()) {
_curve->disconnect_changed(callable_mp(this, &Line2D::_curve_changed));
}
_curve = p_curve;
if (_curve.is_valid()) {
_curve->connect_changed(callable_mp(this, &Line2D::_curve_changed));
}
queue_redraw();
}
Ref<Curve> Line2D::get_curve() const {
return _curve;
}
Vector<Vector2> Line2D::get_points() const {
return _points;
}
void Line2D::set_point_position(int i, Vector2 p_pos) {
ERR_FAIL_INDEX(i, _points.size());
_points.set(i, p_pos);
queue_redraw();
}
Vector2 Line2D::get_point_position(int i) const {
ERR_FAIL_INDEX_V(i, _points.size(), Vector2());
return _points.get(i);
}
int Line2D::get_point_count() const {
return _points.size();
}
void Line2D::clear_points() {
int count = _points.size();
if (count > 0) {
_points.clear();
queue_redraw();
}
}
void Line2D::add_point(Vector2 p_pos, int p_atpos) {
if (p_atpos < 0 || _points.size() < p_atpos) {
_points.push_back(p_pos);
} else {
_points.insert(p_atpos, p_pos);
}
queue_redraw();
}
void Line2D::remove_point(int i) {
_points.remove_at(i);
queue_redraw();
}
void Line2D::set_default_color(Color p_color) {
_default_color = p_color;
queue_redraw();
}
Color Line2D::get_default_color() const {
return _default_color;
}
void Line2D::set_gradient(const Ref<Gradient> &p_gradient) {
if (_gradient.is_valid()) {
_gradient->disconnect_changed(callable_mp(this, &Line2D::_gradient_changed));
}
_gradient = p_gradient;
if (_gradient.is_valid()) {
_gradient->connect_changed(callable_mp(this, &Line2D::_gradient_changed));
}
queue_redraw();
}
Ref<Gradient> Line2D::get_gradient() const {
return _gradient;
}
void Line2D::set_texture(const Ref<Texture2D> &p_texture) {
_texture = p_texture;
queue_redraw();
}
Ref<Texture2D> Line2D::get_texture() const {
return _texture;
}
void Line2D::set_texture_mode(const LineTextureMode p_mode) {
_texture_mode = p_mode;
queue_redraw();
}
Line2D::LineTextureMode Line2D::get_texture_mode() const {
return _texture_mode;
}
void Line2D::set_joint_mode(LineJointMode p_mode) {
_joint_mode = p_mode;
queue_redraw();
}
Line2D::LineJointMode Line2D::get_joint_mode() const {
return _joint_mode;
}
void Line2D::set_begin_cap_mode(LineCapMode p_mode) {
_begin_cap_mode = p_mode;
queue_redraw();
}
Line2D::LineCapMode Line2D::get_begin_cap_mode() const {
return _begin_cap_mode;
}
void Line2D::set_end_cap_mode(LineCapMode p_mode) {
_end_cap_mode = p_mode;
queue_redraw();
}
Line2D::LineCapMode Line2D::get_end_cap_mode() const {
return _end_cap_mode;
}
void Line2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
_draw();
} break;
}
}
void Line2D::set_sharp_limit(float p_limit) {
if (p_limit < 0.f) {
p_limit = 0.f;
}
_sharp_limit = p_limit;
queue_redraw();
}
float Line2D::get_sharp_limit() const {
return _sharp_limit;
}
void Line2D::set_round_precision(int p_precision) {
_round_precision = MAX(1, p_precision);
queue_redraw();
}
int Line2D::get_round_precision() const {
return _round_precision;
}
void Line2D::set_antialiased(bool p_antialiased) {
_antialiased = p_antialiased;
queue_redraw();
}
bool Line2D::get_antialiased() const {
return _antialiased;
}
void Line2D::_draw() {
int len = _points.size();
if (len <= 1 || _width == 0.f) {
return;
}
// TODO Maybe have it as member rather than copying parameters and allocating memory?
LineBuilder lb;
lb.points = _points;
lb.closed = _closed;
lb.default_color = _default_color;
lb.gradient = *_gradient;
lb.texture_mode = _texture_mode;
lb.joint_mode = _joint_mode;
lb.begin_cap_mode = _begin_cap_mode;
lb.end_cap_mode = _end_cap_mode;
lb.round_precision = _round_precision;
lb.sharp_limit = _sharp_limit;
lb.width = _width;
lb.curve = *_curve;
RID texture_rid;
if (_texture.is_valid()) {
texture_rid = _texture->get_rid();
lb.tile_aspect = _texture->get_size().aspect();
}
lb.build();
RS::get_singleton()->canvas_item_add_triangle_array(
get_canvas_item(),
lb.indices,
lb.vertices,
lb.colors,
lb.uvs, Vector<int>(), Vector<float>(),
texture_rid);
// DEBUG: Draw wireframe
// if (lb.indices.size() % 3 == 0) {
// Color col(0, 0, 0);
// for (int i = 0; i < lb.indices.size(); i += 3) {
// Vector2 a = lb.vertices[lb.indices[i]];
// Vector2 b = lb.vertices[lb.indices[i+1]];
// Vector2 c = lb.vertices[lb.indices[i+2]];
// draw_line(a, b, col);
// draw_line(b, c, col);
// draw_line(c, a, col);
// }
// for (int i = 0; i < lb.vertices.size(); ++i) {
// Vector2 p = lb.vertices[i];
// draw_rect(Rect2(p.x - 1, p.y - 1, 2, 2), Color(0, 0, 0, 0.5));
// }
// }
}
void Line2D::_gradient_changed() {
queue_redraw();
}
void Line2D::_curve_changed() {
queue_redraw();
}
// static
void Line2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_points", "points"), &Line2D::set_points);
ClassDB::bind_method(D_METHOD("get_points"), &Line2D::get_points);
ClassDB::bind_method(D_METHOD("set_point_position", "index", "position"), &Line2D::set_point_position);
ClassDB::bind_method(D_METHOD("get_point_position", "index"), &Line2D::get_point_position);
ClassDB::bind_method(D_METHOD("get_point_count"), &Line2D::get_point_count);
ClassDB::bind_method(D_METHOD("add_point", "position", "index"), &Line2D::add_point, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("remove_point", "index"), &Line2D::remove_point);
ClassDB::bind_method(D_METHOD("clear_points"), &Line2D::clear_points);
ClassDB::bind_method(D_METHOD("set_closed", "closed"), &Line2D::set_closed);
ClassDB::bind_method(D_METHOD("is_closed"), &Line2D::is_closed);
ClassDB::bind_method(D_METHOD("set_width", "width"), &Line2D::set_width);
ClassDB::bind_method(D_METHOD("get_width"), &Line2D::get_width);
ClassDB::bind_method(D_METHOD("set_curve", "curve"), &Line2D::set_curve);
ClassDB::bind_method(D_METHOD("get_curve"), &Line2D::get_curve);
ClassDB::bind_method(D_METHOD("set_default_color", "color"), &Line2D::set_default_color);
ClassDB::bind_method(D_METHOD("get_default_color"), &Line2D::get_default_color);
ClassDB::bind_method(D_METHOD("set_gradient", "color"), &Line2D::set_gradient);
ClassDB::bind_method(D_METHOD("get_gradient"), &Line2D::get_gradient);
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &Line2D::set_texture);
ClassDB::bind_method(D_METHOD("get_texture"), &Line2D::get_texture);
ClassDB::bind_method(D_METHOD("set_texture_mode", "mode"), &Line2D::set_texture_mode);
ClassDB::bind_method(D_METHOD("get_texture_mode"), &Line2D::get_texture_mode);
ClassDB::bind_method(D_METHOD("set_joint_mode", "mode"), &Line2D::set_joint_mode);
ClassDB::bind_method(D_METHOD("get_joint_mode"), &Line2D::get_joint_mode);
ClassDB::bind_method(D_METHOD("set_begin_cap_mode", "mode"), &Line2D::set_begin_cap_mode);
ClassDB::bind_method(D_METHOD("get_begin_cap_mode"), &Line2D::get_begin_cap_mode);
ClassDB::bind_method(D_METHOD("set_end_cap_mode", "mode"), &Line2D::set_end_cap_mode);
ClassDB::bind_method(D_METHOD("get_end_cap_mode"), &Line2D::get_end_cap_mode);
ClassDB::bind_method(D_METHOD("set_sharp_limit", "limit"), &Line2D::set_sharp_limit);
ClassDB::bind_method(D_METHOD("get_sharp_limit"), &Line2D::get_sharp_limit);
ClassDB::bind_method(D_METHOD("set_round_precision", "precision"), &Line2D::set_round_precision);
ClassDB::bind_method(D_METHOD("get_round_precision"), &Line2D::get_round_precision);
ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &Line2D::set_antialiased);
ClassDB::bind_method(D_METHOD("get_antialiased"), &Line2D::get_antialiased);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "points"), "set_points", "get_points");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "closed"), "set_closed", "is_closed");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width", PROPERTY_HINT_NONE, "suffix:px"), "set_width", "get_width");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "width_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_color"), "set_default_color", "get_default_color");
ADD_GROUP("Fill", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_gradient", "get_gradient");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_mode", PROPERTY_HINT_ENUM, "None,Tile,Stretch"), "set_texture_mode", "get_texture_mode");
ADD_GROUP("Capping", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "joint_mode", PROPERTY_HINT_ENUM, "Sharp,Bevel,Round"), "set_joint_mode", "get_joint_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "begin_cap_mode", PROPERTY_HINT_ENUM, "None,Box,Round"), "set_begin_cap_mode", "get_begin_cap_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "end_cap_mode", PROPERTY_HINT_ENUM, "None,Box,Round"), "set_end_cap_mode", "get_end_cap_mode");
ADD_GROUP("Border", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sharp_limit"), "set_sharp_limit", "get_sharp_limit");
ADD_PROPERTY(PropertyInfo(Variant::INT, "round_precision", PROPERTY_HINT_RANGE, "1,32,1"), "set_round_precision", "get_round_precision");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased"), "set_antialiased", "get_antialiased");
BIND_ENUM_CONSTANT(LINE_JOINT_SHARP);
BIND_ENUM_CONSTANT(LINE_JOINT_BEVEL);
BIND_ENUM_CONSTANT(LINE_JOINT_ROUND);
BIND_ENUM_CONSTANT(LINE_CAP_NONE);
BIND_ENUM_CONSTANT(LINE_CAP_BOX);
BIND_ENUM_CONSTANT(LINE_CAP_ROUND);
BIND_ENUM_CONSTANT(LINE_TEXTURE_NONE);
BIND_ENUM_CONSTANT(LINE_TEXTURE_TILE);
BIND_ENUM_CONSTANT(LINE_TEXTURE_STRETCH);
}

150
engine/scene/2d/line_2d.h Normal file
View file

@ -0,0 +1,150 @@
/**************************************************************************/
/* line_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef LINE_2D_H
#define LINE_2D_H
#include "node_2d.h"
class Line2D : public Node2D {
GDCLASS(Line2D, Node2D);
public:
enum LineJointMode {
LINE_JOINT_SHARP = 0,
LINE_JOINT_BEVEL,
LINE_JOINT_ROUND
};
enum LineCapMode {
LINE_CAP_NONE = 0,
LINE_CAP_BOX,
LINE_CAP_ROUND
};
enum LineTextureMode {
LINE_TEXTURE_NONE = 0,
LINE_TEXTURE_TILE,
LINE_TEXTURE_STRETCH
};
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#endif
Line2D();
void set_points(const Vector<Vector2> &p_points);
Vector<Vector2> get_points() const;
void set_point_position(int i, Vector2 pos);
Vector2 get_point_position(int i) const;
int get_point_count() const;
void clear_points();
void add_point(Vector2 pos, int atpos = -1);
void remove_point(int i);
void set_closed(bool p_closed);
bool is_closed() const;
void set_width(float width);
float get_width() const;
void set_curve(const Ref<Curve> &curve);
Ref<Curve> get_curve() const;
void set_default_color(Color color);
Color get_default_color() const;
void set_gradient(const Ref<Gradient> &gradient);
Ref<Gradient> get_gradient() const;
void set_texture(const Ref<Texture2D> &texture);
Ref<Texture2D> get_texture() const;
void set_texture_mode(const LineTextureMode mode);
LineTextureMode get_texture_mode() const;
void set_joint_mode(LineJointMode mode);
LineJointMode get_joint_mode() const;
void set_begin_cap_mode(LineCapMode mode);
LineCapMode get_begin_cap_mode() const;
void set_end_cap_mode(LineCapMode mode);
LineCapMode get_end_cap_mode() const;
void set_sharp_limit(float limit);
float get_sharp_limit() const;
void set_round_precision(int precision);
int get_round_precision() const;
void set_antialiased(bool p_antialiased);
bool get_antialiased() const;
protected:
void _notification(int p_what);
void _draw();
static void _bind_methods();
private:
void _gradient_changed();
void _curve_changed();
private:
Vector<Vector2> _points;
LineJointMode _joint_mode = LINE_JOINT_SHARP;
LineCapMode _begin_cap_mode = LINE_CAP_NONE;
LineCapMode _end_cap_mode = LINE_CAP_NONE;
bool _closed = false;
float _width = 10.0;
Ref<Curve> _curve;
Color _default_color = Color(1, 1, 1);
Ref<Gradient> _gradient;
Ref<Texture2D> _texture;
LineTextureMode _texture_mode = LINE_TEXTURE_NONE;
float _sharp_limit = 2.f;
int _round_precision = 8;
bool _antialiased = false;
};
// Needed so we can bind functions
VARIANT_ENUM_CAST(Line2D::LineJointMode)
VARIANT_ENUM_CAST(Line2D::LineCapMode)
VARIANT_ENUM_CAST(Line2D::LineTextureMode)
#endif // LINE_2D_H

View file

@ -0,0 +1,593 @@
/**************************************************************************/
/* line_builder.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 "line_builder.h"
#include "core/math/geometry_2d.h"
// Utility method.
static inline Vector2 interpolate(const Rect2 &r, const Vector2 &v) {
return Vector2(
Math::lerp(r.position.x, r.position.x + r.get_size().x, v.x),
Math::lerp(r.position.y, r.position.y + r.get_size().y, v.y));
}
LineBuilder::LineBuilder() {
}
void LineBuilder::build() {
// Need at least 2 points to draw a line, so clear the output and return.
if (points.size() < 2) {
vertices.clear();
colors.clear();
indices.clear();
uvs.clear();
return;
}
ERR_FAIL_COND(tile_aspect <= 0.f);
const float hw = width / 2.f;
const float hw_sq = hw * hw;
const float sharp_limit_sq = sharp_limit * sharp_limit;
const int point_count = points.size();
const bool wrap_around = closed && point_count > 2;
_interpolate_color = gradient != nullptr;
const bool retrieve_curve = curve != nullptr;
const bool distance_required = _interpolate_color || retrieve_curve ||
texture_mode == Line2D::LINE_TEXTURE_TILE ||
texture_mode == Line2D::LINE_TEXTURE_STRETCH;
// Initial values
Vector2 pos0 = points[0];
Vector2 pos1 = points[1];
Vector2 f0 = (pos1 - pos0).normalized();
Vector2 u0 = f0.orthogonal();
Vector2 pos_up0 = pos0;
Vector2 pos_down0 = pos0;
Color color0;
Color color1;
float current_distance0 = 0.f;
float current_distance1 = 0.f;
float total_distance = 0.f;
float width_factor = 1.f;
float modified_hw = hw;
if (retrieve_curve) {
width_factor = curve->sample_baked(0.f);
modified_hw = hw * width_factor;
}
if (distance_required) {
// Calculate the total distance.
for (int i = 1; i < point_count; ++i) {
total_distance += points[i].distance_to(points[i - 1]);
}
if (wrap_around) {
total_distance += points[point_count - 1].distance_to(pos0);
} else {
// Adjust the total distance.
// The line's outer length may be a little higher due to the end caps.
if (begin_cap_mode == Line2D::LINE_CAP_BOX || begin_cap_mode == Line2D::LINE_CAP_ROUND) {
total_distance += modified_hw;
}
if (end_cap_mode == Line2D::LINE_CAP_BOX || end_cap_mode == Line2D::LINE_CAP_ROUND) {
if (retrieve_curve) {
total_distance += hw * curve->sample_baked(1.f);
} else {
total_distance += hw;
}
}
}
}
if (_interpolate_color) {
color0 = gradient->get_color(0);
} else {
colors.push_back(default_color);
}
float uvx0 = 0.f;
float uvx1 = 0.f;
pos_up0 += u0 * modified_hw;
pos_down0 -= u0 * modified_hw;
// Begin cap
if (!wrap_around) {
if (begin_cap_mode == Line2D::LINE_CAP_BOX) {
// Push back first vertices a little bit.
pos_up0 -= f0 * modified_hw;
pos_down0 -= f0 * modified_hw;
current_distance0 += modified_hw;
current_distance1 = current_distance0;
} else if (begin_cap_mode == Line2D::LINE_CAP_ROUND) {
if (texture_mode == Line2D::LINE_TEXTURE_TILE) {
uvx0 = width_factor * 0.5f / tile_aspect;
} else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) {
uvx0 = width * width_factor / total_distance;
}
new_arc(pos0, pos_up0 - pos0, -Math_PI, color0, Rect2(0.f, 0.f, uvx0 * 2, 1.f));
current_distance0 += modified_hw;
current_distance1 = current_distance0;
}
strip_begin(pos_up0, pos_down0, color0, uvx0);
}
/*
* pos_up0 ------------- pos_up1 --------------------
* | |
* pos0 - - - - - - - - - pos1 - - - - - - - - - pos2
* | |
* pos_down0 ------------ pos_down1 ------------------
*
* i-1 i i+1
*/
// http://labs.hyperandroid.com/tag/opengl-lines
// (not the same implementation but visuals help a lot)
// If the polyline wraps around, then draw two more segments with joints:
// The last one, which should normally end with an end cap, and the one that matches the end and the beginning.
int segments_count = wrap_around ? point_count : (point_count - 2);
// The wraparound case starts with a "fake walk" from the end of the polyline
// to its beginning, so that its first joint is correct, without drawing anything.
int first_point = wrap_around ? -1 : 1;
// If the line wraps around, these variables will be used for the final segment.
Vector2 first_pos_up, first_pos_down;
bool is_first_joint_sharp = false;
// For each additional segment
for (int i = first_point; i <= segments_count; ++i) {
pos1 = points[(i == -1) ? point_count - 1 : i % point_count]; // First point.
Vector2 pos2 = points[(i + 1) % point_count]; // Second point.
Vector2 f1 = (pos2 - pos1).normalized();
Vector2 u1 = f1.orthogonal();
// Determine joint orientation.
float dp = u0.dot(f1);
const Orientation orientation = (dp > 0.f ? UP : DOWN);
if (distance_required && i >= 1) {
current_distance1 += pos0.distance_to(pos1);
}
if (_interpolate_color) {
color1 = gradient->get_color_at_offset(current_distance1 / total_distance);
}
if (retrieve_curve) {
width_factor = curve->sample_baked(current_distance1 / total_distance);
modified_hw = hw * width_factor;
}
Vector2 inner_normal0 = u0 * modified_hw;
Vector2 inner_normal1 = u1 * modified_hw;
if (orientation == DOWN) {
inner_normal0 = -inner_normal0;
inner_normal1 = -inner_normal1;
}
/*
* ---------------------------
* /
* 0 / 1
* / /
* --------------------x------ /
* / / (here shown with orientation == DOWN)
* / /
* / /
* / /
* 2 /
* /
*/
// Find inner intersection at the joint.
Vector2 corner_pos_in, corner_pos_out;
bool is_intersecting = Geometry2D::segment_intersects_segment(
pos0 + inner_normal0, pos1 + inner_normal0,
pos1 + inner_normal1, pos2 + inner_normal1,
&corner_pos_in);
if (is_intersecting) {
// Inner parts of the segments intersect.
corner_pos_out = 2.f * pos1 - corner_pos_in;
} else {
// No intersection, segments are too sharp or they overlap.
corner_pos_in = pos1 + inner_normal0;
corner_pos_out = pos1 - inner_normal0;
}
Vector2 corner_pos_up, corner_pos_down;
if (orientation == UP) {
corner_pos_up = corner_pos_in;
corner_pos_down = corner_pos_out;
} else {
corner_pos_up = corner_pos_out;
corner_pos_down = corner_pos_in;
}
Line2D::LineJointMode current_joint_mode = joint_mode;
Vector2 pos_up1, pos_down1;
if (is_intersecting) {
// Fallback on bevel if sharp angle is too high (because it would produce very long miters).
float width_factor_sq = width_factor * width_factor;
if (current_joint_mode == Line2D::LINE_JOINT_SHARP && corner_pos_out.distance_squared_to(pos1) / (hw_sq * width_factor_sq) > sharp_limit_sq) {
current_joint_mode = Line2D::LINE_JOINT_BEVEL;
}
if (current_joint_mode == Line2D::LINE_JOINT_SHARP) {
// In this case, we won't create joint geometry,
// The previous and next line quads will directly share an edge.
pos_up1 = corner_pos_up;
pos_down1 = corner_pos_down;
} else {
// Bevel or round
if (orientation == UP) {
pos_up1 = corner_pos_up;
pos_down1 = pos1 - u0 * modified_hw;
} else {
pos_up1 = pos1 + u0 * modified_hw;
pos_down1 = corner_pos_down;
}
}
} else {
// No intersection: fallback
if (current_joint_mode == Line2D::LINE_JOINT_SHARP) {
// There is no fallback implementation for LINE_JOINT_SHARP so switch to the LINE_JOINT_BEVEL.
current_joint_mode = Line2D::LINE_JOINT_BEVEL;
}
pos_up1 = corner_pos_up;
pos_down1 = corner_pos_down;
}
// Triangles are clockwise.
if (texture_mode == Line2D::LINE_TEXTURE_TILE) {
uvx1 = current_distance1 / (width * tile_aspect);
} else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) {
uvx1 = current_distance1 / total_distance;
}
// Swap vars for use in the next line.
color0 = color1;
u0 = u1;
f0 = f1;
pos0 = pos1;
if (is_intersecting) {
if (current_joint_mode == Line2D::LINE_JOINT_SHARP) {
pos_up0 = pos_up1;
pos_down0 = pos_down1;
} else {
if (orientation == UP) {
pos_up0 = corner_pos_up;
pos_down0 = pos1 - u1 * modified_hw;
} else {
pos_up0 = pos1 + u1 * modified_hw;
pos_down0 = corner_pos_down;
}
}
} else {
pos_up0 = pos1 + u1 * modified_hw;
pos_down0 = pos1 - u1 * modified_hw;
}
// End the "fake pass" in the closed line case before the drawing subroutine.
if (i == -1) {
continue;
}
// For wrap-around polylines, store some kind of start positions of the first joint for the final connection.
if (wrap_around && i == 0) {
Vector2 first_pos_center = (pos_up1 + pos_down1) / 2;
float lerp_factor = 1.0 / width_factor;
first_pos_up = first_pos_center.lerp(pos_up1, lerp_factor);
first_pos_down = first_pos_center.lerp(pos_down1, lerp_factor);
is_first_joint_sharp = current_joint_mode == Line2D::LINE_JOINT_SHARP;
}
// Add current line body quad.
if (wrap_around && retrieve_curve && !is_first_joint_sharp && i == segments_count) {
// If the width curve is not seamless, we might need to fetch the line's start points to use them for the final connection.
Vector2 first_pos_center = (first_pos_up + first_pos_down) / 2;
strip_add_quad(first_pos_center.lerp(first_pos_up, width_factor), first_pos_center.lerp(first_pos_down, width_factor), color1, uvx1);
return;
} else {
strip_add_quad(pos_up1, pos_down1, color1, uvx1);
}
// From this point, bu0 and bd0 concern the next segment.
// Add joint geometry.
if (current_joint_mode != Line2D::LINE_JOINT_SHARP) {
/* ________________ cbegin
* / \
* / \
* ____________/_ _ _\ cend
* | |
* | |
* | |
*/
Vector2 cbegin, cend;
if (orientation == UP) {
cbegin = pos_down1;
cend = pos_down0;
} else {
cbegin = pos_up1;
cend = pos_up0;
}
if (current_joint_mode == Line2D::LINE_JOINT_BEVEL && !(wrap_around && i == segments_count)) {
strip_add_tri(cend, orientation);
} else if (current_joint_mode == Line2D::LINE_JOINT_ROUND && !(wrap_around && i == segments_count)) {
Vector2 vbegin = cbegin - pos1;
Vector2 vend = cend - pos1;
// We want to use vbegin.angle_to(vend) below, which evaluates to
// Math::atan2(vbegin.cross(vend), vbegin.dot(vend)) but we need to
// calculate this ourselves as we need to check if the cross product
// in that calculation ends up being -0.f and flip it if so, effectively
// flipping the resulting angle_delta to not return -PI but +PI instead
float cross_product = vbegin.cross(vend);
float dot_product = vbegin.dot(vend);
// Note that we're comparing against -0.f for clarity but 0.f would
// match as well, therefore we need the explicit signbit check too.
if (cross_product == -0.f && signbit(cross_product)) {
cross_product = 0.f;
}
float angle_delta = Math::atan2(cross_product, dot_product);
strip_add_arc(pos1, angle_delta, orientation);
}
if (!is_intersecting) {
// In this case the joint is too corrupted to be re-used,
// start again the strip with fallback points
strip_begin(pos_up0, pos_down0, color1, uvx1);
}
}
}
// Draw the last (or only) segment, with its end cap logic.
if (!wrap_around) {
pos1 = points[point_count - 1];
if (distance_required) {
current_distance1 += pos0.distance_to(pos1);
}
if (_interpolate_color) {
color1 = gradient->get_color(gradient->get_point_count() - 1);
}
if (retrieve_curve) {
width_factor = curve->sample_baked(1.f);
modified_hw = hw * width_factor;
}
Vector2 pos_up1 = pos1 + u0 * modified_hw;
Vector2 pos_down1 = pos1 - u0 * modified_hw;
// Add extra distance for a box end cap.
if (end_cap_mode == Line2D::LINE_CAP_BOX) {
pos_up1 += f0 * modified_hw;
pos_down1 += f0 * modified_hw;
current_distance1 += modified_hw;
}
if (texture_mode == Line2D::LINE_TEXTURE_TILE) {
uvx1 = current_distance1 / (width * tile_aspect);
} else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) {
uvx1 = current_distance1 / total_distance;
}
strip_add_quad(pos_up1, pos_down1, color1, uvx1);
// Custom drawing for a round end cap.
if (end_cap_mode == Line2D::LINE_CAP_ROUND) {
// Note: color is not used in case we don't interpolate.
Color color = _interpolate_color ? gradient->get_color(gradient->get_point_count() - 1) : Color(0, 0, 0);
float dist = 0;
if (texture_mode == Line2D::LINE_TEXTURE_TILE) {
dist = width_factor / tile_aspect;
} else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) {
dist = width * width_factor / total_distance;
}
new_arc(pos1, pos_up1 - pos1, Math_PI, color, Rect2(uvx1 - 0.5f * dist, 0.f, dist, 1.f));
}
}
}
void LineBuilder::strip_begin(Vector2 up, Vector2 down, Color color, float uvx) {
int vi = vertices.size();
vertices.push_back(up);
vertices.push_back(down);
if (_interpolate_color) {
colors.push_back(color);
colors.push_back(color);
}
if (texture_mode != Line2D::LINE_TEXTURE_NONE) {
uvs.push_back(Vector2(uvx, 0.f));
uvs.push_back(Vector2(uvx, 1.f));
}
_last_index[UP] = vi;
_last_index[DOWN] = vi + 1;
}
void LineBuilder::strip_add_quad(Vector2 up, Vector2 down, Color color, float uvx) {
int vi = vertices.size();
vertices.push_back(up);
vertices.push_back(down);
if (_interpolate_color) {
colors.push_back(color);
colors.push_back(color);
}
if (texture_mode != Line2D::LINE_TEXTURE_NONE) {
uvs.push_back(Vector2(uvx, 0.f));
uvs.push_back(Vector2(uvx, 1.f));
}
indices.push_back(_last_index[UP]);
indices.push_back(vi + 1);
indices.push_back(_last_index[DOWN]);
indices.push_back(_last_index[UP]);
indices.push_back(vi);
indices.push_back(vi + 1);
_last_index[UP] = vi;
_last_index[DOWN] = vi + 1;
}
void LineBuilder::strip_add_tri(Vector2 up, Orientation orientation) {
int vi = vertices.size();
vertices.push_back(up);
if (_interpolate_color) {
colors.push_back(colors[colors.size() - 1]);
}
Orientation opposite_orientation = orientation == UP ? DOWN : UP;
if (texture_mode != Line2D::LINE_TEXTURE_NONE) {
// UVs are just one slice of the texture all along
// (otherwise we can't share the bottom vertex)
uvs.push_back(uvs[_last_index[opposite_orientation]]);
}
indices.push_back(_last_index[opposite_orientation]);
indices.push_back(vi);
indices.push_back(_last_index[orientation]);
_last_index[opposite_orientation] = vi;
}
void LineBuilder::strip_add_arc(Vector2 center, float angle_delta, Orientation orientation) {
// Take the two last vertices and extrude an arc made of triangles
// that all share one of the initial vertices
Orientation opposite_orientation = orientation == UP ? DOWN : UP;
Vector2 vbegin = vertices[_last_index[opposite_orientation]] - center;
float radius = vbegin.length();
float angle_step = Math_PI / static_cast<float>(round_precision);
float steps = Math::abs(angle_delta) / angle_step;
if (angle_delta < 0.f) {
angle_step = -angle_step;
}
float t = Vector2(1, 0).angle_to(vbegin);
float end_angle = t + angle_delta;
Vector2 rpos(0, 0);
// Arc vertices
for (int ti = 0; ti < steps; ++ti, t += angle_step) {
rpos = center + Vector2(Math::cos(t), Math::sin(t)) * radius;
strip_add_tri(rpos, orientation);
}
// Last arc vertex
rpos = center + Vector2(Math::cos(end_angle), Math::sin(end_angle)) * radius;
strip_add_tri(rpos, orientation);
}
void LineBuilder::new_arc(Vector2 center, Vector2 vbegin, float angle_delta, Color color, Rect2 uv_rect) {
// Make a standalone arc that doesn't use existing vertices,
// with undistorted UVs from within a square section
float radius = vbegin.length();
float angle_step = Math_PI / static_cast<float>(round_precision);
float steps = Math::abs(angle_delta) / angle_step;
if (angle_delta < 0.f) {
angle_step = -angle_step;
}
float t = Vector2(1, 0).angle_to(vbegin);
float end_angle = t + angle_delta;
Vector2 rpos(0, 0);
float tt_begin = -Math_PI / 2.0f;
float tt = tt_begin;
// Center vertice
int vi = vertices.size();
vertices.push_back(center);
if (_interpolate_color) {
colors.push_back(color);
}
if (texture_mode != Line2D::LINE_TEXTURE_NONE) {
uvs.push_back(interpolate(uv_rect, Vector2(0.5f, 0.5f)));
}
// Arc vertices
for (int ti = 0; ti < steps; ++ti, t += angle_step) {
Vector2 sc = Vector2(Math::cos(t), Math::sin(t));
rpos = center + sc * radius;
vertices.push_back(rpos);
if (_interpolate_color) {
colors.push_back(color);
}
if (texture_mode != Line2D::LINE_TEXTURE_NONE) {
Vector2 tsc = Vector2(Math::cos(tt), Math::sin(tt));
uvs.push_back(interpolate(uv_rect, 0.5f * (tsc + Vector2(1.f, 1.f))));
tt += angle_step;
}
}
// Last arc vertex
Vector2 sc = Vector2(Math::cos(end_angle), Math::sin(end_angle));
rpos = center + sc * radius;
vertices.push_back(rpos);
if (_interpolate_color) {
colors.push_back(color);
}
if (texture_mode != Line2D::LINE_TEXTURE_NONE) {
tt = tt_begin + angle_delta;
Vector2 tsc = Vector2(Math::cos(tt), Math::sin(tt));
uvs.push_back(interpolate(uv_rect, 0.5f * (tsc + Vector2(1.f, 1.f))));
}
// Make up triangles
int vi0 = vi;
for (int ti = 0; ti < steps; ++ti) {
indices.push_back(vi0);
indices.push_back(++vi);
indices.push_back(vi + 1);
}
}

View file

@ -0,0 +1,86 @@
/**************************************************************************/
/* line_builder.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef LINE_BUILDER_H
#define LINE_BUILDER_H
#include "line_2d.h"
class LineBuilder {
public:
// TODO Move in a struct and reference it
// Input
Vector<Vector2> points;
Line2D::LineJointMode joint_mode = Line2D::LINE_JOINT_SHARP;
Line2D::LineCapMode begin_cap_mode = Line2D::LINE_CAP_NONE;
Line2D::LineCapMode end_cap_mode = Line2D::LINE_CAP_NONE;
bool closed = false;
float width = 10.0;
Curve *curve = nullptr;
Color default_color = Color(0.4, 0.5, 1);
Gradient *gradient = nullptr;
Line2D::LineTextureMode texture_mode = Line2D::LineTextureMode::LINE_TEXTURE_NONE;
float sharp_limit = 2.f;
int round_precision = 8;
float tile_aspect = 1.f; // w/h
// TODO offset_joints option (offers alternative implementation of round joints)
// TODO Move in a struct and reference it
// Output
Vector<Vector2> vertices;
Vector<Color> colors;
Vector<Vector2> uvs;
Vector<int> indices;
LineBuilder();
void build();
private:
enum Orientation {
UP = 0,
DOWN = 1
};
// Triangle-strip methods
void strip_begin(Vector2 up, Vector2 down, Color color, float uvx);
void strip_new_quad(Vector2 up, Vector2 down, Color color, float uvx);
void strip_add_quad(Vector2 up, Vector2 down, Color color, float uvx);
void strip_add_tri(Vector2 up, Orientation orientation);
void strip_add_arc(Vector2 center, float angle_delta, Orientation orientation);
void new_arc(Vector2 center, Vector2 vbegin, float angle_delta, Color color, Rect2 uv_rect);
private:
bool _interpolate_color = false;
int _last_index[2] = {}; // Index of last up and down vertices of the strip
};
#endif // LINE_BUILDER_H

View file

@ -0,0 +1,110 @@
/**************************************************************************/
/* marker_2d.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_2d.h"
void Marker2D::_draw_cross() {
const real_t extents = get_gizmo_extents();
PackedVector2Array points = {
Point2(+extents, 0),
Point2(),
Point2(),
Point2(-extents, 0),
Point2(0, +extents),
Point2(),
Point2(),
Point2(0, -extents),
};
// Use the axis color which is brighter for the positive axis.
// Use a darkened axis color for the negative axis.
// This makes it possible to see in which direction the Marker3D node is rotated
// (which can be important depending on how it's used).
// Axis colors are taken from `axis_x_color` and `axis_y_color` (defined in `editor/themes/editor_theme_manager.cpp`).
const Color color_x = Color(0.96, 0.20, 0.32);
const Color color_y = Color(0.53, 0.84, 0.01);
PackedColorArray colors = {
color_x,
color_x.darkened(0.5),
color_y,
color_y.darkened(0.5),
};
draw_multiline_colors(points, colors);
}
#ifdef TOOLS_ENABLED
Rect2 Marker2D::_edit_get_rect() const {
real_t extents = get_gizmo_extents();
return Rect2(Point2(-extents, -extents), Size2(extents * 2, extents * 2));
}
bool Marker2D::_edit_use_rect() const {
return false;
}
#endif
void Marker2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
queue_redraw();
} break;
case NOTIFICATION_DRAW: {
if (!is_inside_tree()) {
break;
}
if (Engine::get_singleton()->is_editor_hint()) {
_draw_cross();
}
} break;
}
}
void Marker2D::set_gizmo_extents(real_t p_extents) {
gizmo_extents = p_extents;
queue_redraw();
}
real_t Marker2D::get_gizmo_extents() const {
return gizmo_extents;
}
void Marker2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_gizmo_extents", "extents"), &Marker2D::set_gizmo_extents);
ClassDB::bind_method(D_METHOD("get_gizmo_extents"), &Marker2D::get_gizmo_extents);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gizmo_extents", PROPERTY_HINT_RANGE, "0,1000,0.1,or_greater,suffix:px"), "set_gizmo_extents", "get_gizmo_extents");
}
Marker2D::Marker2D() {
set_hide_clip_children(true);
}

View file

@ -0,0 +1,59 @@
/**************************************************************************/
/* marker_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef MARKER_2D_H
#define MARKER_2D_H
#include "scene/2d/node_2d.h"
class Marker2D : public Node2D {
GDCLASS(Marker2D, Node2D);
real_t gizmo_extents = 10.0;
void _draw_cross();
protected:
void _notification(int p_what);
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
#endif
void set_gizmo_extents(real_t p_extents);
real_t get_gizmo_extents() const;
Marker2D();
};
#endif // MARKER_2D_H

View file

@ -0,0 +1,94 @@
/**************************************************************************/
/* mesh_instance_2d.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_2d.h"
void MeshInstance2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
if (mesh.is_valid()) {
draw_mesh(mesh, texture);
}
} break;
}
}
void MeshInstance2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance2D::set_mesh);
ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance2D::get_mesh);
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &MeshInstance2D::set_texture);
ClassDB::bind_method(D_METHOD("get_texture"), &MeshInstance2D::get_texture);
ADD_SIGNAL(MethodInfo("texture_changed"));
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
}
void MeshInstance2D::set_mesh(const Ref<Mesh> &p_mesh) {
mesh = p_mesh;
queue_redraw();
}
Ref<Mesh> MeshInstance2D::get_mesh() const {
return mesh;
}
void MeshInstance2D::set_texture(const Ref<Texture2D> &p_texture) {
if (p_texture == texture) {
return;
}
texture = p_texture;
queue_redraw();
emit_signal(SceneStringName(texture_changed));
}
Ref<Texture2D> MeshInstance2D::get_texture() const {
return texture;
}
#ifdef TOOLS_ENABLED
Rect2 MeshInstance2D::_edit_get_rect() const {
if (mesh.is_valid()) {
AABB aabb = mesh->get_aabb();
return Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y);
}
return Node2D::_edit_get_rect();
}
bool MeshInstance2D::_edit_use_rect() const {
return mesh.is_valid();
}
#endif
MeshInstance2D::MeshInstance2D() {
}

View file

@ -0,0 +1,62 @@
/**************************************************************************/
/* mesh_instance_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef MESH_INSTANCE_2D_H
#define MESH_INSTANCE_2D_H
#include "scene/2d/node_2d.h"
class MeshInstance2D : public Node2D {
GDCLASS(MeshInstance2D, Node2D);
Ref<Mesh> mesh;
Ref<Texture2D> texture;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
#endif
void set_mesh(const Ref<Mesh> &p_mesh);
Ref<Mesh> get_mesh() const;
void set_texture(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture() const;
MeshInstance2D();
};
#endif // MESH_INSTANCE_2D_H

View file

@ -0,0 +1,102 @@
/**************************************************************************/
/* multimesh_instance_2d.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_2d.h"
void MultiMeshInstance2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
if (multimesh.is_valid()) {
draw_multimesh(multimesh, texture);
}
} break;
}
}
void MultiMeshInstance2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_multimesh", "multimesh"), &MultiMeshInstance2D::set_multimesh);
ClassDB::bind_method(D_METHOD("get_multimesh"), &MultiMeshInstance2D::get_multimesh);
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &MultiMeshInstance2D::set_texture);
ClassDB::bind_method(D_METHOD("get_texture"), &MultiMeshInstance2D::get_texture);
ADD_SIGNAL(MethodInfo("texture_changed"));
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multimesh", PROPERTY_HINT_RESOURCE_TYPE, "MultiMesh"), "set_multimesh", "get_multimesh");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
}
void MultiMeshInstance2D::set_multimesh(const Ref<MultiMesh> &p_multimesh) {
// Cleanup previous connection if any.
if (multimesh.is_valid()) {
multimesh->disconnect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
}
multimesh = p_multimesh;
// Connect to the multimesh so the AABB can update when instance transforms are changed.
if (multimesh.is_valid()) {
multimesh->connect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
}
queue_redraw();
}
Ref<MultiMesh> MultiMeshInstance2D::get_multimesh() const {
return multimesh;
}
void MultiMeshInstance2D::set_texture(const Ref<Texture2D> &p_texture) {
if (p_texture == texture) {
return;
}
texture = p_texture;
queue_redraw();
emit_signal(SceneStringName(texture_changed));
}
Ref<Texture2D> MultiMeshInstance2D::get_texture() const {
return texture;
}
#ifdef TOOLS_ENABLED
Rect2 MultiMeshInstance2D::_edit_get_rect() const {
if (multimesh.is_valid()) {
AABB aabb = multimesh->get_aabb();
return Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y);
}
return Node2D::_edit_get_rect();
}
#endif
MultiMeshInstance2D::MultiMeshInstance2D() {
}
MultiMeshInstance2D::~MultiMeshInstance2D() {
}

View file

@ -0,0 +1,63 @@
/**************************************************************************/
/* multimesh_instance_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef MULTIMESH_INSTANCE_2D_H
#define MULTIMESH_INSTANCE_2D_H
#include "scene/2d/node_2d.h"
#include "scene/resources/multimesh.h"
class MultiMeshInstance2D : public Node2D {
GDCLASS(MultiMeshInstance2D, Node2D);
Ref<MultiMesh> multimesh;
Ref<Texture2D> texture;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
#endif
void set_multimesh(const Ref<MultiMesh> &p_multimesh);
Ref<MultiMesh> get_multimesh() const;
void set_texture(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture() const;
MultiMeshInstance2D();
~MultiMeshInstance2D();
};
#endif // MULTIMESH_INSTANCE_2D_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,264 @@
/**************************************************************************/
/* navigation_agent_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef NAVIGATION_AGENT_2D_H
#define NAVIGATION_AGENT_2D_H
#include "scene/main/node.h"
#include "servers/navigation/navigation_path_query_parameters_2d.h"
#include "servers/navigation/navigation_path_query_result_2d.h"
class Node2D;
class NavigationAgent2D : public Node {
GDCLASS(NavigationAgent2D, Node);
Node2D *agent_parent = nullptr;
RID agent;
RID map_override;
bool avoidance_enabled = false;
uint32_t avoidance_layers = 1;
uint32_t avoidance_mask = 1;
real_t avoidance_priority = 1.0;
uint32_t navigation_layers = 1;
NavigationPathQueryParameters2D::PathfindingAlgorithm pathfinding_algorithm = NavigationPathQueryParameters2D::PathfindingAlgorithm::PATHFINDING_ALGORITHM_ASTAR;
NavigationPathQueryParameters2D::PathPostProcessing path_postprocessing = NavigationPathQueryParameters2D::PathPostProcessing::PATH_POSTPROCESSING_CORRIDORFUNNEL;
BitField<NavigationPathQueryParameters2D::PathMetadataFlags> path_metadata_flags = NavigationPathQueryParameters2D::PathMetadataFlags::PATH_METADATA_INCLUDE_ALL;
real_t path_desired_distance = 20.0;
real_t target_desired_distance = 10.0;
real_t radius = 10.0;
real_t neighbor_distance = 500.0;
int max_neighbors = 10;
real_t time_horizon_agents = 1.0;
real_t time_horizon_obstacles = 0.0;
real_t max_speed = 100.0;
real_t path_max_distance = 100.0;
bool simplify_path = false;
real_t simplify_epsilon = 0.0;
Vector2 target_position;
Ref<NavigationPathQueryParameters2D> navigation_query;
Ref<NavigationPathQueryResult2D> navigation_result;
int navigation_path_index = 0;
// the velocity result of the avoidance simulation step
Vector2 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
Vector2 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
Vector2 velocity_forced;
bool velocity_forced_submitted = false;
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;
float debug_path_custom_line_width = -1.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;
#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:
NavigationAgent2D();
virtual ~NavigationAgent2D();
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 NavigationPathQueryParameters2D::PathfindingAlgorithm p_pathfinding_algorithm);
NavigationPathQueryParameters2D::PathfindingAlgorithm get_pathfinding_algorithm() const {
return pathfinding_algorithm;
}
void set_path_postprocessing(const NavigationPathQueryParameters2D::PathPostProcessing p_path_postprocessing);
NavigationPathQueryParameters2D::PathPostProcessing get_path_postprocessing() const {
return path_postprocessing;
}
void set_path_metadata_flags(BitField<NavigationPathQueryParameters2D::PathMetadataFlags> p_flags);
BitField<NavigationPathQueryParameters2D::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_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(Vector2 p_position);
Vector2 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;
Vector2 get_next_path_position();
Ref<NavigationPathQueryResult2D> get_current_navigation_result() const { return navigation_result; }
const Vector<Vector2> &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();
Vector2 get_final_position();
void set_velocity(const Vector2 p_velocity);
Vector2 get_velocity() { return velocity; }
void set_velocity_forced(const Vector2 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;
void set_debug_path_custom_line_width(float p_line_width);
float get_debug_path_custom_line_width() const;
private:
bool _is_target_reachable() const;
Vector2 _get_final_position() const;
void _update_navigation();
void _advance_waypoints(const Vector2 &p_origin);
void _request_repath();
bool _is_last_waypoint() const;
void _move_to_next_waypoint();
bool _is_within_waypoint_distance(const Vector2 &p_origin) const;
bool _is_within_target_distance(const Vector2 &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
};
#endif // NAVIGATION_AGENT_2D_H

View file

@ -0,0 +1,368 @@
/**************************************************************************/
/* navigation_link_2d.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_2d.h"
#include "core/math/geometry_2d.h"
#include "scene/resources/world_2d.h"
#include "servers/navigation_server_2d.h"
#include "servers/navigation_server_3d.h"
void NavigationLink2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rid"), &NavigationLink2D::get_rid);
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationLink2D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationLink2D::is_enabled);
ClassDB::bind_method(D_METHOD("set_bidirectional", "bidirectional"), &NavigationLink2D::set_bidirectional);
ClassDB::bind_method(D_METHOD("is_bidirectional"), &NavigationLink2D::is_bidirectional);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationLink2D::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationLink2D::get_navigation_layers);
ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationLink2D::set_navigation_layer_value);
ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationLink2D::get_navigation_layer_value);
ClassDB::bind_method(D_METHOD("set_start_position", "position"), &NavigationLink2D::set_start_position);
ClassDB::bind_method(D_METHOD("get_start_position"), &NavigationLink2D::get_start_position);
ClassDB::bind_method(D_METHOD("set_end_position", "position"), &NavigationLink2D::set_end_position);
ClassDB::bind_method(D_METHOD("get_end_position"), &NavigationLink2D::get_end_position);
ClassDB::bind_method(D_METHOD("set_global_start_position", "position"), &NavigationLink2D::set_global_start_position);
ClassDB::bind_method(D_METHOD("get_global_start_position"), &NavigationLink2D::get_global_start_position);
ClassDB::bind_method(D_METHOD("set_global_end_position", "position"), &NavigationLink2D::set_global_end_position);
ClassDB::bind_method(D_METHOD("get_global_end_position"), &NavigationLink2D::get_global_end_position);
ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationLink2D::set_enter_cost);
ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationLink2D::get_enter_cost);
ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationLink2D::set_travel_cost);
ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationLink2D::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_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "start_position"), "set_start_position", "get_start_position");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "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 NavigationLink2D::_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 NavigationLink2D::_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 NavigationLink2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (enabled) {
NavigationServer2D::get_singleton()->link_set_map(link, get_world_2d()->get_navigation_map());
}
current_global_transform = get_global_transform();
NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
set_physics_process_internal(true);
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
set_physics_process_internal(false);
if (is_inside_tree()) {
Transform2D new_global_transform = get_global_transform();
if (current_global_transform != new_global_transform) {
current_global_transform = new_global_transform;
NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
queue_redraw();
}
}
} break;
case NOTIFICATION_EXIT_TREE: {
NavigationServer2D::get_singleton()->link_set_map(link, RID());
} break;
case NOTIFICATION_DRAW: {
#ifdef DEBUG_ENABLED
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled())) {
Color color;
if (enabled) {
color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_color();
} else {
color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_disabled_color();
}
real_t radius = NavigationServer2D::get_singleton()->map_get_link_connection_radius(get_world_2d()->get_navigation_map());
draw_line(get_start_position(), get_end_position(), color);
draw_arc(get_start_position(), radius, 0, Math_TAU, 10, color);
draw_arc(get_end_position(), radius, 0, Math_TAU, 10, color);
}
#endif // DEBUG_ENABLED
} break;
}
}
#ifdef TOOLS_ENABLED
Rect2 NavigationLink2D::_edit_get_rect() const {
if (!is_inside_tree()) {
return Rect2();
}
real_t radius = NavigationServer2D::get_singleton()->map_get_link_connection_radius(get_world_2d()->get_navigation_map());
Rect2 rect(get_start_position(), Size2());
rect.expand_to(get_end_position());
rect.grow_by(radius);
return rect;
}
bool NavigationLink2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
Point2 segment[2] = { get_start_position(), get_end_position() };
Vector2 closest_point = Geometry2D::get_closest_point_to_segment(p_point, segment);
return p_point.distance_to(closest_point) < p_tolerance;
}
#endif // TOOLS_ENABLED
RID NavigationLink2D::get_rid() const {
return link;
}
void NavigationLink2D::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 (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) {
queue_redraw();
}
#endif // DEBUG_ENABLED
}
void NavigationLink2D::set_bidirectional(bool p_bidirectional) {
if (bidirectional == p_bidirectional) {
return;
}
bidirectional = p_bidirectional;
NavigationServer2D::get_singleton()->link_set_bidirectional(link, bidirectional);
}
void NavigationLink2D::set_navigation_layers(uint32_t p_navigation_layers) {
if (navigation_layers == p_navigation_layers) {
return;
}
navigation_layers = p_navigation_layers;
NavigationServer2D::get_singleton()->link_set_navigation_layers(link, navigation_layers);
}
void NavigationLink2D::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 NavigationLink2D::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 NavigationLink2D::set_start_position(Vector2 p_position) {
if (start_position.is_equal_approx(p_position)) {
return;
}
start_position = p_position;
if (!is_inside_tree()) {
return;
}
NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
update_configuration_warnings();
#ifdef DEBUG_ENABLED
if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) {
queue_redraw();
}
#endif // DEBUG_ENABLED
}
void NavigationLink2D::set_end_position(Vector2 p_position) {
if (end_position.is_equal_approx(p_position)) {
return;
}
end_position = p_position;
if (!is_inside_tree()) {
return;
}
NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
update_configuration_warnings();
#ifdef DEBUG_ENABLED
if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) {
queue_redraw();
}
#endif // DEBUG_ENABLED
}
void NavigationLink2D::set_global_start_position(Vector2 p_position) {
if (is_inside_tree()) {
set_start_position(to_local(p_position));
} else {
set_start_position(p_position);
}
}
Vector2 NavigationLink2D::get_global_start_position() const {
if (is_inside_tree()) {
return to_global(start_position);
} else {
return start_position;
}
}
void NavigationLink2D::set_global_end_position(Vector2 p_position) {
if (is_inside_tree()) {
set_end_position(to_local(p_position));
} else {
set_end_position(p_position);
}
}
Vector2 NavigationLink2D::get_global_end_position() const {
if (is_inside_tree()) {
return to_global(end_position);
} else {
return end_position;
}
}
void NavigationLink2D::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;
NavigationServer2D::get_singleton()->link_set_enter_cost(link, enter_cost);
}
void NavigationLink2D::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;
NavigationServer2D::get_singleton()->link_set_travel_cost(link, travel_cost);
}
PackedStringArray NavigationLink2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (start_position.is_equal_approx(end_position)) {
warnings.push_back(RTR("NavigationLink2D start position should be different than the end position to be useful."));
}
return warnings;
}
NavigationLink2D::NavigationLink2D() {
link = NavigationServer2D::get_singleton()->link_create();
NavigationServer2D::get_singleton()->link_set_owner_id(link, get_instance_id());
NavigationServer2D::get_singleton()->link_set_enter_cost(link, enter_cost);
NavigationServer2D::get_singleton()->link_set_travel_cost(link, travel_cost);
NavigationServer2D::get_singleton()->link_set_navigation_layers(link, navigation_layers);
NavigationServer2D::get_singleton()->link_set_bidirectional(link, bidirectional);
NavigationServer2D::get_singleton()->link_set_enabled(link, enabled);
set_notify_transform(true);
set_hide_clip_children(true);
}
NavigationLink2D::~NavigationLink2D() {
ERR_FAIL_NULL(NavigationServer2D::get_singleton());
NavigationServer2D::get_singleton()->free(link);
link = RID();
}

View file

@ -0,0 +1,102 @@
/**************************************************************************/
/* navigation_link_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef NAVIGATION_LINK_2D_H
#define NAVIGATION_LINK_2D_H
#include "scene/2d/node_2d.h"
class NavigationLink2D : public Node2D {
GDCLASS(NavigationLink2D, Node2D);
bool enabled = true;
RID link;
bool bidirectional = true;
uint32_t navigation_layers = 1;
Vector2 end_position;
Vector2 start_position;
real_t enter_cost = 0.0;
real_t travel_cost = 1.0;
Transform2D current_global_transform;
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:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#endif
RID get_rid() const;
void set_enabled(bool p_enabled);
bool is_enabled() const { return enabled; }
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(Vector2 p_position);
Vector2 get_start_position() const { return start_position; }
void set_end_position(Vector2 p_position);
Vector2 get_end_position() const { return end_position; }
void set_global_start_position(Vector2 p_position);
Vector2 get_global_start_position() const;
void set_global_end_position(Vector2 p_position);
Vector2 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;
NavigationLink2D();
~NavigationLink2D();
};
#endif // NAVIGATION_LINK_2D_H

View file

@ -0,0 +1,368 @@
/**************************************************************************/
/* navigation_obstacle_2d.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_2d.h"
#include "core/math/geometry_2d.h"
#include "scene/resources/world_2d.h"
#include "servers/navigation_server_2d.h"
#include "servers/navigation_server_3d.h"
void NavigationObstacle2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rid"), &NavigationObstacle2D::get_rid);
ClassDB::bind_method(D_METHOD("set_avoidance_enabled", "enabled"), &NavigationObstacle2D::set_avoidance_enabled);
ClassDB::bind_method(D_METHOD("get_avoidance_enabled"), &NavigationObstacle2D::get_avoidance_enabled);
ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationObstacle2D::set_navigation_map);
ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationObstacle2D::get_navigation_map);
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationObstacle2D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &NavigationObstacle2D::get_radius);
ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &NavigationObstacle2D::set_velocity);
ClassDB::bind_method(D_METHOD("get_velocity"), &NavigationObstacle2D::get_velocity);
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationObstacle2D::set_vertices);
ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationObstacle2D::get_vertices);
ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationObstacle2D::set_avoidance_layers);
ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationObstacle2D::get_avoidance_layers);
ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationObstacle2D::set_avoidance_layer_value);
ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationObstacle2D::get_avoidance_layer_value);
ClassDB::bind_method(D_METHOD("set_affect_navigation_mesh", "enabled"), &NavigationObstacle2D::set_affect_navigation_mesh);
ClassDB::bind_method(D_METHOD("get_affect_navigation_mesh"), &NavigationObstacle2D::get_affect_navigation_mesh);
ClassDB::bind_method(D_METHOD("set_carve_navigation_mesh", "enabled"), &NavigationObstacle2D::set_carve_navigation_mesh);
ClassDB::bind_method(D_METHOD("get_carve_navigation_mesh"), &NavigationObstacle2D::get_carve_navigation_mesh);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,500,0.01,suffix:px"), "set_radius", "get_radius");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_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::VECTOR2, "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");
}
void NavigationObstacle2D::_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_2d()->get_navigation_map());
} else {
_update_map(RID());
}
previous_transform = get_global_transform();
// need to trigger map controlled agent assignment somehow for the fake_agent since obstacles use no callback like regular agents
NavigationServer2D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
_update_position(get_global_position());
set_physics_process_internal(true);
#ifdef DEBUG_ENABLED
RS::get_singleton()->canvas_item_set_parent(debug_canvas_item, get_world_2d()->get_canvas());
#endif // DEBUG_ENABLED
} break;
case NOTIFICATION_EXIT_TREE: {
set_physics_process_internal(false);
_update_map(RID());
#ifdef DEBUG_ENABLED
RS::get_singleton()->canvas_item_set_parent(debug_canvas_item, RID());
#endif // DEBUG_ENABLED
} break;
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();
}
NavigationServer2D::get_singleton()->obstacle_set_paused(obstacle, !can_process());
} break;
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();
}
NavigationServer2D::get_singleton()->obstacle_set_paused(obstacle, !can_process());
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
#ifdef DEBUG_ENABLED
RS::get_singleton()->canvas_item_set_visible(debug_canvas_item, is_visible_in_tree());
#endif // DEBUG_ENABLED
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (is_inside_tree()) {
_update_position(get_global_position());
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)) {
NavigationServer2D::get_singleton()->obstacle_set_velocity(obstacle, velocity);
}
previous_velocity = velocity;
}
}
} break;
case NOTIFICATION_DRAW: {
#ifdef DEBUG_ENABLED
if (is_inside_tree()) {
bool is_debug_enabled = false;
if (Engine::get_singleton()->is_editor_hint()) {
is_debug_enabled = true;
} else if (NavigationServer2D::get_singleton()->get_debug_enabled() && NavigationServer2D::get_singleton()->get_debug_avoidance_enabled()) {
is_debug_enabled = true;
}
if (is_debug_enabled) {
RS::get_singleton()->canvas_item_clear(debug_canvas_item);
Transform2D debug_transform = Transform2D(0.0, get_global_position());
RS::get_singleton()->canvas_item_set_transform(debug_canvas_item, debug_transform);
_update_fake_agent_radius_debug();
_update_static_obstacle_debug();
}
}
#endif // DEBUG_ENABLED
} break;
}
}
NavigationObstacle2D::NavigationObstacle2D() {
obstacle = NavigationServer2D::get_singleton()->obstacle_create();
NavigationServer2D::get_singleton()->obstacle_set_radius(obstacle, radius);
NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle, vertices);
NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
NavigationServer2D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
#ifdef DEBUG_ENABLED
debug_canvas_item = RenderingServer::get_singleton()->canvas_item_create();
#endif // DEBUG_ENABLED
}
NavigationObstacle2D::~NavigationObstacle2D() {
ERR_FAIL_NULL(NavigationServer2D::get_singleton());
NavigationServer2D::get_singleton()->free(obstacle);
obstacle = RID();
#ifdef DEBUG_ENABLED
if (debug_canvas_item.is_valid()) {
RenderingServer::get_singleton()->free(debug_canvas_item);
debug_canvas_item = RID();
}
#endif // DEBUG_ENABLED
}
void NavigationObstacle2D::set_vertices(const Vector<Vector2> &p_vertices) {
vertices = p_vertices;
NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle, vertices);
#ifdef DEBUG_ENABLED
queue_redraw();
#endif // DEBUG_ENABLED
}
void NavigationObstacle2D::set_navigation_map(RID p_navigation_map) {
if (map_override == p_navigation_map) {
return;
}
map_override = p_navigation_map;
_update_map(map_override);
}
RID NavigationObstacle2D::get_navigation_map() const {
if (map_override.is_valid()) {
return map_override;
} else if (is_inside_tree()) {
return get_world_2d()->get_navigation_map();
}
return RID();
}
void NavigationObstacle2D::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;
NavigationServer2D::get_singleton()->obstacle_set_radius(obstacle, radius);
#ifdef DEBUG_ENABLED
queue_redraw();
#endif // DEBUG_ENABLED
}
void NavigationObstacle2D::set_avoidance_layers(uint32_t p_layers) {
if (avoidance_layers == p_layers) {
return;
}
avoidance_layers = p_layers;
NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
}
uint32_t NavigationObstacle2D::get_avoidance_layers() const {
return avoidance_layers;
}
void NavigationObstacle2D::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 NavigationObstacle2D::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 NavigationObstacle2D::set_avoidance_enabled(bool p_enabled) {
if (avoidance_enabled == p_enabled) {
return;
}
avoidance_enabled = p_enabled;
NavigationServer2D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
#ifdef DEBUG_ENABLED
queue_redraw();
#endif // DEBUG_ENABLED
}
bool NavigationObstacle2D::get_avoidance_enabled() const {
return avoidance_enabled;
}
void NavigationObstacle2D::set_velocity(const Vector2 p_velocity) {
velocity = p_velocity;
velocity_submitted = true;
}
void NavigationObstacle2D::set_affect_navigation_mesh(bool p_enabled) {
affect_navigation_mesh = p_enabled;
}
bool NavigationObstacle2D::get_affect_navigation_mesh() const {
return affect_navigation_mesh;
}
void NavigationObstacle2D::set_carve_navigation_mesh(bool p_enabled) {
carve_navigation_mesh = p_enabled;
}
bool NavigationObstacle2D::get_carve_navigation_mesh() const {
return carve_navigation_mesh;
}
void NavigationObstacle2D::_update_map(RID p_map) {
map_current = p_map;
NavigationServer2D::get_singleton()->obstacle_set_map(obstacle, p_map);
}
void NavigationObstacle2D::_update_position(const Vector2 p_position) {
NavigationServer2D::get_singleton()->obstacle_set_position(obstacle, p_position);
#ifdef DEBUG_ENABLED
queue_redraw();
#endif // DEBUG_ENABLED
}
#ifdef DEBUG_ENABLED
void NavigationObstacle2D::_update_fake_agent_radius_debug() {
if (radius > 0.0 && NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius()) {
Color debug_radius_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_obstacles_radius_color();
RS::get_singleton()->canvas_item_add_circle(debug_canvas_item, Vector2(), radius, debug_radius_color);
}
}
#endif // DEBUG_ENABLED
#ifdef DEBUG_ENABLED
void NavigationObstacle2D::_update_static_obstacle_debug() {
if (get_vertices().size() > 2 && NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_static()) {
bool obstacle_pushes_inward = Geometry2D::is_polygon_clockwise(get_vertices());
Color debug_static_obstacle_face_color;
if (obstacle_pushes_inward) {
debug_static_obstacle_face_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_face_color();
} else {
debug_static_obstacle_face_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_face_color();
}
Vector<Vector2> debug_obstacle_polygon_vertices = get_vertices();
Vector<Color> debug_obstacle_polygon_colors;
debug_obstacle_polygon_colors.resize(debug_obstacle_polygon_vertices.size());
debug_obstacle_polygon_colors.fill(debug_static_obstacle_face_color);
RS::get_singleton()->canvas_item_add_polygon(debug_canvas_item, debug_obstacle_polygon_vertices, debug_obstacle_polygon_colors);
Color debug_static_obstacle_edge_color;
if (obstacle_pushes_inward) {
debug_static_obstacle_edge_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_edge_color();
} else {
debug_static_obstacle_edge_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_edge_color();
}
Vector<Vector2> debug_obstacle_line_vertices = get_vertices();
debug_obstacle_line_vertices.push_back(debug_obstacle_line_vertices[0]);
debug_obstacle_line_vertices.resize(debug_obstacle_line_vertices.size());
Vector<Color> debug_obstacle_line_colors;
debug_obstacle_line_colors.resize(debug_obstacle_line_vertices.size());
debug_obstacle_line_colors.fill(debug_static_obstacle_edge_color);
RS::get_singleton()->canvas_item_add_polyline(debug_canvas_item, debug_obstacle_line_vertices, debug_obstacle_line_colors, 4.0);
}
}
#endif // DEBUG_ENABLED

View file

@ -0,0 +1,114 @@
/**************************************************************************/
/* navigation_obstacle_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef NAVIGATION_OBSTACLE_2D_H
#define NAVIGATION_OBSTACLE_2D_H
#include "scene/2d/node_2d.h"
class NavigationObstacle2D : public Node2D {
GDCLASS(NavigationObstacle2D, Node2D);
RID obstacle;
RID map_before_pause;
RID map_override;
RID map_current;
real_t radius = 0.0;
Vector<Vector2> vertices;
bool avoidance_enabled = true;
uint32_t avoidance_layers = 1;
Transform2D previous_transform;
Vector2 velocity;
Vector2 previous_velocity;
bool velocity_submitted = false;
bool affect_navigation_mesh = false;
bool carve_navigation_mesh = false;
#ifdef DEBUG_ENABLED
private:
RID debug_canvas_item;
void _update_fake_agent_radius_debug();
void _update_static_obstacle_debug();
#endif // DEBUG_ENABLED
protected:
static void _bind_methods();
void _notification(int p_what);
public:
NavigationObstacle2D();
virtual ~NavigationObstacle2D();
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_vertices(const Vector<Vector2> &p_vertices);
const Vector<Vector2> &get_vertices() const { return vertices; };
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_velocity(const Vector2 p_velocity);
Vector2 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;
private:
void _update_map(RID p_map);
void _update_position(const Vector2 p_position);
};
#endif // NAVIGATION_OBSTACLE_2D_H

View file

@ -0,0 +1,645 @@
/**************************************************************************/
/* navigation_region_2d.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_2d.h"
#include "core/math/geometry_2d.h"
#include "scene/resources/world_2d.h"
#include "servers/navigation_server_2d.h"
RID NavigationRegion2D::get_rid() const {
return region;
}
void NavigationRegion2D::set_enabled(bool p_enabled) {
if (enabled == p_enabled) {
return;
}
enabled = p_enabled;
NavigationServer2D::get_singleton()->region_set_enabled(region, enabled);
#ifdef DEBUG_ENABLED
if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_navigation_enabled()) {
queue_redraw();
}
#endif // DEBUG_ENABLED
}
bool NavigationRegion2D::is_enabled() const {
return enabled;
}
void NavigationRegion2D::set_use_edge_connections(bool p_enabled) {
if (use_edge_connections == p_enabled) {
return;
}
use_edge_connections = p_enabled;
NavigationServer2D::get_singleton()->region_set_use_edge_connections(region, use_edge_connections);
}
bool NavigationRegion2D::get_use_edge_connections() const {
return use_edge_connections;
}
void NavigationRegion2D::set_navigation_layers(uint32_t p_navigation_layers) {
if (navigation_layers == p_navigation_layers) {
return;
}
navigation_layers = p_navigation_layers;
NavigationServer2D::get_singleton()->region_set_navigation_layers(region, navigation_layers);
}
uint32_t NavigationRegion2D::get_navigation_layers() const {
return navigation_layers;
}
void NavigationRegion2D::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 NavigationRegion2D::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 NavigationRegion2D::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;
NavigationServer2D::get_singleton()->region_set_enter_cost(region, enter_cost);
}
real_t NavigationRegion2D::get_enter_cost() const {
return enter_cost;
}
void NavigationRegion2D::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;
NavigationServer2D::get_singleton()->region_set_travel_cost(region, travel_cost);
}
real_t NavigationRegion2D::get_travel_cost() const {
return travel_cost;
}
RID NavigationRegion2D::get_region_rid() const {
return get_rid();
}
#ifdef TOOLS_ENABLED
Rect2 NavigationRegion2D::_edit_get_rect() const {
return navigation_polygon.is_valid() ? navigation_polygon->_edit_get_rect() : Rect2();
}
bool NavigationRegion2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return navigation_polygon.is_valid() ? navigation_polygon->_edit_is_selected_on_click(p_point, p_tolerance) : false;
}
#endif
void NavigationRegion2D::_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_VISIBILITY_CHANGED: {
#ifdef DEBUG_ENABLED
if (debug_instance_rid.is_valid()) {
RS::get_singleton()->canvas_item_set_visible(debug_instance_rid, is_visible_in_tree());
}
#endif // DEBUG_ENABLED
} break;
case NOTIFICATION_EXIT_TREE: {
_region_exit_navigation_map();
#ifdef DEBUG_ENABLED
_free_debug();
#endif // DEBUG_ENABLED
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
set_physics_process_internal(false);
_region_update_transform();
} break;
case NOTIFICATION_DRAW: {
#ifdef DEBUG_ENABLED
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) && navigation_polygon.is_valid()) {
_update_debug_mesh();
_update_debug_edge_connections_mesh();
_update_debug_baking_rect();
}
#endif // DEBUG_ENABLED
} break;
}
}
void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_navigation_polygon) {
if (navigation_polygon.is_valid()) {
navigation_polygon->disconnect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed));
}
navigation_polygon = p_navigation_polygon;
#ifdef DEBUG_ENABLED
debug_mesh_dirty = true;
#endif // DEBUG_ENABLED
NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, p_navigation_polygon);
if (navigation_polygon.is_valid()) {
navigation_polygon->connect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed));
}
_navigation_polygon_changed();
update_configuration_warnings();
}
Ref<NavigationPolygon> NavigationRegion2D::get_navigation_polygon() const {
return navigation_polygon;
}
void NavigationRegion2D::set_navigation_map(RID p_navigation_map) {
if (map_override == p_navigation_map) {
return;
}
map_override = p_navigation_map;
NavigationServer2D::get_singleton()->region_set_map(region, map_override);
}
RID NavigationRegion2D::get_navigation_map() const {
if (map_override.is_valid()) {
return map_override;
} else if (is_inside_tree()) {
return get_world_2d()->get_navigation_map();
}
return RID();
}
void NavigationRegion2D::bake_navigation_polygon(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_polygon.is_valid(), "Baking the navigation polygon requires a valid `NavigationPolygon` resource.");
Ref<NavigationMeshSourceGeometryData2D> source_geometry_data;
source_geometry_data.instantiate();
NavigationServer2D::get_singleton()->parse_source_geometry_data(navigation_polygon, source_geometry_data, this);
if (p_on_thread) {
NavigationServer2D::get_singleton()->bake_from_source_geometry_data_async(navigation_polygon, source_geometry_data, callable_mp(this, &NavigationRegion2D::_bake_finished).bind(navigation_polygon));
} else {
NavigationServer2D::get_singleton()->bake_from_source_geometry_data(navigation_polygon, source_geometry_data, callable_mp(this, &NavigationRegion2D::_bake_finished).bind(navigation_polygon));
}
}
void NavigationRegion2D::_bake_finished(Ref<NavigationPolygon> p_navigation_polygon) {
if (!Thread::is_main_thread()) {
callable_mp(this, &NavigationRegion2D::_bake_finished).call_deferred(p_navigation_polygon);
return;
}
set_navigation_polygon(p_navigation_polygon);
emit_signal(SNAME("bake_finished"));
}
bool NavigationRegion2D::is_baking() const {
return NavigationServer2D::get_singleton()->is_baking_navigation_polygon(navigation_polygon);
}
void NavigationRegion2D::_navigation_polygon_changed() {
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) {
queue_redraw();
}
if (navigation_polygon.is_valid()) {
NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, navigation_polygon);
}
}
#ifdef DEBUG_ENABLED
void NavigationRegion2D::_navigation_map_changed(RID p_map) {
if (is_inside_tree() && get_world_2d()->get_navigation_map() == p_map) {
queue_redraw();
}
}
#endif // DEBUG_ENABLED
#ifdef DEBUG_ENABLED
void NavigationRegion2D::_navigation_debug_changed() {
if (is_inside_tree()) {
queue_redraw();
}
}
#endif // DEBUG_ENABLED
PackedStringArray NavigationRegion2D::get_configuration_warnings() const {
PackedStringArray warnings = Node2D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!navigation_polygon.is_valid()) {
warnings.push_back(RTR("A NavigationMesh resource must be set or created for this node to work. Please set a property or draw a polygon."));
}
}
return warnings;
}
void NavigationRegion2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rid"), &NavigationRegion2D::get_rid);
ClassDB::bind_method(D_METHOD("set_navigation_polygon", "navigation_polygon"), &NavigationRegion2D::set_navigation_polygon);
ClassDB::bind_method(D_METHOD("get_navigation_polygon"), &NavigationRegion2D::get_navigation_polygon);
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationRegion2D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationRegion2D::is_enabled);
ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationRegion2D::set_navigation_map);
ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationRegion2D::get_navigation_map);
ClassDB::bind_method(D_METHOD("set_use_edge_connections", "enabled"), &NavigationRegion2D::set_use_edge_connections);
ClassDB::bind_method(D_METHOD("get_use_edge_connections"), &NavigationRegion2D::get_use_edge_connections);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationRegion2D::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationRegion2D::get_navigation_layers);
ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationRegion2D::set_navigation_layer_value);
ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationRegion2D::get_navigation_layer_value);
ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationRegion2D::get_region_rid);
ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationRegion2D::set_enter_cost);
ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationRegion2D::get_enter_cost);
ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationRegion2D::set_travel_cost);
ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationRegion2D::get_travel_cost);
ClassDB::bind_method(D_METHOD("bake_navigation_polygon", "on_thread"), &NavigationRegion2D::bake_navigation_polygon, DEFVAL(true));
ClassDB::bind_method(D_METHOD("is_baking"), &NavigationRegion2D::is_baking);
ClassDB::bind_method(D_METHOD("_navigation_polygon_changed"), &NavigationRegion2D::_navigation_polygon_changed);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navigation_polygon", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon"), "set_navigation_polygon", "get_navigation_polygon");
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_2D_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_polygon_changed"));
ADD_SIGNAL(MethodInfo("bake_finished"));
}
#ifndef DISABLE_DEPRECATED
// Compatibility with earlier 4.0 betas.
bool NavigationRegion2D::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "navpoly") {
set_navigation_polygon(p_value);
return true;
}
return false;
}
bool NavigationRegion2D::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "navpoly") {
r_ret = get_navigation_polygon();
return true;
}
return false;
}
#endif // DISABLE_DEPRECATED
NavigationRegion2D::NavigationRegion2D() {
set_notify_transform(true);
set_hide_clip_children(true);
region = NavigationServer2D::get_singleton()->region_create();
NavigationServer2D::get_singleton()->region_set_owner_id(region, get_instance_id());
NavigationServer2D::get_singleton()->region_set_enter_cost(region, get_enter_cost());
NavigationServer2D::get_singleton()->region_set_travel_cost(region, get_travel_cost());
NavigationServer2D::get_singleton()->region_set_navigation_layers(region, navigation_layers);
NavigationServer2D::get_singleton()->region_set_use_edge_connections(region, use_edge_connections);
NavigationServer2D::get_singleton()->region_set_enabled(region, enabled);
#ifdef DEBUG_ENABLED
NavigationServer2D::get_singleton()->connect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed));
NavigationServer2D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion2D::_navigation_debug_changed));
#endif // DEBUG_ENABLED
}
NavigationRegion2D::~NavigationRegion2D() {
ERR_FAIL_NULL(NavigationServer2D::get_singleton());
NavigationServer2D::get_singleton()->free(region);
#ifdef DEBUG_ENABLED
NavigationServer2D::get_singleton()->disconnect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed));
NavigationServer2D::get_singleton()->disconnect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion2D::_navigation_debug_changed));
#endif // DEBUG_ENABLED
}
void NavigationRegion2D::_region_enter_navigation_map() {
if (!is_inside_tree()) {
return;
}
if (map_override.is_valid()) {
NavigationServer2D::get_singleton()->region_set_map(region, map_override);
} else {
NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map());
}
current_global_transform = get_global_transform();
NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform);
NavigationServer2D::get_singleton()->region_set_enabled(region, enabled);
queue_redraw();
}
void NavigationRegion2D::_region_exit_navigation_map() {
NavigationServer2D::get_singleton()->region_set_map(region, RID());
}
void NavigationRegion2D::_region_update_transform() {
if (!is_inside_tree()) {
return;
}
Transform2D new_global_transform = get_global_transform();
if (current_global_transform != new_global_transform) {
current_global_transform = new_global_transform;
NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform);
}
queue_redraw();
}
#ifdef DEBUG_ENABLED
void NavigationRegion2D::_update_debug_mesh() {
if (!is_inside_tree()) {
_free_debug();
return;
}
const NavigationServer2D *ns2d = NavigationServer2D::get_singleton();
RenderingServer *rs = RenderingServer::get_singleton();
if (!debug_instance_rid.is_valid()) {
debug_instance_rid = rs->canvas_item_create();
}
if (!debug_mesh_rid.is_valid()) {
debug_mesh_rid = rs->mesh_create();
}
const Transform2D region_gt = get_global_transform();
rs->canvas_item_set_parent(debug_instance_rid, get_world_2d()->get_canvas());
rs->canvas_item_set_transform(debug_instance_rid, region_gt);
if (!debug_mesh_dirty) {
return;
}
rs->canvas_item_clear(debug_instance_rid);
rs->mesh_clear(debug_mesh_rid);
debug_mesh_dirty = false;
const Vector<Vector2> &vertices = navigation_polygon->get_vertices();
if (vertices.size() < 3) {
return;
}
int polygon_count = navigation_polygon->get_polygon_count();
if (polygon_count == 0) {
return;
}
bool enabled_geometry_face_random_color = ns2d->get_debug_navigation_enable_geometry_face_random_color();
bool enabled_edge_lines = ns2d->get_debug_navigation_enable_edge_lines();
Color debug_face_color = ns2d->get_debug_navigation_geometry_face_color();
Color debug_edge_color = ns2d->get_debug_navigation_geometry_edge_color();
if (!enabled) {
debug_face_color = ns2d->get_debug_navigation_geometry_face_disabled_color();
debug_edge_color = ns2d->get_debug_navigation_geometry_edge_disabled_color();
}
int vertex_count = 0;
int line_count = 0;
for (int i = 0; i < polygon_count; i++) {
const Vector<int> &polygon = navigation_polygon->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<Vector2> 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<Vector2> line_vertex_array;
if (enabled_edge_lines) {
line_vertex_array.resize(line_count);
}
RandomPCG rand;
Color polygon_color = debug_face_color;
int face_vertex_index = 0;
int line_vertex_index = 0;
Vector2 *face_vertex_array_ptrw = face_vertex_array.ptrw();
Color *face_color_array_ptrw = face_color_array.ptrw();
Vector2 *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_polygon->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_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, debug_face_color.get_s(), debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2);
polygon_color.a = debug_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;
}
}
}
}
if (!enabled_geometry_face_random_color) {
face_color_array.resize(face_vertex_array.size());
face_color_array.fill(debug_face_color);
}
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_COLOR] = face_color_array;
rs->mesh_add_surface_from_arrays(debug_mesh_rid, RS::PRIMITIVE_TRIANGLES, face_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
if (enabled_edge_lines) {
Vector<Color> line_color_array;
line_color_array.resize(line_vertex_array.size());
line_color_array.fill(debug_edge_color);
Array line_mesh_array;
line_mesh_array.resize(Mesh::ARRAY_MAX);
line_mesh_array[Mesh::ARRAY_VERTEX] = line_vertex_array;
line_mesh_array[Mesh::ARRAY_COLOR] = line_color_array;
rs->mesh_add_surface_from_arrays(debug_mesh_rid, RS::PRIMITIVE_LINES, line_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
}
rs->canvas_item_add_mesh(debug_instance_rid, debug_mesh_rid, Transform2D());
rs->canvas_item_set_visible(debug_instance_rid, is_visible_in_tree());
}
#endif // DEBUG_ENABLED
#ifdef DEBUG_ENABLED
void NavigationRegion2D::_update_debug_edge_connections_mesh() {
const NavigationServer2D *ns2d = NavigationServer2D::get_singleton();
bool enable_edge_connections = use_edge_connections && ns2d->get_debug_navigation_enable_edge_connections() && ns2d->map_get_use_edge_connections(get_world_2d()->get_navigation_map());
if (enable_edge_connections) {
Color debug_edge_connection_color = ns2d->get_debug_navigation_edge_connection_color();
// Draw the region edge connections.
Transform2D xform = get_global_transform();
real_t radius = ns2d->map_get_edge_connection_margin(get_world_2d()->get_navigation_map()) / 2.0;
for (int i = 0; i < ns2d->region_get_connections_count(region); i++) {
// Two main points
Vector2 a = ns2d->region_get_connection_pathway_start(region, i);
a = xform.affine_inverse().xform(a);
Vector2 b = ns2d->region_get_connection_pathway_end(region, i);
b = xform.affine_inverse().xform(b);
draw_line(a, b, debug_edge_connection_color);
// Draw a circle to illustrate the margins.
real_t angle = a.angle_to_point(b);
draw_arc(a, radius, angle + Math_PI / 2.0, angle - Math_PI / 2.0 + Math_TAU, 10, debug_edge_connection_color);
draw_arc(b, radius, angle - Math_PI / 2.0, angle + Math_PI / 2.0, 10, debug_edge_connection_color);
}
}
}
#endif // DEBUG_ENABLED
#ifdef DEBUG_ENABLED
void NavigationRegion2D::_update_debug_baking_rect() {
Rect2 baking_rect = get_navigation_polygon()->get_baking_rect();
if (baking_rect.has_area()) {
Vector2 baking_rect_offset = get_navigation_polygon()->get_baking_rect_offset();
Rect2 debug_baking_rect = Rect2(baking_rect.position.x + baking_rect_offset.x, baking_rect.position.y + baking_rect_offset.y, baking_rect.size.x, baking_rect.size.y);
Color debug_baking_rect_color = Color(0.8, 0.5, 0.7, 0.1);
draw_rect(debug_baking_rect, debug_baking_rect_color);
}
}
#endif // DEBUG_ENABLED
#ifdef DEBUG_ENABLED
void NavigationRegion2D::_free_debug() {
RenderingServer *rs = RenderingServer::get_singleton();
ERR_FAIL_NULL(rs);
if (debug_instance_rid.is_valid()) {
rs->canvas_item_clear(debug_instance_rid);
rs->free(debug_instance_rid);
debug_instance_rid = RID();
}
if (debug_mesh_rid.is_valid()) {
rs->free(debug_mesh_rid);
debug_mesh_rid = RID();
}
}
#endif // DEBUG_ENABLED

View file

@ -0,0 +1,125 @@
/**************************************************************************/
/* navigation_region_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef NAVIGATION_REGION_2D_H
#define NAVIGATION_REGION_2D_H
#include "scene/resources/2d/navigation_polygon.h"
class NavigationRegion2D : public Node2D {
GDCLASS(NavigationRegion2D, Node2D);
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<NavigationPolygon> navigation_polygon;
Transform2D current_global_transform;
void _navigation_polygon_changed();
#ifdef DEBUG_ENABLED
private:
RID debug_mesh_rid;
RID debug_instance_rid;
bool debug_mesh_dirty = true;
void _free_debug();
void _update_debug_mesh();
void _update_debug_edge_connections_mesh();
void _update_debug_baking_rect();
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:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#endif
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_polygon(const Ref<NavigationPolygon> &p_navigation_polygon);
Ref<NavigationPolygon> get_navigation_polygon() const;
PackedStringArray get_configuration_warnings() const override;
void bake_navigation_polygon(bool p_on_thread);
void _bake_finished(Ref<NavigationPolygon> p_navigation_polygon);
bool is_baking() const;
NavigationRegion2D();
~NavigationRegion2D();
private:
void _region_enter_navigation_map();
void _region_exit_navigation_map();
void _region_update_transform();
};
#endif // NAVIGATION_REGION_2D_H

503
engine/scene/2d/node_2d.cpp Normal file
View file

@ -0,0 +1,503 @@
/**************************************************************************/
/* node_2d.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 "node_2d.h"
#include "scene/main/viewport.h"
#ifdef TOOLS_ENABLED
Dictionary Node2D::_edit_get_state() const {
Dictionary state;
state["position"] = get_position();
state["rotation"] = get_rotation();
state["scale"] = get_scale();
state["skew"] = get_skew();
return state;
}
void Node2D::_edit_set_state(const Dictionary &p_state) {
position = p_state["position"];
rotation = p_state["rotation"];
scale = p_state["scale"];
skew = p_state["skew"];
_update_transform();
}
void Node2D::_edit_set_position(const Point2 &p_position) {
set_position(p_position);
}
Point2 Node2D::_edit_get_position() const {
return position;
}
void Node2D::_edit_set_scale(const Size2 &p_scale) {
set_scale(p_scale);
}
Size2 Node2D::_edit_get_scale() const {
return scale;
}
void Node2D::_edit_set_rotation(real_t p_rotation) {
rotation = p_rotation;
_update_transform();
}
real_t Node2D::_edit_get_rotation() const {
return rotation;
}
bool Node2D::_edit_use_rotation() const {
return true;
}
void Node2D::_edit_set_rect(const Rect2 &p_edit_rect) {
ERR_FAIL_COND(!_edit_use_rect());
Rect2 r = _edit_get_rect();
Vector2 zero_offset;
Size2 new_scale(1, 1);
if (r.size.x != 0) {
zero_offset.x = -r.position.x / r.size.x;
new_scale.x = p_edit_rect.size.x / r.size.x;
}
if (r.size.y != 0) {
zero_offset.y = -r.position.y / r.size.y;
new_scale.y = p_edit_rect.size.y / r.size.y;
}
Point2 new_pos = p_edit_rect.position + p_edit_rect.size * zero_offset;
Transform2D postxf;
postxf.set_rotation_scale_and_skew(rotation, scale, skew);
new_pos = postxf.xform(new_pos);
position += new_pos;
scale *= new_scale;
_update_transform();
}
#endif
void Node2D::_set_xform_dirty(bool p_dirty) const {
if (is_group_processing()) {
if (p_dirty) {
xform_dirty.mt.set();
} else {
xform_dirty.mt.clear();
}
} else {
xform_dirty.st = p_dirty;
}
}
void Node2D::_update_xform_values() const {
rotation = transform.get_rotation();
skew = transform.get_skew();
position = transform.columns[2];
scale = transform.get_scale();
_set_xform_dirty(false);
}
void Node2D::_update_transform() {
transform.set_rotation_scale_and_skew(rotation, scale, skew);
transform.columns[2] = position;
RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform);
_notify_transform();
}
void Node2D::reparent(Node *p_parent, bool p_keep_global_transform) {
ERR_THREAD_GUARD;
if (p_keep_global_transform) {
Transform2D temp = get_global_transform();
Node::reparent(p_parent);
set_global_transform(temp);
} else {
Node::reparent(p_parent);
}
}
void Node2D::set_position(const Point2 &p_pos) {
ERR_THREAD_GUARD;
if (_is_xform_dirty()) {
_update_xform_values();
}
position = p_pos;
_update_transform();
}
void Node2D::set_rotation(real_t p_radians) {
ERR_THREAD_GUARD;
if (_is_xform_dirty()) {
_update_xform_values();
}
rotation = p_radians;
_update_transform();
}
void Node2D::set_rotation_degrees(real_t p_degrees) {
ERR_THREAD_GUARD;
set_rotation(Math::deg_to_rad(p_degrees));
}
void Node2D::set_skew(real_t p_radians) {
ERR_THREAD_GUARD;
if (_is_xform_dirty()) {
_update_xform_values();
}
skew = p_radians;
_update_transform();
}
void Node2D::set_scale(const Size2 &p_scale) {
ERR_THREAD_GUARD;
if (_is_xform_dirty()) {
_update_xform_values();
}
scale = p_scale;
// Avoid having 0 scale values, can lead to errors in physics and rendering.
if (Math::is_zero_approx(scale.x)) {
scale.x = CMP_EPSILON;
}
if (Math::is_zero_approx(scale.y)) {
scale.y = CMP_EPSILON;
}
_update_transform();
}
Point2 Node2D::get_position() const {
ERR_READ_THREAD_GUARD_V(Point2());
if (_is_xform_dirty()) {
_update_xform_values();
}
return position;
}
real_t Node2D::get_rotation() const {
ERR_READ_THREAD_GUARD_V(0);
if (_is_xform_dirty()) {
_update_xform_values();
}
return rotation;
}
real_t Node2D::get_rotation_degrees() const {
ERR_READ_THREAD_GUARD_V(0);
return Math::rad_to_deg(get_rotation());
}
real_t Node2D::get_skew() const {
ERR_READ_THREAD_GUARD_V(0);
if (_is_xform_dirty()) {
_update_xform_values();
}
return skew;
}
Size2 Node2D::get_scale() const {
ERR_READ_THREAD_GUARD_V(Size2());
if (_is_xform_dirty()) {
_update_xform_values();
}
return scale;
}
Transform2D Node2D::get_transform() const {
ERR_READ_THREAD_GUARD_V(Transform2D());
return transform;
}
void Node2D::rotate(real_t p_radians) {
ERR_THREAD_GUARD;
set_rotation(get_rotation() + p_radians);
}
void Node2D::translate(const Vector2 &p_amount) {
ERR_THREAD_GUARD;
set_position(get_position() + p_amount);
}
void Node2D::global_translate(const Vector2 &p_amount) {
ERR_THREAD_GUARD;
set_global_position(get_global_position() + p_amount);
}
void Node2D::apply_scale(const Size2 &p_amount) {
ERR_THREAD_GUARD;
set_scale(get_scale() * p_amount);
}
void Node2D::move_x(real_t p_delta, bool p_scaled) {
ERR_THREAD_GUARD;
Transform2D t = get_transform();
Vector2 m = t[0];
if (!p_scaled) {
m.normalize();
}
set_position(t[2] + m * p_delta);
}
void Node2D::move_y(real_t p_delta, bool p_scaled) {
ERR_THREAD_GUARD;
Transform2D t = get_transform();
Vector2 m = t[1];
if (!p_scaled) {
m.normalize();
}
set_position(t[2] + m * p_delta);
}
Point2 Node2D::get_global_position() const {
ERR_READ_THREAD_GUARD_V(Point2());
return get_global_transform().get_origin();
}
void Node2D::set_global_position(const Point2 &p_pos) {
ERR_THREAD_GUARD;
CanvasItem *parent = get_parent_item();
if (parent) {
Transform2D inv = parent->get_global_transform().affine_inverse();
set_position(inv.xform(p_pos));
} else {
set_position(p_pos);
}
}
real_t Node2D::get_global_rotation() const {
ERR_READ_THREAD_GUARD_V(0);
return get_global_transform().get_rotation();
}
real_t Node2D::get_global_rotation_degrees() const {
ERR_READ_THREAD_GUARD_V(0);
return Math::rad_to_deg(get_global_rotation());
}
real_t Node2D::get_global_skew() const {
ERR_READ_THREAD_GUARD_V(0);
return get_global_transform().get_skew();
}
void Node2D::set_global_rotation(const real_t p_radians) {
ERR_THREAD_GUARD;
CanvasItem *parent = get_parent_item();
if (parent) {
Transform2D parent_global_transform = parent->get_global_transform();
Transform2D new_transform = parent_global_transform * get_transform();
new_transform.set_rotation(p_radians);
new_transform = parent_global_transform.affine_inverse() * new_transform;
set_rotation(new_transform.get_rotation());
} else {
set_rotation(p_radians);
}
}
void Node2D::set_global_rotation_degrees(const real_t p_degrees) {
ERR_THREAD_GUARD;
set_global_rotation(Math::deg_to_rad(p_degrees));
}
void Node2D::set_global_skew(const real_t p_radians) {
ERR_THREAD_GUARD;
CanvasItem *parent = get_parent_item();
if (parent) {
Transform2D parent_global_transform = parent->get_global_transform();
Transform2D new_transform = parent_global_transform * get_transform();
new_transform.set_skew(p_radians);
new_transform = parent_global_transform.affine_inverse() * new_transform;
set_skew(new_transform.get_skew());
} else {
set_skew(p_radians);
}
}
Size2 Node2D::get_global_scale() const {
ERR_READ_THREAD_GUARD_V(Size2());
return get_global_transform().get_scale();
}
void Node2D::set_global_scale(const Size2 &p_scale) {
ERR_THREAD_GUARD;
CanvasItem *parent = get_parent_item();
if (parent) {
Transform2D parent_global_transform = parent->get_global_transform();
Transform2D new_transform = parent_global_transform * get_transform();
new_transform.set_scale(p_scale);
new_transform = parent_global_transform.affine_inverse() * new_transform;
set_scale(new_transform.get_scale());
} else {
set_scale(p_scale);
}
}
void Node2D::set_transform(const Transform2D &p_transform) {
ERR_THREAD_GUARD;
transform = p_transform;
_set_xform_dirty(true);
RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform);
_notify_transform();
}
void Node2D::set_global_transform(const Transform2D &p_transform) {
ERR_THREAD_GUARD;
CanvasItem *parent = get_parent_item();
if (parent) {
set_transform(parent->get_global_transform().affine_inverse() * p_transform);
} else {
set_transform(p_transform);
}
}
Transform2D Node2D::get_relative_transform_to_parent(const Node *p_parent) const {
ERR_READ_THREAD_GUARD_V(Transform2D());
if (p_parent == this) {
return Transform2D();
}
Node2D *parent_2d = Object::cast_to<Node2D>(get_parent());
ERR_FAIL_NULL_V(parent_2d, Transform2D());
if (p_parent == parent_2d) {
return get_transform();
} else {
return parent_2d->get_relative_transform_to_parent(p_parent) * get_transform();
}
}
void Node2D::look_at(const Vector2 &p_pos) {
ERR_THREAD_GUARD;
rotate(get_angle_to(p_pos));
}
real_t Node2D::get_angle_to(const Vector2 &p_pos) const {
ERR_READ_THREAD_GUARD_V(0);
return (to_local(p_pos) * get_scale()).angle();
}
Point2 Node2D::to_local(Point2 p_global) const {
ERR_READ_THREAD_GUARD_V(Point2());
return get_global_transform().affine_inverse().xform(p_global);
}
Point2 Node2D::to_global(Point2 p_local) const {
ERR_READ_THREAD_GUARD_V(Point2());
return get_global_transform().xform(p_local);
}
void Node2D::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_ENTER_TREE: {
ERR_MAIN_THREAD_GUARD;
if (get_viewport()) {
get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty), CONNECT_REFERENCE_COUNTED);
}
} break;
case NOTIFICATION_EXIT_TREE: {
ERR_MAIN_THREAD_GUARD;
if (get_viewport()) {
get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty));
}
} break;
}
}
void Node2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_position", "position"), &Node2D::set_position);
ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &Node2D::set_rotation);
ClassDB::bind_method(D_METHOD("set_rotation_degrees", "degrees"), &Node2D::set_rotation_degrees);
ClassDB::bind_method(D_METHOD("set_skew", "radians"), &Node2D::set_skew);
ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Node2D::set_scale);
ClassDB::bind_method(D_METHOD("get_position"), &Node2D::get_position);
ClassDB::bind_method(D_METHOD("get_rotation"), &Node2D::get_rotation);
ClassDB::bind_method(D_METHOD("get_rotation_degrees"), &Node2D::get_rotation_degrees);
ClassDB::bind_method(D_METHOD("get_skew"), &Node2D::get_skew);
ClassDB::bind_method(D_METHOD("get_scale"), &Node2D::get_scale);
ClassDB::bind_method(D_METHOD("rotate", "radians"), &Node2D::rotate);
ClassDB::bind_method(D_METHOD("move_local_x", "delta", "scaled"), &Node2D::move_x, DEFVAL(false));
ClassDB::bind_method(D_METHOD("move_local_y", "delta", "scaled"), &Node2D::move_y, DEFVAL(false));
ClassDB::bind_method(D_METHOD("translate", "offset"), &Node2D::translate);
ClassDB::bind_method(D_METHOD("global_translate", "offset"), &Node2D::global_translate);
ClassDB::bind_method(D_METHOD("apply_scale", "ratio"), &Node2D::apply_scale);
ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Node2D::set_global_position);
ClassDB::bind_method(D_METHOD("get_global_position"), &Node2D::get_global_position);
ClassDB::bind_method(D_METHOD("set_global_rotation", "radians"), &Node2D::set_global_rotation);
ClassDB::bind_method(D_METHOD("set_global_rotation_degrees", "degrees"), &Node2D::set_global_rotation_degrees);
ClassDB::bind_method(D_METHOD("get_global_rotation"), &Node2D::get_global_rotation);
ClassDB::bind_method(D_METHOD("get_global_rotation_degrees"), &Node2D::get_global_rotation_degrees);
ClassDB::bind_method(D_METHOD("set_global_skew", "radians"), &Node2D::set_global_skew);
ClassDB::bind_method(D_METHOD("get_global_skew"), &Node2D::get_global_skew);
ClassDB::bind_method(D_METHOD("set_global_scale", "scale"), &Node2D::set_global_scale);
ClassDB::bind_method(D_METHOD("get_global_scale"), &Node2D::get_global_scale);
ClassDB::bind_method(D_METHOD("set_transform", "xform"), &Node2D::set_transform);
ClassDB::bind_method(D_METHOD("set_global_transform", "xform"), &Node2D::set_global_transform);
ClassDB::bind_method(D_METHOD("look_at", "point"), &Node2D::look_at);
ClassDB::bind_method(D_METHOD("get_angle_to", "point"), &Node2D::get_angle_to);
ClassDB::bind_method(D_METHOD("to_local", "global_point"), &Node2D::to_local);
ClassDB::bind_method(D_METHOD("to_global", "local_point"), &Node2D::to_global);
ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent);
ADD_GROUP("Transform", "");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_less,or_greater,hide_slider,suffix:px"), "set_position", "get_position");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians_as_degrees"), "set_rotation", "get_rotation");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation_degrees", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_rotation_degrees", "get_rotation_degrees");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale", PROPERTY_HINT_LINK), "set_scale", "get_scale");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "skew", PROPERTY_HINT_RANGE, "-89.9,89.9,0.1,radians_as_degrees"), "set_skew", "get_skew");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "set_transform", "get_transform");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "set_global_position", "get_global_position");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_rotation", PROPERTY_HINT_NONE, "radians_as_degrees", PROPERTY_USAGE_NONE), "set_global_rotation", "get_global_rotation");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_rotation_degrees", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_rotation_degrees", "get_global_rotation_degrees");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_scale", "get_global_scale");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_skew", PROPERTY_HINT_NONE, "radians_as_degrees", PROPERTY_USAGE_NONE), "set_global_skew", "get_global_skew");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_transform", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform");
}

123
engine/scene/2d/node_2d.h Normal file
View file

@ -0,0 +1,123 @@
/**************************************************************************/
/* node_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef NODE_2D_H
#define NODE_2D_H
#include "scene/main/canvas_item.h"
class Node2D : public CanvasItem {
GDCLASS(Node2D, CanvasItem);
mutable MTFlag xform_dirty;
mutable Point2 position;
mutable real_t rotation = 0.0;
mutable Size2 scale = Vector2(1, 1);
mutable real_t skew = 0.0;
Transform2D transform;
_FORCE_INLINE_ bool _is_xform_dirty() const { return is_group_processing() ? xform_dirty.mt.is_set() : xform_dirty.st; }
void _set_xform_dirty(bool p_dirty) const;
void _update_transform();
void _update_xform_values() const;
protected:
void _notification(int p_notification);
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Dictionary _edit_get_state() const override;
virtual void _edit_set_state(const Dictionary &p_state) override;
virtual void _edit_set_position(const Point2 &p_position) override;
virtual Point2 _edit_get_position() const override;
virtual void _edit_set_scale(const Size2 &p_scale) override;
virtual Size2 _edit_get_scale() const override;
virtual void _edit_set_rotation(real_t p_rotation) override;
virtual real_t _edit_get_rotation() const override;
virtual bool _edit_use_rotation() const override;
virtual void _edit_set_rect(const Rect2 &p_edit_rect) override;
#endif
virtual void reparent(Node *p_parent, bool p_keep_global_transform = true) override;
void set_position(const Point2 &p_pos);
void set_rotation(real_t p_radians);
void set_rotation_degrees(real_t p_degrees);
void set_skew(real_t p_radians);
void set_scale(const Size2 &p_scale);
void rotate(real_t p_radians);
void move_x(real_t p_delta, bool p_scaled = false);
void move_y(real_t p_delta, bool p_scaled = false);
void translate(const Vector2 &p_amount);
void global_translate(const Vector2 &p_amount);
void apply_scale(const Size2 &p_amount);
Point2 get_position() const;
real_t get_rotation() const;
real_t get_rotation_degrees() const;
real_t get_skew() const;
Size2 get_scale() const;
Point2 get_global_position() const;
real_t get_global_rotation() const;
real_t get_global_rotation_degrees() const;
real_t get_global_skew() const;
Size2 get_global_scale() const;
void set_transform(const Transform2D &p_transform);
void set_global_transform(const Transform2D &p_transform);
void set_global_position(const Point2 &p_pos);
void set_global_rotation(const real_t p_radians);
void set_global_rotation_degrees(const real_t p_degrees);
void set_global_skew(const real_t p_radians);
void set_global_scale(const Size2 &p_scale);
void look_at(const Vector2 &p_pos);
real_t get_angle_to(const Vector2 &p_pos) const;
Point2 to_local(Point2 p_global) const;
Point2 to_global(Point2 p_local) const;
Transform2D get_relative_transform_to_parent(const Node *p_parent) const;
Transform2D get_transform() const override;
Node2D() {}
};
#endif // NODE_2D_H

View file

@ -0,0 +1,296 @@
/**************************************************************************/
/* parallax_2d.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 "parallax_2d.h"
#include "core/config/project_settings.h"
#include "scene/main/viewport.h"
void Parallax2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
group_name = "__cameras_" + itos(get_viewport_rid().get_id());
add_to_group(group_name);
_update_repeat();
_update_scroll();
} break;
case NOTIFICATION_READY: {
_update_process();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
autoscroll_offset += autoscroll * get_process_delta_time();
autoscroll_offset = autoscroll_offset.posmodv(repeat_size);
_update_scroll();
} break;
case NOTIFICATION_EXIT_TREE: {
remove_from_group(group_name);
} break;
}
}
#ifdef TOOLS_ENABLED
void Parallax2D::_edit_set_position(const Point2 &p_position) {
set_scroll_offset(p_position);
}
#endif // TOOLS_ENABLED
void Parallax2D::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "position") {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
void Parallax2D::_camera_moved(const Transform2D &p_transform, const Point2 &p_screen_offset, const Point2 &p_adj_screen_pos) {
if (!ignore_camera_scroll) {
if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
set_screen_offset((p_adj_screen_pos + Vector2(0.5, 0.5)).floor());
} else {
set_screen_offset(p_adj_screen_pos);
}
}
}
void Parallax2D::_update_process() {
set_process_internal(!Engine::get_singleton()->is_editor_hint() && (repeat_size.x || repeat_size.y) && (autoscroll.x || autoscroll.y));
}
void Parallax2D::_update_scroll() {
if (!is_inside_tree()) {
return;
}
Point2 scroll_ofs = screen_offset;
if (!Engine::get_singleton()->is_editor_hint()) {
Size2 vps = get_viewport_rect().size;
if (limit_begin.x <= limit_end.x - vps.x) {
scroll_ofs.x = CLAMP(scroll_ofs.x, limit_begin.x, limit_end.x - vps.x);
}
if (limit_begin.y <= limit_end.y - vps.y) {
scroll_ofs.y = CLAMP(scroll_ofs.y, limit_begin.y, limit_end.y - vps.y);
}
}
scroll_ofs *= scroll_scale;
if (repeat_size.x) {
real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x - autoscroll_offset.x, repeat_size.x * get_scale().x);
scroll_ofs.x = screen_offset.x - mod;
} else {
scroll_ofs.x = screen_offset.x + scroll_offset.x - scroll_ofs.x;
}
if (repeat_size.y) {
real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y - autoscroll_offset.y, repeat_size.y * get_scale().y);
scroll_ofs.y = screen_offset.y - mod;
} else {
scroll_ofs.y = screen_offset.y + scroll_offset.y - scroll_ofs.y;
}
if (!follow_viewport) {
scroll_ofs -= screen_offset;
}
set_position(scroll_ofs);
}
void Parallax2D::_update_repeat() {
if (!is_inside_tree()) {
return;
}
RenderingServer::get_singleton()->canvas_set_item_repeat(get_canvas_item(), repeat_size, repeat_times);
RenderingServer::get_singleton()->canvas_item_set_interpolated(get_canvas_item(), false);
}
void Parallax2D::set_scroll_scale(const Size2 &p_scale) {
scroll_scale = p_scale;
}
Size2 Parallax2D::get_scroll_scale() const {
return scroll_scale;
}
void Parallax2D::set_repeat_size(const Size2 &p_repeat_size) {
if (p_repeat_size == repeat_size) {
return;
}
repeat_size = p_repeat_size.maxf(0);
_update_process();
_update_repeat();
_update_scroll();
}
Size2 Parallax2D::get_repeat_size() const {
return repeat_size;
}
void Parallax2D::set_repeat_times(int p_repeat_times) {
if (p_repeat_times == repeat_times) {
return;
}
repeat_times = MAX(p_repeat_times, 1);
_update_repeat();
}
int Parallax2D::get_repeat_times() const {
return repeat_times;
}
void Parallax2D::set_scroll_offset(const Point2 &p_offset) {
if (p_offset == scroll_offset) {
return;
}
scroll_offset = p_offset;
_update_scroll();
}
Point2 Parallax2D::get_scroll_offset() const {
return scroll_offset;
}
void Parallax2D::set_autoscroll(const Point2 &p_autoscroll) {
if (p_autoscroll == autoscroll) {
return;
}
autoscroll = p_autoscroll;
autoscroll_offset = Point2();
_update_process();
_update_scroll();
}
Point2 Parallax2D::get_autoscroll() const {
return autoscroll;
}
void Parallax2D::set_screen_offset(const Point2 &p_offset) {
if (p_offset == screen_offset) {
return;
}
screen_offset = p_offset;
_update_scroll();
}
Point2 Parallax2D::get_screen_offset() const {
return screen_offset;
}
void Parallax2D::set_limit_begin(const Point2 &p_offset) {
limit_begin = p_offset;
}
Point2 Parallax2D::get_limit_begin() const {
return limit_begin;
}
void Parallax2D::set_limit_end(const Point2 &p_offset) {
limit_end = p_offset;
}
Point2 Parallax2D::get_limit_end() const {
return limit_end;
}
void Parallax2D::set_follow_viewport(bool p_follow) {
follow_viewport = p_follow;
}
bool Parallax2D::get_follow_viewport() {
return follow_viewport;
}
void Parallax2D::set_ignore_camera_scroll(bool p_ignore) {
ignore_camera_scroll = p_ignore;
}
bool Parallax2D::is_ignore_camera_scroll() {
return ignore_camera_scroll;
}
void Parallax2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("_camera_moved", "transform", "screen_offset", "adj_screen_offset"), &Parallax2D::_camera_moved);
ClassDB::bind_method(D_METHOD("set_scroll_scale", "scale"), &Parallax2D::set_scroll_scale);
ClassDB::bind_method(D_METHOD("get_scroll_scale"), &Parallax2D::get_scroll_scale);
ClassDB::bind_method(D_METHOD("set_repeat_size", "repeat_size"), &Parallax2D::set_repeat_size);
ClassDB::bind_method(D_METHOD("get_repeat_size"), &Parallax2D::get_repeat_size);
ClassDB::bind_method(D_METHOD("set_repeat_times", "repeat_times"), &Parallax2D::set_repeat_times);
ClassDB::bind_method(D_METHOD("get_repeat_times"), &Parallax2D::get_repeat_times);
ClassDB::bind_method(D_METHOD("set_autoscroll", "autoscroll"), &Parallax2D::set_autoscroll);
ClassDB::bind_method(D_METHOD("get_autoscroll"), &Parallax2D::get_autoscroll);
ClassDB::bind_method(D_METHOD("set_scroll_offset", "offset"), &Parallax2D::set_scroll_offset);
ClassDB::bind_method(D_METHOD("get_scroll_offset"), &Parallax2D::get_scroll_offset);
ClassDB::bind_method(D_METHOD("set_screen_offset", "offset"), &Parallax2D::set_screen_offset);
ClassDB::bind_method(D_METHOD("get_screen_offset"), &Parallax2D::get_screen_offset);
ClassDB::bind_method(D_METHOD("set_limit_begin", "offset"), &Parallax2D::set_limit_begin);
ClassDB::bind_method(D_METHOD("get_limit_begin"), &Parallax2D::get_limit_begin);
ClassDB::bind_method(D_METHOD("set_limit_end", "offset"), &Parallax2D::set_limit_end);
ClassDB::bind_method(D_METHOD("get_limit_end"), &Parallax2D::get_limit_end);
ClassDB::bind_method(D_METHOD("set_follow_viewport", "follow"), &Parallax2D::set_follow_viewport);
ClassDB::bind_method(D_METHOD("get_follow_viewport"), &Parallax2D::get_follow_viewport);
ClassDB::bind_method(D_METHOD("set_ignore_camera_scroll", "ignore"), &Parallax2D::set_ignore_camera_scroll);
ClassDB::bind_method(D_METHOD("is_ignore_camera_scroll"), &Parallax2D::is_ignore_camera_scroll);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_scale", PROPERTY_HINT_LINK), "set_scroll_scale", "get_scroll_scale");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_offset", "get_scroll_offset");
ADD_GROUP("Repeat", "");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "repeat_size"), "set_repeat_size", "get_repeat_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "autoscroll", PROPERTY_HINT_NONE, "suffix:px/s"), "set_autoscroll", "get_autoscroll");
ADD_PROPERTY(PropertyInfo(Variant::INT, "repeat_times"), "set_repeat_times", "get_repeat_times");
ADD_GROUP("Limit", "limit_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "limit_begin", PROPERTY_HINT_NONE, "suffix:px"), "set_limit_begin", "get_limit_begin");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "limit_end", PROPERTY_HINT_NONE, "suffix:px"), "set_limit_end", "get_limit_end");
ADD_GROUP("Override", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_viewport"), "set_follow_viewport", "get_follow_viewport");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_camera_scroll"), "set_ignore_camera_scroll", "is_ignore_camera_scroll");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "screen_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_screen_offset", "get_screen_offset");
}
Parallax2D::Parallax2D() {
// Parallax2D is always updated every frame so there is no need to interpolate.
set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}

View file

@ -0,0 +1,101 @@
/**************************************************************************/
/* parallax_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef PARALLAX_2D_H
#define PARALLAX_2D_H
#include "scene/2d/node_2d.h"
class Parallax2D : public Node2D {
GDCLASS(Parallax2D, Node2D);
static constexpr real_t DEFAULT_LIMIT = 10000000;
String group_name;
Size2 scroll_scale = Size2(1, 1);
Point2 scroll_offset;
Point2 screen_offset;
Vector2 repeat_size;
int repeat_times = 1;
Point2 limit_begin = Point2(-DEFAULT_LIMIT, -DEFAULT_LIMIT);
Point2 limit_end = Point2(DEFAULT_LIMIT, DEFAULT_LIMIT);
Point2 autoscroll;
Point2 autoscroll_offset;
bool follow_viewport = true;
bool ignore_camera_scroll = false;
void _update_process();
void _update_repeat();
void _update_scroll();
protected:
#ifdef TOOLS_ENABLED
void _edit_set_position(const Point2 &p_position) override;
#endif // TOOLS_ENABLED
void _validate_property(PropertyInfo &p_property) const;
void _camera_moved(const Transform2D &p_transform, const Point2 &p_screen_offset, const Point2 &p_adj_screen_offset);
void _notification(int p_what);
static void _bind_methods();
public:
void set_scroll_scale(const Size2 &p_scale);
Size2 get_scroll_scale() const;
void set_repeat_size(const Size2 &p_repeat_size);
Size2 get_repeat_size() const;
void set_repeat_times(int p_repeat_times);
int get_repeat_times() const;
void set_autoscroll(const Point2 &p_autoscroll);
Point2 get_autoscroll() const;
void set_scroll_offset(const Point2 &p_offset);
Point2 get_scroll_offset() const;
void set_screen_offset(const Point2 &p_offset);
Point2 get_screen_offset() const;
void set_limit_begin(const Point2 &p_offset);
Point2 get_limit_begin() const;
void set_limit_end(const Point2 &p_offset);
Point2 get_limit_end() const;
void set_follow_viewport(bool p_follow);
bool get_follow_viewport();
void set_ignore_camera_scroll(bool p_ignore);
bool is_ignore_camera_scroll();
Parallax2D();
};
#endif // PARALLAX_2D_H

View file

@ -0,0 +1,190 @@
/**************************************************************************/
/* parallax_background.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 "parallax_background.h"
#include "parallax_layer.h"
void ParallaxBackground::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
group_name = "__cameras_" + itos(get_viewport().get_id());
add_to_group(group_name);
} break;
case NOTIFICATION_EXIT_TREE: {
remove_from_group(group_name);
} break;
}
}
void ParallaxBackground::_camera_moved(const Transform2D &p_transform, const Point2 &p_screen_offset, const Point2 &p_adj_screen_offset) {
screen_offset = p_screen_offset;
set_scroll_scale(p_transform.get_scale().dot(Vector2(0.5, 0.5)));
set_scroll_offset(p_transform.get_origin());
}
void ParallaxBackground::set_scroll_scale(real_t p_scale) {
scale = p_scale;
}
real_t ParallaxBackground::get_scroll_scale() const {
return scale;
}
void ParallaxBackground::set_scroll_offset(const Point2 &p_ofs) {
offset = p_ofs;
_update_scroll();
}
void ParallaxBackground::_update_scroll() {
if (!is_inside_tree()) {
return;
}
Vector2 scroll_ofs = base_offset + offset * base_scale;
Size2 vps = get_viewport_size();
scroll_ofs = -scroll_ofs;
if (limit_begin.x < limit_end.x) {
if (scroll_ofs.x < limit_begin.x) {
scroll_ofs.x = limit_begin.x;
} else if (scroll_ofs.x + vps.x > limit_end.x) {
scroll_ofs.x = limit_end.x - vps.x;
}
}
if (limit_begin.y < limit_end.y) {
if (scroll_ofs.y < limit_begin.y) {
scroll_ofs.y = limit_begin.y;
} else if (scroll_ofs.y + vps.y > limit_end.y) {
scroll_ofs.y = limit_end.y - vps.y;
}
}
scroll_ofs = -scroll_ofs;
final_offset = scroll_ofs;
for (int i = 0; i < get_child_count(); i++) {
ParallaxLayer *l = Object::cast_to<ParallaxLayer>(get_child(i));
if (!l) {
continue;
}
if (ignore_camera_zoom) {
l->set_base_offset_and_scale((scroll_ofs + screen_offset * (scale - 1)) / scale, 1.0);
} else {
l->set_base_offset_and_scale(scroll_ofs, scale);
}
}
}
Point2 ParallaxBackground::get_scroll_offset() const {
return offset;
}
void ParallaxBackground::set_scroll_base_offset(const Point2 &p_ofs) {
base_offset = p_ofs;
_update_scroll();
}
Point2 ParallaxBackground::get_scroll_base_offset() const {
return base_offset;
}
void ParallaxBackground::set_scroll_base_scale(const Point2 &p_ofs) {
base_scale = p_ofs;
_update_scroll();
}
Point2 ParallaxBackground::get_scroll_base_scale() const {
return base_scale;
}
void ParallaxBackground::set_limit_begin(const Point2 &p_ofs) {
limit_begin = p_ofs;
_update_scroll();
}
Point2 ParallaxBackground::get_limit_begin() const {
return limit_begin;
}
void ParallaxBackground::set_limit_end(const Point2 &p_ofs) {
limit_end = p_ofs;
_update_scroll();
}
Point2 ParallaxBackground::get_limit_end() const {
return limit_end;
}
void ParallaxBackground::set_ignore_camera_zoom(bool ignore) {
ignore_camera_zoom = ignore;
}
bool ParallaxBackground::is_ignore_camera_zoom() {
return ignore_camera_zoom;
}
Vector2 ParallaxBackground::get_final_offset() const {
return final_offset;
}
void ParallaxBackground::_bind_methods() {
ClassDB::bind_method(D_METHOD("_camera_moved"), &ParallaxBackground::_camera_moved);
ClassDB::bind_method(D_METHOD("set_scroll_offset", "offset"), &ParallaxBackground::set_scroll_offset);
ClassDB::bind_method(D_METHOD("get_scroll_offset"), &ParallaxBackground::get_scroll_offset);
ClassDB::bind_method(D_METHOD("set_scroll_base_offset", "offset"), &ParallaxBackground::set_scroll_base_offset);
ClassDB::bind_method(D_METHOD("get_scroll_base_offset"), &ParallaxBackground::get_scroll_base_offset);
ClassDB::bind_method(D_METHOD("set_scroll_base_scale", "scale"), &ParallaxBackground::set_scroll_base_scale);
ClassDB::bind_method(D_METHOD("get_scroll_base_scale"), &ParallaxBackground::get_scroll_base_scale);
ClassDB::bind_method(D_METHOD("set_limit_begin", "offset"), &ParallaxBackground::set_limit_begin);
ClassDB::bind_method(D_METHOD("get_limit_begin"), &ParallaxBackground::get_limit_begin);
ClassDB::bind_method(D_METHOD("set_limit_end", "offset"), &ParallaxBackground::set_limit_end);
ClassDB::bind_method(D_METHOD("get_limit_end"), &ParallaxBackground::get_limit_end);
ClassDB::bind_method(D_METHOD("set_ignore_camera_zoom", "ignore"), &ParallaxBackground::set_ignore_camera_zoom);
ClassDB::bind_method(D_METHOD("is_ignore_camera_zoom"), &ParallaxBackground::is_ignore_camera_zoom);
ADD_GROUP("Scroll", "scroll_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_offset", "get_scroll_offset");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_base_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_base_offset", "get_scroll_base_offset");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_base_scale", PROPERTY_HINT_LINK), "set_scroll_base_scale", "get_scroll_base_scale");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_limit_begin", PROPERTY_HINT_NONE, "suffix:px"), "set_limit_begin", "get_limit_begin");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_limit_end", PROPERTY_HINT_NONE, "suffix:px"), "set_limit_end", "get_limit_end");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_ignore_camera_zoom"), "set_ignore_camera_zoom", "is_ignore_camera_zoom");
}
ParallaxBackground::ParallaxBackground() {
set_layer(-100); //behind all by default
}

View file

@ -0,0 +1,85 @@
/**************************************************************************/
/* parallax_background.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef PARALLAX_BACKGROUND_H
#define PARALLAX_BACKGROUND_H
#include "scene/main/canvas_layer.h"
class ParallaxBackground : public CanvasLayer {
GDCLASS(ParallaxBackground, CanvasLayer);
Point2 offset;
real_t scale = 1.0;
Point2 base_offset;
Point2 base_scale = Vector2(1, 1);
Point2 screen_offset;
String group_name;
Point2 limit_begin;
Point2 limit_end;
Point2 final_offset;
bool ignore_camera_zoom = false;
void _update_scroll();
protected:
void _camera_moved(const Transform2D &p_transform, const Point2 &p_screen_offset, const Point2 &p_adj_screen_offset);
void _notification(int p_what);
static void _bind_methods();
public:
void set_scroll_offset(const Point2 &p_ofs);
Point2 get_scroll_offset() const;
void set_scroll_scale(real_t p_scale);
real_t get_scroll_scale() const;
void set_scroll_base_offset(const Point2 &p_ofs);
Point2 get_scroll_base_offset() const;
void set_scroll_base_scale(const Point2 &p_ofs);
Point2 get_scroll_base_scale() const;
void set_limit_begin(const Point2 &p_ofs);
Point2 get_limit_begin() const;
void set_limit_end(const Point2 &p_ofs);
Point2 get_limit_end() const;
void set_ignore_camera_zoom(bool ignore);
bool is_ignore_camera_zoom();
Vector2 get_final_offset() const;
ParallaxBackground();
};
#endif // PARALLAX_BACKGROUND_H

View file

@ -0,0 +1,168 @@
/**************************************************************************/
/* parallax_layer.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 "parallax_layer.h"
#include "parallax_background.h"
void ParallaxLayer::set_motion_scale(const Size2 &p_scale) {
motion_scale = p_scale;
ParallaxBackground *pb = Object::cast_to<ParallaxBackground>(get_parent());
if (pb && is_inside_tree()) {
Vector2 final_ofs = pb->get_final_offset();
real_t scroll_scale = pb->get_scroll_scale();
set_base_offset_and_scale(final_ofs, scroll_scale);
}
}
Size2 ParallaxLayer::get_motion_scale() const {
return motion_scale;
}
void ParallaxLayer::set_motion_offset(const Size2 &p_offset) {
motion_offset = p_offset;
ParallaxBackground *pb = Object::cast_to<ParallaxBackground>(get_parent());
if (pb && is_inside_tree()) {
Vector2 final_ofs = pb->get_final_offset();
real_t scroll_scale = pb->get_scroll_scale();
set_base_offset_and_scale(final_ofs, scroll_scale);
}
}
Size2 ParallaxLayer::get_motion_offset() const {
return motion_offset;
}
void ParallaxLayer::_update_mirroring() {
if (!is_inside_tree()) {
return;
}
ParallaxBackground *pb = Object::cast_to<ParallaxBackground>(get_parent());
if (pb) {
RID c = pb->get_canvas();
RID ci = get_canvas_item();
Point2 mirror_scale = mirroring * orig_scale;
RenderingServer::get_singleton()->canvas_set_item_mirroring(c, ci, mirror_scale);
RenderingServer::get_singleton()->canvas_item_set_interpolated(ci, false);
}
}
void ParallaxLayer::set_mirroring(const Size2 &p_mirroring) {
mirroring = p_mirroring;
if (mirroring.x < 0) {
mirroring.x = 0;
}
if (mirroring.y < 0) {
mirroring.y = 0;
}
_update_mirroring();
}
Size2 ParallaxLayer::get_mirroring() const {
return mirroring;
}
void ParallaxLayer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
orig_offset = get_position();
orig_scale = get_scale();
_update_mirroring();
} break;
case NOTIFICATION_EXIT_TREE: {
if (Engine::get_singleton()->is_editor_hint()) {
break;
}
set_position(orig_offset);
set_scale(orig_scale);
} break;
}
}
void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_scale) {
if (!is_inside_tree()) {
return;
}
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
Point2 new_ofs = p_offset * motion_scale + motion_offset * p_scale + orig_offset * p_scale;
if (mirroring.x) {
real_t den = mirroring.x * p_scale;
new_ofs.x -= den * ceil(new_ofs.x / den);
}
if (mirroring.y) {
real_t den = mirroring.y * p_scale;
new_ofs.y -= den * ceil(new_ofs.y / den);
}
set_position(new_ofs);
set_scale(Vector2(1, 1) * p_scale * orig_scale);
_update_mirroring();
}
PackedStringArray ParallaxLayer::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (!Object::cast_to<ParallaxBackground>(get_parent())) {
warnings.push_back(RTR("ParallaxLayer node only works when set as child of a ParallaxBackground node."));
}
return warnings;
}
void ParallaxLayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_motion_scale", "scale"), &ParallaxLayer::set_motion_scale);
ClassDB::bind_method(D_METHOD("get_motion_scale"), &ParallaxLayer::get_motion_scale);
ClassDB::bind_method(D_METHOD("set_motion_offset", "offset"), &ParallaxLayer::set_motion_offset);
ClassDB::bind_method(D_METHOD("get_motion_offset"), &ParallaxLayer::get_motion_offset);
ClassDB::bind_method(D_METHOD("set_mirroring", "mirror"), &ParallaxLayer::set_mirroring);
ClassDB::bind_method(D_METHOD("get_mirroring"), &ParallaxLayer::get_mirroring);
ADD_GROUP("Motion", "motion_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_scale", PROPERTY_HINT_LINK), "set_motion_scale", "get_motion_scale");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_motion_offset", "get_motion_offset");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_mirroring"), "set_mirroring", "get_mirroring");
}
ParallaxLayer::ParallaxLayer() {
// ParallaxLayer is always updated every frame so there is no need to interpolate.
set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}

View file

@ -0,0 +1,66 @@
/**************************************************************************/
/* parallax_layer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef PARALLAX_LAYER_H
#define PARALLAX_LAYER_H
#include "scene/2d/node_2d.h"
class ParallaxLayer : public Node2D {
GDCLASS(ParallaxLayer, Node2D);
Point2 orig_offset;
Point2 orig_scale;
Size2 motion_scale = Size2(1, 1);
Vector2 motion_offset;
Vector2 mirroring;
void _update_mirroring();
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_motion_offset(const Size2 &p_offset);
Size2 get_motion_offset() const;
void set_motion_scale(const Size2 &p_scale);
Size2 get_motion_scale() const;
void set_mirroring(const Size2 &p_mirroring);
Size2 get_mirroring() const;
void set_base_offset_and_scale(const Point2 &p_offset, real_t p_scale);
PackedStringArray get_configuration_warnings() const override;
ParallaxLayer();
};
#endif // PARALLAX_LAYER_H

409
engine/scene/2d/path_2d.cpp Normal file
View file

@ -0,0 +1,409 @@
/**************************************************************************/
/* path_2d.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_2d.h"
#include "core/math/geometry_2d.h"
#include "scene/main/timer.h"
#ifdef TOOLS_ENABLED
#include "editor/themes/editor_scale.h"
#endif
#ifdef TOOLS_ENABLED
Rect2 Path2D::_edit_get_rect() const {
if (!curve.is_valid() || curve->get_point_count() == 0) {
return Rect2(0, 0, 0, 0);
}
Rect2 aabb = Rect2(curve->get_point_position(0), Vector2(0, 0));
for (int i = 0; i < curve->get_point_count(); i++) {
for (int j = 0; j <= 8; j++) {
real_t frac = j / 8.0;
Vector2 p = curve->sample(i, frac);
aabb.expand_to(p);
}
}
return aabb;
}
bool Path2D::_edit_use_rect() const {
return curve.is_valid() && curve->get_point_count() != 0;
}
bool Path2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
if (curve.is_null()) {
return false;
}
for (int i = 0; i < curve->get_point_count(); i++) {
Vector2 s[2];
s[0] = curve->get_point_position(i);
for (int j = 1; j <= 8; j++) {
real_t frac = j / 8.0;
s[1] = curve->sample(i, frac);
Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, s);
if (p.distance_to(p_point) <= p_tolerance) {
return true;
}
s[0] = s[1];
}
}
return false;
}
#endif
void Path2D::_notification(int p_what) {
switch (p_what) {
// Draw the curve if path debugging is enabled.
case NOTIFICATION_DRAW: {
if (!curve.is_valid()) {
break;
}
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_paths_hint()) {
return;
}
if (curve->get_point_count() < 2) {
return;
}
#ifdef TOOLS_ENABLED
const real_t line_width = get_tree()->get_debug_paths_width() * EDSCALE;
#else
const real_t line_width = get_tree()->get_debug_paths_width();
#endif
real_t interval = 10;
const real_t length = curve->get_baked_length();
if (length > CMP_EPSILON) {
const int sample_count = int(length / interval) + 2;
interval = length / (sample_count - 1); // Recalculate real interval length.
Vector<Transform2D> frames;
frames.resize(sample_count);
{
Transform2D *w = frames.ptrw();
for (int i = 0; i < sample_count; i++) {
w[i] = curve->sample_baked_with_rotation(i * interval, false);
}
}
const Transform2D *r = frames.ptr();
// Draw curve segments
{
PackedVector2Array v2p;
v2p.resize(sample_count);
Vector2 *w = v2p.ptrw();
for (int i = 0; i < sample_count; i++) {
w[i] = r[i].get_origin();
}
draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width, false);
}
// Draw fish bones
{
PackedVector2Array v2p;
v2p.resize(3);
Vector2 *w = v2p.ptrw();
for (int i = 0; i < sample_count; i++) {
const Vector2 p = r[i].get_origin();
const Vector2 side = r[i].columns[1];
const Vector2 forward = r[i].columns[0];
// Fish Bone.
w[0] = p + (side - forward) * 5;
w[1] = p;
w[2] = p + (-side - forward) * 5;
draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width * 0.5, false);
}
}
}
} break;
}
}
void Path2D::_curve_changed() {
if (!is_inside_tree()) {
return;
}
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_paths_hint()) {
return;
}
queue_redraw();
for (int i = 0; i < get_child_count(); i++) {
PathFollow2D *follow = Object::cast_to<PathFollow2D>(get_child(i));
if (follow) {
follow->path_changed();
}
}
}
void Path2D::set_curve(const Ref<Curve2D> &p_curve) {
if (curve.is_valid()) {
curve->disconnect_changed(callable_mp(this, &Path2D::_curve_changed));
}
curve = p_curve;
if (curve.is_valid()) {
curve->connect_changed(callable_mp(this, &Path2D::_curve_changed));
}
_curve_changed();
}
Ref<Curve2D> Path2D::get_curve() const {
return curve;
}
void Path2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_curve", "curve"), &Path2D::set_curve);
ClassDB::bind_method(D_METHOD("get_curve"), &Path2D::get_curve);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_curve", "get_curve");
}
/////////////////////////////////////////////////////////////////////////////////
void PathFollow2D::path_changed() {
if (update_timer && !update_timer->is_stopped()) {
update_timer->start();
} else {
_update_transform();
}
}
void PathFollow2D::_update_transform() {
if (!path) {
return;
}
Ref<Curve2D> c = path->get_curve();
if (!c.is_valid()) {
return;
}
real_t path_length = c->get_baked_length();
if (path_length == 0) {
return;
}
if (rotates) {
Transform2D xform = c->sample_baked_with_rotation(progress, cubic);
xform.translate_local(h_offset, v_offset);
set_rotation(xform[0].angle());
set_position(xform[2]);
} else {
Vector2 pos = c->sample_baked(progress, cubic);
pos.x += h_offset;
pos.y += v_offset;
set_position(pos);
}
}
void PathFollow2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
if (Engine::get_singleton()->is_editor_hint()) {
update_timer = memnew(Timer);
update_timer->set_wait_time(0.2);
update_timer->set_one_shot(true);
update_timer->connect("timeout", callable_mp(this, &PathFollow2D::_update_transform));
add_child(update_timer, false, Node::INTERNAL_MODE_BACK);
}
} break;
case NOTIFICATION_ENTER_TREE: {
path = Object::cast_to<Path2D>(get_parent());
if (path) {
_update_transform();
}
} break;
case NOTIFICATION_EXIT_TREE: {
path = nullptr;
} break;
}
}
void PathFollow2D::set_cubic_interpolation_enabled(bool p_enabled) {
cubic = p_enabled;
}
bool PathFollow2D::is_cubic_interpolation_enabled() const {
return cubic;
}
void PathFollow2D::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "offset") {
real_t max = 10000.0;
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 PathFollow2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!Object::cast_to<Path2D>(get_parent())) {
warnings.push_back(RTR("PathFollow2D only works when set as a child of a Path2D node."));
}
}
return warnings;
}
void PathFollow2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_progress", "progress"), &PathFollow2D::set_progress);
ClassDB::bind_method(D_METHOD("get_progress"), &PathFollow2D::get_progress);
ClassDB::bind_method(D_METHOD("set_h_offset", "h_offset"), &PathFollow2D::set_h_offset);
ClassDB::bind_method(D_METHOD("get_h_offset"), &PathFollow2D::get_h_offset);
ClassDB::bind_method(D_METHOD("set_v_offset", "v_offset"), &PathFollow2D::set_v_offset);
ClassDB::bind_method(D_METHOD("get_v_offset"), &PathFollow2D::get_v_offset);
ClassDB::bind_method(D_METHOD("set_progress_ratio", "ratio"), &PathFollow2D::set_progress_ratio);
ClassDB::bind_method(D_METHOD("get_progress_ratio"), &PathFollow2D::get_progress_ratio);
ClassDB::bind_method(D_METHOD("set_rotates", "enabled"), &PathFollow2D::set_rotation_enabled);
ClassDB::bind_method(D_METHOD("is_rotating"), &PathFollow2D::is_rotation_enabled);
ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enabled"), &PathFollow2D::set_cubic_interpolation_enabled);
ClassDB::bind_method(D_METHOD("get_cubic_interpolation"), &PathFollow2D::is_cubic_interpolation_enabled);
ClassDB::bind_method(D_METHOD("set_loop", "loop"), &PathFollow2D::set_loop);
ClassDB::bind_method(D_METHOD("has_loop"), &PathFollow2D::has_loop);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "progress", PROPERTY_HINT_RANGE, "0,10000,0.01,or_less,or_greater,suffix:px"), "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"), "set_h_offset", "get_h_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset"), "set_v_offset", "get_v_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotates"), "set_rotates", "is_rotating");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
}
void PathFollow2D::set_progress(real_t p_progress) {
ERR_FAIL_COND(!isfinite(p_progress));
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 PathFollow2D::set_h_offset(real_t p_h_offset) {
h_offset = p_h_offset;
if (path) {
_update_transform();
}
}
real_t PathFollow2D::get_h_offset() const {
return h_offset;
}
void PathFollow2D::set_v_offset(real_t p_v_offset) {
v_offset = p_v_offset;
if (path) {
_update_transform();
}
}
real_t PathFollow2D::get_v_offset() const {
return v_offset;
}
real_t PathFollow2D::get_progress() const {
return progress;
}
void PathFollow2D::set_progress_ratio(real_t p_ratio) {
if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) {
set_progress(p_ratio * path->get_curve()->get_baked_length());
}
}
real_t PathFollow2D::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 PathFollow2D::set_rotation_enabled(bool p_enabled) {
rotates = p_enabled;
_update_transform();
}
bool PathFollow2D::is_rotation_enabled() const {
return rotates;
}
void PathFollow2D::set_loop(bool p_loop) {
loop = p_loop;
}
bool PathFollow2D::has_loop() const {
return loop;
}

114
engine/scene/2d/path_2d.h Normal file
View file

@ -0,0 +1,114 @@
/**************************************************************************/
/* path_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef PATH_2D_H
#define PATH_2D_H
#include "scene/2d/node_2d.h"
#include "scene/resources/curve.h"
class Timer;
class Path2D : public Node2D {
GDCLASS(Path2D, Node2D);
Ref<Curve2D> curve;
void _curve_changed();
protected:
void _notification(int p_what);
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#endif
void set_curve(const Ref<Curve2D> &p_curve);
Ref<Curve2D> get_curve() const;
Path2D() {}
};
class PathFollow2D : public Node2D {
GDCLASS(PathFollow2D, Node2D);
public:
private:
Path2D *path = nullptr;
real_t progress = 0.0;
Timer *update_timer = nullptr;
real_t h_offset = 0.0;
real_t v_offset = 0.0;
bool cubic = true;
bool loop = true;
bool rotates = true;
void _update_transform();
protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
static void _bind_methods();
public:
void path_changed();
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_rotation_enabled(bool p_enabled);
bool is_rotation_enabled() const;
void set_cubic_interpolation_enabled(bool p_enabled);
bool is_cubic_interpolation_enabled() const;
PackedStringArray get_configuration_warnings() const override;
PathFollow2D() {}
};
#endif // PATH_2D_H

View file

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

View file

@ -0,0 +1,111 @@
/**************************************************************************/
/* animatable_body_2d.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_2d.h"
void AnimatableBody2D::set_sync_to_physics(bool p_enable) {
if (sync_to_physics == p_enable) {
return;
}
sync_to_physics = p_enable;
_update_kinematic_motion();
}
bool AnimatableBody2D::is_sync_to_physics_enabled() const {
return sync_to_physics;
}
void AnimatableBody2D::_update_kinematic_motion() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
#endif
if (sync_to_physics) {
PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), callable_mp(this, &AnimatableBody2D::_body_state_changed));
set_only_update_transform_changes(true);
set_notify_local_transform(true);
} else {
PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), Callable());
set_only_update_transform_changes(false);
set_notify_local_transform(false);
}
}
void AnimatableBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) {
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);
}
void AnimatableBody2D::_notification(int p_what) {
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...
Transform2D new_transform = get_global_transform();
PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::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);
} break;
}
}
void AnimatableBody2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &AnimatableBody2D::set_sync_to_physics);
ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &AnimatableBody2D::is_sync_to_physics_enabled);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled");
}
AnimatableBody2D::AnimatableBody2D() :
StaticBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) {
}

View file

@ -0,0 +1,61 @@
/**************************************************************************/
/* animatable_body_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef ANIMATABLE_BODY_2D_H
#define ANIMATABLE_BODY_2D_H
#include "scene/2d/physics/static_body_2d.h"
class AnimatableBody2D : public StaticBody2D {
GDCLASS(AnimatableBody2D, StaticBody2D);
private:
bool sync_to_physics = true;
Transform2D last_valid_transform;
static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state);
void _body_state_changed(PhysicsDirectBodyState2D *p_state);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
AnimatableBody2D();
private:
void _update_kinematic_motion();
void set_sync_to_physics(bool p_enable);
bool is_sync_to_physics_enabled() const;
};
#endif // ANIMATABLE_BODY_2D_H

View file

@ -0,0 +1,686 @@
/**************************************************************************/
/* area_2d.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_2d.h"
#include "servers/audio_server.h"
void Area2D::set_gravity_space_override_mode(SpaceOverride p_mode) {
gravity_space_override = p_mode;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_OVERRIDE_MODE, p_mode);
}
Area2D::SpaceOverride Area2D::get_gravity_space_override_mode() const {
return gravity_space_override;
}
void Area2D::set_gravity_is_point(bool p_enabled) {
gravity_is_point = p_enabled;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_IS_POINT, p_enabled);
}
bool Area2D::is_gravity_a_point() const {
return gravity_is_point;
}
void Area2D::set_gravity_point_unit_distance(real_t p_scale) {
gravity_point_unit_distance = p_scale;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_POINT_UNIT_DISTANCE, p_scale);
}
real_t Area2D::get_gravity_point_unit_distance() const {
return gravity_point_unit_distance;
}
void Area2D::set_gravity_point_center(const Vector2 &p_center) {
gravity_vec = p_center;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, p_center);
}
const Vector2 &Area2D::get_gravity_point_center() const {
return gravity_vec;
}
void Area2D::set_gravity_direction(const Vector2 &p_direction) {
gravity_vec = p_direction;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, p_direction);
}
const Vector2 &Area2D::get_gravity_direction() const {
return gravity_vec;
}
void Area2D::set_gravity(real_t p_gravity) {
gravity = p_gravity;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY, p_gravity);
}
real_t Area2D::get_gravity() const {
return gravity;
}
void Area2D::set_linear_damp_space_override_mode(SpaceOverride p_mode) {
linear_damp_space_override = p_mode;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE, p_mode);
}
Area2D::SpaceOverride Area2D::get_linear_damp_space_override_mode() const {
return linear_damp_space_override;
}
void Area2D::set_angular_damp_space_override_mode(SpaceOverride p_mode) {
angular_damp_space_override = p_mode;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE, p_mode);
}
Area2D::SpaceOverride Area2D::get_angular_damp_space_override_mode() const {
return angular_damp_space_override;
}
void Area2D::set_linear_damp(real_t p_linear_damp) {
linear_damp = p_linear_damp;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_LINEAR_DAMP, p_linear_damp);
}
real_t Area2D::get_linear_damp() const {
return linear_damp;
}
void Area2D::set_angular_damp(real_t p_angular_damp) {
angular_damp = p_angular_damp;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_ANGULAR_DAMP, p_angular_damp);
}
real_t Area2D::get_angular_damp() const {
return angular_damp;
}
void Area2D::set_priority(int p_priority) {
priority = p_priority;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_PRIORITY, p_priority);
}
int Area2D::get_priority() const {
return priority;
}
void Area2D::_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 Area2D::_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 Area2D::_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 == PhysicsServer2D::AREA_BODY_ADDED;
ObjectID objid = p_instance;
// Exit early if instance is invalid.
if (objid.is_null()) {
// Emit the appropriate signals.
lock_callback();
locked = true;
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; //does not exist because it was 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, &Area2D::_body_enter_tree).bind(objid));
node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_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, &Area2D::_body_enter_tree));
node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_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 Area2D::_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 Area2D::_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 Area2D::_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 == PhysicsServer2D::AREA_BODY_ADDED;
ObjectID objid = p_instance;
// Exit early if instance is invalid.
if (objid.is_null()) {
// Emit the appropriate signals.
lock_callback();
locked = true;
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, &Area2D::_area_enter_tree).bind(objid));
node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_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, &Area2D::_area_enter_tree));
node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_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();
}
void Area2D::_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;
}
node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_body_enter_tree));
node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_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), obj);
}
}
{
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;
}
node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_area_enter_tree));
node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_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 Area2D::_space_changed(const RID &p_new_space) {
if (p_new_space.is_null()) {
_clear_monitoring();
}
}
void Area2D::set_monitoring(bool p_enable) {
if (p_enable == monitoring) {
return;
}
ERR_FAIL_COND_MSG(locked, "Function blocked during in/out signal. Use set_deferred(\"monitoring\", true/false).");
monitoring = p_enable;
if (monitoring) {
PhysicsServer2D::get_singleton()->area_set_monitor_callback(get_rid(), callable_mp(this, &Area2D::_body_inout));
PhysicsServer2D::get_singleton()->area_set_area_monitor_callback(get_rid(), callable_mp(this, &Area2D::_area_inout));
} else {
PhysicsServer2D::get_singleton()->area_set_monitor_callback(get_rid(), Callable());
PhysicsServer2D::get_singleton()->area_set_area_monitor_callback(get_rid(), Callable());
_clear_monitoring();
}
}
bool Area2D::is_monitoring() const {
return monitoring;
}
void Area2D::set_monitorable(bool p_enable) {
ERR_FAIL_COND_MSG(locked || (is_inside_tree() && PhysicsServer2D::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;
PhysicsServer2D::get_singleton()->area_set_monitorable(get_rid(), monitorable);
}
bool Area2D::is_monitorable() const {
return monitorable;
}
TypedArray<Node2D> Area2D::get_overlapping_bodies() const {
TypedArray<Node2D> 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;
}
TypedArray<Area2D> Area2D::get_overlapping_areas() const {
TypedArray<Area2D> 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 Area2D::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();
}
bool Area2D::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 Area2D::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 Area2D::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 Area2D::set_audio_bus_override(bool p_override) {
audio_bus_override = p_override;
}
bool Area2D::is_overriding_audio_bus() const {
return audio_bus_override;
}
void Area2D::set_audio_bus_name(const StringName &p_audio_bus) {
audio_bus = p_audio_bus;
}
StringName Area2D::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 Area2D::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "audio_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 Area2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_gravity_space_override_mode", "space_override_mode"), &Area2D::set_gravity_space_override_mode);
ClassDB::bind_method(D_METHOD("get_gravity_space_override_mode"), &Area2D::get_gravity_space_override_mode);
ClassDB::bind_method(D_METHOD("set_gravity_is_point", "enable"), &Area2D::set_gravity_is_point);
ClassDB::bind_method(D_METHOD("is_gravity_a_point"), &Area2D::is_gravity_a_point);
ClassDB::bind_method(D_METHOD("set_gravity_point_unit_distance", "distance_scale"), &Area2D::set_gravity_point_unit_distance);
ClassDB::bind_method(D_METHOD("get_gravity_point_unit_distance"), &Area2D::get_gravity_point_unit_distance);
ClassDB::bind_method(D_METHOD("set_gravity_point_center", "center"), &Area2D::set_gravity_point_center);
ClassDB::bind_method(D_METHOD("get_gravity_point_center"), &Area2D::get_gravity_point_center);
ClassDB::bind_method(D_METHOD("set_gravity_direction", "direction"), &Area2D::set_gravity_direction);
ClassDB::bind_method(D_METHOD("get_gravity_direction"), &Area2D::get_gravity_direction);
ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &Area2D::set_gravity);
ClassDB::bind_method(D_METHOD("get_gravity"), &Area2D::get_gravity);
ClassDB::bind_method(D_METHOD("set_linear_damp_space_override_mode", "space_override_mode"), &Area2D::set_linear_damp_space_override_mode);
ClassDB::bind_method(D_METHOD("get_linear_damp_space_override_mode"), &Area2D::get_linear_damp_space_override_mode);
ClassDB::bind_method(D_METHOD("set_angular_damp_space_override_mode", "space_override_mode"), &Area2D::set_angular_damp_space_override_mode);
ClassDB::bind_method(D_METHOD("get_angular_damp_space_override_mode"), &Area2D::get_angular_damp_space_override_mode);
ClassDB::bind_method(D_METHOD("set_linear_damp", "linear_damp"), &Area2D::set_linear_damp);
ClassDB::bind_method(D_METHOD("get_linear_damp"), &Area2D::get_linear_damp);
ClassDB::bind_method(D_METHOD("set_angular_damp", "angular_damp"), &Area2D::set_angular_damp);
ClassDB::bind_method(D_METHOD("get_angular_damp"), &Area2D::get_angular_damp);
ClassDB::bind_method(D_METHOD("set_priority", "priority"), &Area2D::set_priority);
ClassDB::bind_method(D_METHOD("get_priority"), &Area2D::get_priority);
ClassDB::bind_method(D_METHOD("set_monitoring", "enable"), &Area2D::set_monitoring);
ClassDB::bind_method(D_METHOD("is_monitoring"), &Area2D::is_monitoring);
ClassDB::bind_method(D_METHOD("set_monitorable", "enable"), &Area2D::set_monitorable);
ClassDB::bind_method(D_METHOD("is_monitorable"), &Area2D::is_monitorable);
ClassDB::bind_method(D_METHOD("get_overlapping_bodies"), &Area2D::get_overlapping_bodies);
ClassDB::bind_method(D_METHOD("get_overlapping_areas"), &Area2D::get_overlapping_areas);
ClassDB::bind_method(D_METHOD("has_overlapping_bodies"), &Area2D::has_overlapping_bodies);
ClassDB::bind_method(D_METHOD("has_overlapping_areas"), &Area2D::has_overlapping_areas);
ClassDB::bind_method(D_METHOD("overlaps_body", "body"), &Area2D::overlaps_body);
ClassDB::bind_method(D_METHOD("overlaps_area", "area"), &Area2D::overlaps_area);
ClassDB::bind_method(D_METHOD("set_audio_bus_name", "name"), &Area2D::set_audio_bus_name);
ClassDB::bind_method(D_METHOD("get_audio_bus_name"), &Area2D::get_audio_bus_name);
ClassDB::bind_method(D_METHOD("set_audio_bus_override", "enable"), &Area2D::set_audio_bus_override);
ClassDB::bind_method(D_METHOD("is_overriding_audio_bus"), &Area2D::is_overriding_audio_bus);
ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D"), 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, "Node2D"), 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, "Node2D")));
ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D")));
ADD_SIGNAL(MethodInfo("area_shape_entered", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), 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, "Area2D"), 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, "Area2D")));
ADD_SIGNAL(MethodInfo("area_exited", PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D")));
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:px"), "set_gravity_point_unit_distance", "get_gravity_point_unit_distance");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity_point_center", PROPERTY_HINT_NONE, "suffix:px"), "set_gravity_point_center", "get_gravity_point_center");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity_direction"), "set_gravity_direction", "get_gravity_direction");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, U"-4096,4096,0.001,or_less,or_greater,suffix:px/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("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");
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);
}
Area2D::Area2D() :
CollisionObject2D(PhysicsServer2D::get_singleton()->area_create(), true) {
set_gravity(980);
set_gravity_direction(Vector2(0, 1));
set_monitoring(true);
set_monitorable(true);
set_hide_clip_children(true);
}
Area2D::~Area2D() {
}

View file

@ -0,0 +1,202 @@
/**************************************************************************/
/* area_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef AREA_2D_H
#define AREA_2D_H
#include "core/templates/vset.h"
#include "scene/2d/physics/collision_object_2d.h"
class Area2D : public CollisionObject2D {
GDCLASS(Area2D, CollisionObject2D);
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;
Vector2 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 linear_damp = 0.1;
real_t angular_damp = 1.0;
int priority = 0;
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;
protected:
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 Vector2 &p_center);
const Vector2 &get_gravity_point_center() const;
void set_gravity_direction(const Vector2 &p_direction);
const Vector2 &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_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_priority(int p_priority);
int get_priority() const;
void set_monitoring(bool p_enable);
bool is_monitoring() const;
void set_monitorable(bool p_enable);
bool is_monitorable() const;
TypedArray<Node2D> get_overlapping_bodies() const; //function for script
TypedArray<Area2D> 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;
Area2D();
~Area2D();
};
VARIANT_ENUM_CAST(Area2D::SpaceOverride);
#endif // AREA_2D_H

View file

@ -0,0 +1,747 @@
/**************************************************************************/
/* character_body_2d.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_2d.h"
// So, if you pass 45 as limit, avoid numerical precision errors when angle is 45.
#define FLOOR_ANGLE_THRESHOLD 0.01
bool CharacterBody2D::move_and_slide() {
// Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky.
double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time();
Vector2 current_platform_velocity = platform_velocity;
Transform2D gt = get_global_transform();
previous_position = gt.columns[2];
if ((on_floor || on_wall) && platform_rid.is_valid()) {
bool excluded = false;
if (on_floor) {
excluded = (platform_floor_layers & platform_layer) == 0;
} else if (on_wall) {
excluded = (platform_wall_layers & platform_layer) == 0;
}
if (!excluded) {
//this approach makes sure there is less delay between the actual body velocity and the one we saved
PhysicsDirectBodyState2D *bs = PhysicsServer2D::get_singleton()->body_get_direct_state(platform_rid);
if (bs) {
Vector2 local_position = gt.columns[2] - bs->get_transform().columns[2];
current_platform_velocity = bs->get_velocity_at_local_position(local_position);
} else {
// Body is removed or destroyed, invalidate floor.
current_platform_velocity = Vector2();
platform_rid = RID();
}
} else {
current_platform_velocity = Vector2();
}
}
motion_results.clear();
last_motion = Vector2();
bool was_on_floor = on_floor;
on_floor = false;
on_ceiling = false;
on_wall = false;
if (!current_platform_velocity.is_zero_approx()) {
PhysicsServer2D::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);
}
PhysicsServer2D::MotionResult floor_result;
if (move_and_collide(parameters, floor_result, false, false)) {
motion_results.push_back(floor_result);
_set_collision_direction(floor_result);
}
}
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 (!on_floor && !on_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 CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor) {
Vector2 motion = velocity * p_delta;
Vector2 motion_slide_up = motion.slide(up_direction);
Vector2 prev_floor_normal = floor_normal;
platform_rid = RID();
platform_object_id = ObjectID();
floor_normal = Vector2();
platform_velocity = Vector2();
// 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;
Vector2 last_travel;
for (int iteration = 0; iteration < max_slides; ++iteration) {
PhysicsServer2D::MotionParameters parameters(get_global_transform(), motion, margin);
parameters.recovery_as_collision = true; // Also report collisions generated only from recovery.
Vector2 prev_position = parameters.from.columns[2];
PhysicsServer2D::MotionResult result;
bool collided = move_and_collide(parameters, result, false, !sliding_enabled);
last_motion = result.travel;
if (collided) {
motion_results.push_back(result);
_set_collision_direction(result);
// If we hit a ceiling platform, we set the vertical velocity to at least the platform one.
if (on_ceiling && result.collider_velocity != Vector2() && result.collider_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 || (result.collision_normal + up_direction).length() < 0.01) {
apply_ceiling_velocity = true;
Vector2 ceiling_vertical_velocity = up_direction * up_direction.dot(result.collider_velocity);
Vector2 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 (on_floor && floor_stop_on_slope && (velocity.normalized() + up_direction).length() < 0.01) {
Transform2D gt = get_global_transform();
if (result.travel.length() <= margin + CMP_EPSILON) {
gt.columns[2] -= result.travel;
}
set_global_transform(gt);
velocity = Vector2();
last_motion = Vector2();
motion = Vector2();
break;
}
if (result.remainder.is_zero_approx()) {
motion = Vector2();
break;
}
// Move on floor only checks.
if (floor_block_on_wall && on_wall && motion_slide_up.dot(result.collision_normal) <= 0) {
// Avoid to move forward on a wall if floor_block_on_wall is true.
if (p_was_on_floor && !on_floor && !vel_dir_facing_up) {
// If the movement is large the body can be prevented from reaching the walls.
if (result.travel.length() <= margin + CMP_EPSILON) {
// Cancels the motion.
Transform2D gt = get_global_transform();
gt.columns[2] -= result.travel;
set_global_transform(gt);
}
// Determines if you are on the ground.
_snap_on_floor(true, false, true);
velocity = Vector2();
last_motion = Vector2();
motion = Vector2();
break;
}
// Prevents the body from being able to climb a slope when it moves forward against the wall.
else if (!on_floor) {
motion = up_direction * up_direction.dot(result.remainder);
motion = motion.slide(result.collision_normal);
} else {
motion = result.remainder;
}
}
// Constant Speed when the slope is upward.
else if (floor_constant_speed && is_on_floor_only() && can_apply_constant_speed && p_was_on_floor && motion.dot(result.collision_normal) < 0) {
can_apply_constant_speed = false;
Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized();
motion = motion_slide_norm * (motion_slide_up.length() - result.travel.slide(up_direction).length() - last_travel.slide(up_direction).length());
}
// Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling.
else if ((sliding_enabled || !on_floor) && (!on_ceiling || slide_on_ceiling || !vel_dir_facing_up) && !apply_ceiling_velocity) {
Vector2 slide_motion = result.remainder.slide(result.collision_normal);
if (slide_motion.dot(velocity) > 0.0) {
motion = slide_motion;
} else {
motion = Vector2();
}
if (slide_on_ceiling && on_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(result.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 (on_ceiling && !slide_on_ceiling && vel_dir_facing_up) {
velocity = velocity.slide(up_direction);
motion = motion.slide(up_direction);
}
}
last_travel = result.travel;
}
// 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;
Transform2D gt = get_global_transform();
gt.columns[2] = prev_position;
set_global_transform(gt);
Vector2 motion_slide_norm = motion.slide(prev_floor_normal).normalized();
motion = motion_slide_norm * (motion_slide_up.length());
collided = true;
}
can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled;
sliding_enabled = true;
first_slide = false;
if (!collided || motion.is_zero_approx()) {
break;
}
}
_snap_on_floor(p_was_on_floor, vel_dir_facing_up);
// Scales the horizontal velocity according to the wall slope.
if (is_on_wall_only() && motion_slide_up.dot(motion_results.get(0).collision_normal) < 0) {
Vector2 slide_motion = velocity.slide(motion_results.get(0).collision_normal);
if (motion_slide_up.dot(slide_motion) < 0) {
velocity = up_direction * up_direction.dot(velocity);
} else {
// 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);
}
}
// Reset the gravity accumulation when touching the ground.
if (on_floor && !vel_dir_facing_up) {
velocity = velocity.slide(up_direction);
}
}
void CharacterBody2D::_move_and_slide_floating(double p_delta) {
Vector2 motion = velocity * p_delta;
platform_rid = RID();
platform_object_id = ObjectID();
floor_normal = Vector2();
platform_velocity = Vector2();
bool first_slide = true;
for (int iteration = 0; iteration < max_slides; ++iteration) {
PhysicsServer2D::MotionParameters parameters(get_global_transform(), motion, margin);
parameters.recovery_as_collision = true; // Also report collisions generated only from recovery.
PhysicsServer2D::MotionResult result;
bool collided = move_and_collide(parameters, result, false, false);
last_motion = result.travel;
if (collided) {
motion_results.push_back(result);
_set_collision_direction(result);
if (result.remainder.is_zero_approx()) {
motion = Vector2();
break;
}
if (wall_min_slide_angle != 0 && result.get_angle(-velocity.normalized()) < wall_min_slide_angle + FLOOR_ANGLE_THRESHOLD) {
motion = Vector2();
} else if (first_slide) {
Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized();
motion = motion_slide_norm * (motion.length() - result.travel.length());
} else {
motion = result.remainder.slide(result.collision_normal);
}
if (motion.dot(velocity) <= 0.0) {
motion = Vector2();
}
}
if (!collided || motion.is_zero_approx()) {
break;
}
first_slide = false;
}
}
void CharacterBody2D::apply_floor_snap() {
_apply_floor_snap();
}
// Method that avoids the p_wall_as_floor parameter for the public method.
void CharacterBody2D::_apply_floor_snap(bool p_wall_as_floor) {
if (on_floor) {
return;
}
// Snap by at least collision margin to keep floor state consistent.
real_t length = MAX(floor_snap_length, margin);
PhysicsServer2D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin);
parameters.recovery_as_collision = true; // Also report collisions generated only from recovery.
parameters.collide_separation_ray = true;
PhysicsServer2D::MotionResult result;
if (move_and_collide(parameters, result, true, false)) {
if ((result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) ||
(p_wall_as_floor && result.get_angle(-up_direction) > floor_max_angle + FLOOR_ANGLE_THRESHOLD)) {
on_floor = true;
floor_normal = result.collision_normal;
_set_platform_data(result);
if (floor_stop_on_slope) {
// move and collide may stray the object a bit because of pre un-stucking,
// so only ensure that motion happens on floor direction in this case.
if (result.travel.length() > margin) {
result.travel = up_direction * up_direction.dot(result.travel);
} else {
result.travel = Vector2();
}
}
parameters.from.columns[2] += result.travel;
set_global_transform(parameters.from);
}
}
}
void CharacterBody2D::_snap_on_floor(bool p_was_on_floor, bool p_vel_dir_facing_up, bool p_wall_as_floor) {
if (on_floor || !p_was_on_floor || p_vel_dir_facing_up) {
return;
}
_apply_floor_snap(p_wall_as_floor);
}
bool CharacterBody2D::_on_floor_if_snapped(bool p_was_on_floor, bool p_vel_dir_facing_up) {
if (up_direction == Vector2() || on_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);
PhysicsServer2D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin);
parameters.recovery_as_collision = true; // Also report collisions generated only from recovery.
parameters.collide_separation_ray = true;
PhysicsServer2D::MotionResult result;
if (move_and_collide(parameters, result, true, false)) {
if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {
return true;
}
}
return false;
}
void CharacterBody2D::_set_collision_direction(const PhysicsServer2D::MotionResult &p_result) {
if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor
on_floor = true;
floor_normal = p_result.collision_normal;
_set_platform_data(p_result);
} else if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling
on_ceiling = true;
} else {
on_wall = true;
wall_normal = p_result.collision_normal;
// Don't apply wall velocity when the collider is a CharacterBody2D.
if (Object::cast_to<CharacterBody2D>(ObjectDB::get_instance(p_result.collider_id)) == nullptr) {
_set_platform_data(p_result);
}
}
}
void CharacterBody2D::_set_platform_data(const PhysicsServer2D::MotionResult &p_result) {
platform_rid = p_result.collider;
platform_object_id = p_result.collider_id;
platform_velocity = p_result.collider_velocity;
platform_layer = PhysicsServer2D::get_singleton()->body_get_collision_layer(platform_rid);
}
const Vector2 &CharacterBody2D::get_velocity() const {
return velocity;
}
void CharacterBody2D::set_velocity(const Vector2 &p_velocity) {
velocity = p_velocity;
}
bool CharacterBody2D::is_on_floor() const {
return on_floor;
}
bool CharacterBody2D::is_on_floor_only() const {
return on_floor && !on_wall && !on_ceiling;
}
bool CharacterBody2D::is_on_wall() const {
return on_wall;
}
bool CharacterBody2D::is_on_wall_only() const {
return on_wall && !on_floor && !on_ceiling;
}
bool CharacterBody2D::is_on_ceiling() const {
return on_ceiling;
}
bool CharacterBody2D::is_on_ceiling_only() const {
return on_ceiling && !on_floor && !on_wall;
}
const Vector2 &CharacterBody2D::get_floor_normal() const {
return floor_normal;
}
const Vector2 &CharacterBody2D::get_wall_normal() const {
return wall_normal;
}
const Vector2 &CharacterBody2D::get_last_motion() const {
return last_motion;
}
Vector2 CharacterBody2D::get_position_delta() const {
return get_global_transform().columns[2] - previous_position;
}
const Vector2 &CharacterBody2D::get_real_velocity() const {
return real_velocity;
}
real_t CharacterBody2D::get_floor_angle(const Vector2 &p_up_direction) const {
ERR_FAIL_COND_V(p_up_direction == Vector2(), 0);
return Math::acos(floor_normal.dot(p_up_direction));
}
const Vector2 &CharacterBody2D::get_platform_velocity() const {
return platform_velocity;
}
int CharacterBody2D::get_slide_collision_count() const {
return motion_results.size();
}
PhysicsServer2D::MotionResult CharacterBody2D::get_slide_collision(int p_bounce) const {
ERR_FAIL_INDEX_V(p_bounce, motion_results.size(), PhysicsServer2D::MotionResult());
return motion_results[p_bounce];
}
Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) {
ERR_FAIL_INDEX_V(p_bounce, motion_results.size(), Ref<KinematicCollision2D>());
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<KinematicCollision2D> CharacterBody2D::_get_last_slide_collision() {
if (motion_results.size() == 0) {
return Ref<KinematicCollision2D>();
}
return _get_slide_collision(motion_results.size() - 1);
}
void CharacterBody2D::set_safe_margin(real_t p_margin) {
margin = p_margin;
}
real_t CharacterBody2D::get_safe_margin() const {
return margin;
}
bool CharacterBody2D::is_floor_stop_on_slope_enabled() const {
return floor_stop_on_slope;
}
void CharacterBody2D::set_floor_stop_on_slope_enabled(bool p_enabled) {
floor_stop_on_slope = p_enabled;
}
bool CharacterBody2D::is_floor_constant_speed_enabled() const {
return floor_constant_speed;
}
void CharacterBody2D::set_floor_constant_speed_enabled(bool p_enabled) {
floor_constant_speed = p_enabled;
}
bool CharacterBody2D::is_floor_block_on_wall_enabled() const {
return floor_block_on_wall;
}
void CharacterBody2D::set_floor_block_on_wall_enabled(bool p_enabled) {
floor_block_on_wall = p_enabled;
}
bool CharacterBody2D::is_slide_on_ceiling_enabled() const {
return slide_on_ceiling;
}
void CharacterBody2D::set_slide_on_ceiling_enabled(bool p_enabled) {
slide_on_ceiling = p_enabled;
}
uint32_t CharacterBody2D::get_platform_floor_layers() const {
return platform_floor_layers;
}
void CharacterBody2D::set_platform_floor_layers(uint32_t p_exclude_layers) {
platform_floor_layers = p_exclude_layers;
}
uint32_t CharacterBody2D::get_platform_wall_layers() const {
return platform_wall_layers;
}
void CharacterBody2D::set_platform_wall_layers(uint32_t p_exclude_layers) {
platform_wall_layers = p_exclude_layers;
}
void CharacterBody2D::set_motion_mode(MotionMode p_mode) {
motion_mode = p_mode;
}
CharacterBody2D::MotionMode CharacterBody2D::get_motion_mode() const {
return motion_mode;
}
void CharacterBody2D::set_platform_on_leave(PlatformOnLeave p_on_leave_apply_velocity) {
platform_on_leave = p_on_leave_apply_velocity;
}
CharacterBody2D::PlatformOnLeave CharacterBody2D::get_platform_on_leave() const {
return platform_on_leave;
}
int CharacterBody2D::get_max_slides() const {
return max_slides;
}
void CharacterBody2D::set_max_slides(int p_max_slides) {
ERR_FAIL_COND(p_max_slides < 1);
max_slides = p_max_slides;
}
real_t CharacterBody2D::get_floor_max_angle() const {
return floor_max_angle;
}
void CharacterBody2D::set_floor_max_angle(real_t p_radians) {
floor_max_angle = p_radians;
}
real_t CharacterBody2D::get_floor_snap_length() {
return floor_snap_length;
}
void CharacterBody2D::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 CharacterBody2D::get_wall_min_slide_angle() const {
return wall_min_slide_angle;
}
void CharacterBody2D::set_wall_min_slide_angle(real_t p_radians) {
wall_min_slide_angle = p_radians;
}
const Vector2 &CharacterBody2D::get_up_direction() const {
return up_direction;
}
void CharacterBody2D::set_up_direction(const Vector2 &p_up_direction) {
ERR_FAIL_COND_MSG(p_up_direction == Vector2(), "up_direction can't be equal to Vector2.ZERO, consider using Floating motion mode instead.");
up_direction = p_up_direction.normalized();
}
void CharacterBody2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
// Reset move_and_slide() data.
on_floor = false;
platform_rid = RID();
platform_object_id = ObjectID();
on_ceiling = false;
on_wall = false;
motion_results.clear();
platform_velocity = Vector2();
} break;
}
}
void CharacterBody2D::_validate_property(PropertyInfo &p_property) const {
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;
}
} else {
if (p_property.name == "wall_min_slide_angle") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
}
void CharacterBody2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("move_and_slide"), &CharacterBody2D::move_and_slide);
ClassDB::bind_method(D_METHOD("apply_floor_snap"), &CharacterBody2D::apply_floor_snap);
ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &CharacterBody2D::set_velocity);
ClassDB::bind_method(D_METHOD("get_velocity"), &CharacterBody2D::get_velocity);
ClassDB::bind_method(D_METHOD("set_safe_margin", "margin"), &CharacterBody2D::set_safe_margin);
ClassDB::bind_method(D_METHOD("get_safe_margin"), &CharacterBody2D::get_safe_margin);
ClassDB::bind_method(D_METHOD("is_floor_stop_on_slope_enabled"), &CharacterBody2D::is_floor_stop_on_slope_enabled);
ClassDB::bind_method(D_METHOD("set_floor_stop_on_slope_enabled", "enabled"), &CharacterBody2D::set_floor_stop_on_slope_enabled);
ClassDB::bind_method(D_METHOD("set_floor_constant_speed_enabled", "enabled"), &CharacterBody2D::set_floor_constant_speed_enabled);
ClassDB::bind_method(D_METHOD("is_floor_constant_speed_enabled"), &CharacterBody2D::is_floor_constant_speed_enabled);
ClassDB::bind_method(D_METHOD("set_floor_block_on_wall_enabled", "enabled"), &CharacterBody2D::set_floor_block_on_wall_enabled);
ClassDB::bind_method(D_METHOD("is_floor_block_on_wall_enabled"), &CharacterBody2D::is_floor_block_on_wall_enabled);
ClassDB::bind_method(D_METHOD("set_slide_on_ceiling_enabled", "enabled"), &CharacterBody2D::set_slide_on_ceiling_enabled);
ClassDB::bind_method(D_METHOD("is_slide_on_ceiling_enabled"), &CharacterBody2D::is_slide_on_ceiling_enabled);
ClassDB::bind_method(D_METHOD("set_platform_floor_layers", "exclude_layer"), &CharacterBody2D::set_platform_floor_layers);
ClassDB::bind_method(D_METHOD("get_platform_floor_layers"), &CharacterBody2D::get_platform_floor_layers);
ClassDB::bind_method(D_METHOD("set_platform_wall_layers", "exclude_layer"), &CharacterBody2D::set_platform_wall_layers);
ClassDB::bind_method(D_METHOD("get_platform_wall_layers"), &CharacterBody2D::get_platform_wall_layers);
ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody2D::get_max_slides);
ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody2D::set_max_slides);
ClassDB::bind_method(D_METHOD("get_floor_max_angle"), &CharacterBody2D::get_floor_max_angle);
ClassDB::bind_method(D_METHOD("set_floor_max_angle", "radians"), &CharacterBody2D::set_floor_max_angle);
ClassDB::bind_method(D_METHOD("get_floor_snap_length"), &CharacterBody2D::get_floor_snap_length);
ClassDB::bind_method(D_METHOD("set_floor_snap_length", "floor_snap_length"), &CharacterBody2D::set_floor_snap_length);
ClassDB::bind_method(D_METHOD("get_wall_min_slide_angle"), &CharacterBody2D::get_wall_min_slide_angle);
ClassDB::bind_method(D_METHOD("set_wall_min_slide_angle", "radians"), &CharacterBody2D::set_wall_min_slide_angle);
ClassDB::bind_method(D_METHOD("get_up_direction"), &CharacterBody2D::get_up_direction);
ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody2D::set_up_direction);
ClassDB::bind_method(D_METHOD("set_motion_mode", "mode"), &CharacterBody2D::set_motion_mode);
ClassDB::bind_method(D_METHOD("get_motion_mode"), &CharacterBody2D::get_motion_mode);
ClassDB::bind_method(D_METHOD("set_platform_on_leave", "on_leave_apply_velocity"), &CharacterBody2D::set_platform_on_leave);
ClassDB::bind_method(D_METHOD("get_platform_on_leave"), &CharacterBody2D::get_platform_on_leave);
ClassDB::bind_method(D_METHOD("is_on_floor"), &CharacterBody2D::is_on_floor);
ClassDB::bind_method(D_METHOD("is_on_floor_only"), &CharacterBody2D::is_on_floor_only);
ClassDB::bind_method(D_METHOD("is_on_ceiling"), &CharacterBody2D::is_on_ceiling);
ClassDB::bind_method(D_METHOD("is_on_ceiling_only"), &CharacterBody2D::is_on_ceiling_only);
ClassDB::bind_method(D_METHOD("is_on_wall"), &CharacterBody2D::is_on_wall);
ClassDB::bind_method(D_METHOD("is_on_wall_only"), &CharacterBody2D::is_on_wall_only);
ClassDB::bind_method(D_METHOD("get_floor_normal"), &CharacterBody2D::get_floor_normal);
ClassDB::bind_method(D_METHOD("get_wall_normal"), &CharacterBody2D::get_wall_normal);
ClassDB::bind_method(D_METHOD("get_last_motion"), &CharacterBody2D::get_last_motion);
ClassDB::bind_method(D_METHOD("get_position_delta"), &CharacterBody2D::get_position_delta);
ClassDB::bind_method(D_METHOD("get_real_velocity"), &CharacterBody2D::get_real_velocity);
ClassDB::bind_method(D_METHOD("get_floor_angle", "up_direction"), &CharacterBody2D::get_floor_angle, DEFVAL(Vector2(0.0, -1.0)));
ClassDB::bind_method(D_METHOD("get_platform_velocity"), &CharacterBody2D::get_platform_velocity);
ClassDB::bind_method(D_METHOD("get_slide_collision_count"), &CharacterBody2D::get_slide_collision_count);
ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody2D::_get_slide_collision);
ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody2D::_get_last_slide_collision);
ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Floating", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "up_direction"), "set_up_direction", "get_up_direction");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "velocity", PROPERTY_HINT_NONE, "suffix:px/s", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_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,32,0.1,or_greater,suffix:px"), "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_2D_PHYSICS), "set_platform_floor_layers", "get_platform_floor_layers");
ADD_PROPERTY(PropertyInfo(Variant::INT, "platform_wall_layers", PROPERTY_HINT_LAYERS_2D_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:px"), "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);
}
CharacterBody2D::CharacterBody2D() :
PhysicsBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) {
}

View file

@ -0,0 +1,172 @@
/**************************************************************************/
/* character_body_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CHARACTER_BODY_2D_H
#define CHARACTER_BODY_2D_H
#include "scene/2d/physics/kinematic_collision_2d.h"
#include "scene/2d/physics/physics_body_2d.h"
class CharacterBody2D : public PhysicsBody2D {
GDCLASS(CharacterBody2D, PhysicsBody2D);
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 Vector2 &get_velocity() const;
void set_velocity(const Vector2 &p_velocity);
bool is_on_floor() const;
bool is_on_floor_only() const;
bool is_on_wall() const;
bool is_on_wall_only() const;
bool is_on_ceiling() const;
bool is_on_ceiling_only() const;
const Vector2 &get_last_motion() const;
Vector2 get_position_delta() const;
const Vector2 &get_floor_normal() const;
const Vector2 &get_wall_normal() const;
const Vector2 &get_real_velocity() const;
real_t get_floor_angle(const Vector2 &p_up_direction = Vector2(0.0, -1.0)) const;
const Vector2 &get_platform_velocity() const;
int get_slide_collision_count() const;
PhysicsServer2D::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;
CharacterBody2D();
private:
real_t margin = 0.08;
MotionMode motion_mode = MOTION_MODE_GROUNDED;
PlatformOnLeave platform_on_leave = PLATFORM_ON_LEAVE_ADD_VELOCITY;
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 = 4;
int platform_layer = 0;
real_t floor_max_angle = Math::deg_to_rad((real_t)45.0);
real_t floor_snap_length = 1;
real_t wall_min_slide_angle = Math::deg_to_rad((real_t)15.0);
Vector2 up_direction = Vector2(0.0, -1.0);
uint32_t platform_floor_layers = UINT32_MAX;
uint32_t platform_wall_layers = 0;
Vector2 velocity;
Vector2 floor_normal;
Vector2 platform_velocity;
Vector2 wall_normal;
Vector2 last_motion;
Vector2 previous_position;
Vector2 real_velocity;
RID platform_rid;
ObjectID platform_object_id;
bool on_floor = false;
bool on_ceiling = false;
bool on_wall = false;
Vector<PhysicsServer2D::MotionResult> motion_results;
Vector<Ref<KinematicCollision2D>> slide_colliders;
void _move_and_slide_floating(double p_delta);
void _move_and_slide_grounded(double p_delta, bool p_was_on_floor);
Ref<KinematicCollision2D> _get_slide_collision(int p_bounce);
Ref<KinematicCollision2D> _get_last_slide_collision();
const Vector2 &get_up_direction() const;
bool _on_floor_if_snapped(bool p_was_on_floor, bool p_vel_dir_facing_up);
void set_up_direction(const Vector2 &p_up_direction);
void _set_collision_direction(const PhysicsServer2D::MotionResult &p_result);
void _set_platform_data(const PhysicsServer2D::MotionResult &p_result);
void _apply_floor_snap(bool p_wall_as_floor = false);
void _snap_on_floor(bool p_was_on_floor, bool p_vel_dir_facing_up, bool p_wall_as_floor = false);
protected:
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
};
VARIANT_ENUM_CAST(CharacterBody2D::MotionMode);
VARIANT_ENUM_CAST(CharacterBody2D::PlatformOnLeave);
#endif // CHARACTER_BODY_2D_H

View file

@ -0,0 +1,683 @@
/**************************************************************************/
/* collision_object_2d.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_2d.h"
#include "scene/resources/world_2d.h"
void CollisionObject2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
Transform2D gl_transform = get_global_transform();
if (area) {
PhysicsServer2D::get_singleton()->area_set_transform(rid, gl_transform);
} else {
PhysicsServer2D::get_singleton()->body_set_state(rid, PhysicsServer2D::BODY_STATE_TRANSFORM, gl_transform);
}
bool disabled = !is_enabled();
if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) {
_apply_disabled();
}
if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) {
Ref<World2D> world_ref = get_world_2d();
ERR_FAIL_COND(!world_ref.is_valid());
RID space = world_ref->get_space();
if (area) {
PhysicsServer2D::get_singleton()->area_set_space(rid, space);
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, space);
}
_space_changed(space);
}
_update_pickable();
} break;
case NOTIFICATION_ENTER_CANVAS: {
if (area) {
PhysicsServer2D::get_singleton()->area_attach_canvas_instance_id(rid, get_canvas_layer_instance_id());
} else {
PhysicsServer2D::get_singleton()->body_attach_canvas_instance_id(rid, get_canvas_layer_instance_id());
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
_update_pickable();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if (only_update_transform_changes) {
return;
}
Transform2D gl_transform = get_global_transform();
if (area) {
PhysicsServer2D::get_singleton()->area_set_transform(rid, gl_transform);
} else {
PhysicsServer2D::get_singleton()->body_set_state(rid, PhysicsServer2D::BODY_STATE_TRANSFORM, gl_transform);
}
} break;
case NOTIFICATION_EXIT_TREE: {
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) {
PhysicsServer2D::get_singleton()->area_set_space(rid, RID());
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, RID());
}
_space_changed(RID());
}
}
if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) {
_apply_enabled();
}
} break;
case NOTIFICATION_EXIT_CANVAS: {
if (area) {
PhysicsServer2D::get_singleton()->area_attach_canvas_instance_id(rid, ObjectID());
} else {
PhysicsServer2D::get_singleton()->body_attach_canvas_instance_id(rid, ObjectID());
}
} break;
case NOTIFICATION_WORLD_2D_CHANGED: {
RID space = get_world_2d()->get_space();
if (area) {
PhysicsServer2D::get_singleton()->area_set_space(rid, space);
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, space);
}
_space_changed(space);
} break;
case NOTIFICATION_DISABLED: {
_apply_disabled();
} break;
case NOTIFICATION_ENABLED: {
_apply_enabled();
} break;
}
}
void CollisionObject2D::set_collision_layer(uint32_t p_layer) {
collision_layer = p_layer;
if (area) {
PhysicsServer2D::get_singleton()->area_set_collision_layer(get_rid(), p_layer);
} else {
PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), p_layer);
}
}
uint32_t CollisionObject2D::get_collision_layer() const {
return collision_layer;
}
void CollisionObject2D::set_collision_mask(uint32_t p_mask) {
collision_mask = p_mask;
if (area) {
PhysicsServer2D::get_singleton()->area_set_collision_mask(get_rid(), p_mask);
} else {
PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), p_mask);
}
}
uint32_t CollisionObject2D::get_collision_mask() const {
return collision_mask;
}
void CollisionObject2D::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 CollisionObject2D::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 CollisionObject2D::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 CollisionObject2D::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 CollisionObject2D::set_collision_priority(real_t p_priority) {
collision_priority = p_priority;
if (!area) {
PhysicsServer2D::get_singleton()->body_set_collision_priority(get_rid(), p_priority);
}
}
real_t CollisionObject2D::get_collision_priority() const {
return collision_priority;
}
void CollisionObject2D::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();
}
}
CollisionObject2D::DisableMode CollisionObject2D::get_disable_mode() const {
return disable_mode;
}
void CollisionObject2D::_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) {
PhysicsServer2D::get_singleton()->area_set_space(rid, RID());
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, RID());
}
_space_changed(RID());
}
}
} break;
case DISABLE_MODE_MAKE_STATIC: {
if (!area && (body_mode != PhysicsServer2D::BODY_MODE_STATIC)) {
PhysicsServer2D::get_singleton()->body_set_mode(rid, PhysicsServer2D::BODY_MODE_STATIC);
}
} break;
case DISABLE_MODE_KEEP_ACTIVE: {
// Nothing to do.
} break;
}
}
void CollisionObject2D::_apply_enabled() {
switch (disable_mode) {
case DISABLE_MODE_REMOVE: {
if (is_inside_tree()) {
RID space = get_world_2d()->get_space();
if (area) {
PhysicsServer2D::get_singleton()->area_set_space(rid, space);
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, space);
}
_space_changed(space);
}
} break;
case DISABLE_MODE_MAKE_STATIC: {
if (!area && (body_mode != PhysicsServer2D::BODY_MODE_STATIC)) {
PhysicsServer2D::get_singleton()->body_set_mode(rid, body_mode);
}
} break;
case DISABLE_MODE_KEEP_ACTIVE: {
// Nothing to do.
} break;
}
}
uint32_t CollisionObject2D::create_shape_owner(Object *p_owner) {
ShapeData sd;
uint32_t id;
if (shapes.size() == 0) {
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 CollisionObject2D::remove_shape_owner(uint32_t owner) {
ERR_FAIL_COND(!shapes.has(owner));
shape_owner_clear_shapes(owner);
shapes.erase(owner);
}
void CollisionObject2D::shape_owner_set_disabled(uint32_t p_owner, bool p_disabled) {
ERR_FAIL_COND(!shapes.has(p_owner));
ShapeData &sd = shapes[p_owner];
sd.disabled = p_disabled;
for (int i = 0; i < sd.shapes.size(); i++) {
if (area) {
PhysicsServer2D::get_singleton()->area_set_shape_disabled(rid, sd.shapes[i].index, p_disabled);
} else {
PhysicsServer2D::get_singleton()->body_set_shape_disabled(rid, sd.shapes[i].index, p_disabled);
}
}
}
bool CollisionObject2D::is_shape_owner_disabled(uint32_t p_owner) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), false);
return shapes[p_owner].disabled;
}
void CollisionObject2D::shape_owner_set_one_way_collision(uint32_t p_owner, bool p_enable) {
if (area) {
return; //not for areas
}
ERR_FAIL_COND(!shapes.has(p_owner));
ShapeData &sd = shapes[p_owner];
sd.one_way_collision = p_enable;
for (int i = 0; i < sd.shapes.size(); i++) {
PhysicsServer2D::get_singleton()->body_set_shape_as_one_way_collision(rid, sd.shapes[i].index, sd.one_way_collision, sd.one_way_collision_margin);
}
}
bool CollisionObject2D::is_shape_owner_one_way_collision_enabled(uint32_t p_owner) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), false);
return shapes[p_owner].one_way_collision;
}
void CollisionObject2D::shape_owner_set_one_way_collision_margin(uint32_t p_owner, real_t p_margin) {
if (area) {
return; //not for areas
}
ERR_FAIL_COND(!shapes.has(p_owner));
ShapeData &sd = shapes[p_owner];
sd.one_way_collision_margin = p_margin;
for (int i = 0; i < sd.shapes.size(); i++) {
PhysicsServer2D::get_singleton()->body_set_shape_as_one_way_collision(rid, sd.shapes[i].index, sd.one_way_collision, sd.one_way_collision_margin);
}
}
real_t CollisionObject2D::get_shape_owner_one_way_collision_margin(uint32_t p_owner) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), 0);
return shapes[p_owner].one_way_collision_margin;
}
void CollisionObject2D::get_shape_owners(List<uint32_t> *r_owners) {
for (const KeyValue<uint32_t, ShapeData> &E : shapes) {
r_owners->push_back(E.key);
}
}
PackedInt32Array CollisionObject2D::_get_shape_owners() {
PackedInt32Array ret;
for (const KeyValue<uint32_t, ShapeData> &E : shapes) {
ret.push_back(E.key);
}
return ret;
}
void CollisionObject2D::shape_owner_set_transform(uint32_t p_owner, const Transform2D &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) {
PhysicsServer2D::get_singleton()->area_set_shape_transform(rid, sd.shapes[i].index, sd.xform);
} else {
PhysicsServer2D::get_singleton()->body_set_shape_transform(rid, sd.shapes[i].index, sd.xform);
}
}
}
Transform2D CollisionObject2D::shape_owner_get_transform(uint32_t p_owner) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), Transform2D());
return shapes[p_owner].xform;
}
Object *CollisionObject2D::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 CollisionObject2D::shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2D> &p_shape) {
ERR_FAIL_COND(!shapes.has(p_owner));
ERR_FAIL_COND(p_shape.is_null());
ShapeData &sd = shapes[p_owner];
ShapeData::Shape s;
s.index = total_subshapes;
s.shape = p_shape;
if (area) {
PhysicsServer2D::get_singleton()->area_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled);
} else {
PhysicsServer2D::get_singleton()->body_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled);
}
sd.shapes.push_back(s);
total_subshapes++;
}
int CollisionObject2D::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<Shape2D> CollisionObject2D::shape_owner_get_shape(uint32_t p_owner, int p_shape) const {
ERR_FAIL_COND_V(!shapes.has(p_owner), Ref<Shape2D>());
ERR_FAIL_INDEX_V(p_shape, shapes[p_owner].shapes.size(), Ref<Shape2D>());
return shapes[p_owner].shapes[p_shape].shape;
}
int CollisionObject2D::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 CollisionObject2D::shape_owner_remove_shape(uint32_t p_owner, int p_shape) {
ERR_FAIL_COND(!shapes.has(p_owner));
ERR_FAIL_INDEX(p_shape, shapes[p_owner].shapes.size());
int index_to_remove = shapes[p_owner].shapes[p_shape].index;
if (area) {
PhysicsServer2D::get_singleton()->area_remove_shape(rid, index_to_remove);
} else {
PhysicsServer2D::get_singleton()->body_remove_shape(rid, index_to_remove);
}
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 CollisionObject2D::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);
}
}
uint32_t CollisionObject2D::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) + ".");
}
void CollisionObject2D::set_pickable(bool p_enabled) {
if (pickable == p_enabled) {
return;
}
pickable = p_enabled;
_update_pickable();
}
bool CollisionObject2D::is_pickable() const {
return pickable;
}
void CollisionObject2D::_input_event_call(Viewport *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape) {
GDVIRTUAL_CALL(_input_event, p_viewport, p_input_event, p_shape);
emit_signal(SceneStringName(input_event), p_viewport, p_input_event, p_shape);
}
void CollisionObject2D::_mouse_enter() {
GDVIRTUAL_CALL(_mouse_enter);
emit_signal(SceneStringName(mouse_entered));
}
void CollisionObject2D::_mouse_exit() {
GDVIRTUAL_CALL(_mouse_exit);
emit_signal(SceneStringName(mouse_exited));
}
void CollisionObject2D::_mouse_shape_enter(int p_shape) {
GDVIRTUAL_CALL(_mouse_shape_enter, p_shape);
emit_signal(SceneStringName(mouse_shape_entered), p_shape);
}
void CollisionObject2D::_mouse_shape_exit(int p_shape) {
GDVIRTUAL_CALL(_mouse_shape_exit, p_shape);
emit_signal(SceneStringName(mouse_shape_exited), p_shape);
}
void CollisionObject2D::set_only_update_transform_changes(bool p_enable) {
only_update_transform_changes = p_enable;
}
bool CollisionObject2D::is_only_update_transform_changes_enabled() const {
return only_update_transform_changes;
}
void CollisionObject2D::set_body_mode(PhysicsServer2D::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;
}
PhysicsServer2D::get_singleton()->body_set_mode(rid, p_mode);
}
void CollisionObject2D::_space_changed(const RID &p_new_space) {
}
void CollisionObject2D::_update_pickable() {
if (!is_inside_tree()) {
return;
}
bool is_pickable = pickable && is_visible_in_tree();
if (area) {
PhysicsServer2D::get_singleton()->area_set_pickable(rid, is_pickable);
} else {
PhysicsServer2D::get_singleton()->body_set_pickable(rid, is_pickable);
}
}
PackedStringArray CollisionObject2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::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 CollisionShape2D or CollisionPolygon2D as a child to define its shape."));
}
return warnings;
}
void CollisionObject2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rid"), &CollisionObject2D::get_rid);
ClassDB::bind_method(D_METHOD("set_collision_layer", "layer"), &CollisionObject2D::set_collision_layer);
ClassDB::bind_method(D_METHOD("get_collision_layer"), &CollisionObject2D::get_collision_layer);
ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &CollisionObject2D::set_collision_mask);
ClassDB::bind_method(D_METHOD("get_collision_mask"), &CollisionObject2D::get_collision_mask);
ClassDB::bind_method(D_METHOD("set_collision_layer_value", "layer_number", "value"), &CollisionObject2D::set_collision_layer_value);
ClassDB::bind_method(D_METHOD("get_collision_layer_value", "layer_number"), &CollisionObject2D::get_collision_layer_value);
ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &CollisionObject2D::set_collision_mask_value);
ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &CollisionObject2D::get_collision_mask_value);
ClassDB::bind_method(D_METHOD("set_collision_priority", "priority"), &CollisionObject2D::set_collision_priority);
ClassDB::bind_method(D_METHOD("get_collision_priority"), &CollisionObject2D::get_collision_priority);
ClassDB::bind_method(D_METHOD("set_disable_mode", "mode"), &CollisionObject2D::set_disable_mode);
ClassDB::bind_method(D_METHOD("get_disable_mode"), &CollisionObject2D::get_disable_mode);
ClassDB::bind_method(D_METHOD("set_pickable", "enabled"), &CollisionObject2D::set_pickable);
ClassDB::bind_method(D_METHOD("is_pickable"), &CollisionObject2D::is_pickable);
ClassDB::bind_method(D_METHOD("create_shape_owner", "owner"), &CollisionObject2D::create_shape_owner);
ClassDB::bind_method(D_METHOD("remove_shape_owner", "owner_id"), &CollisionObject2D::remove_shape_owner);
ClassDB::bind_method(D_METHOD("get_shape_owners"), &CollisionObject2D::_get_shape_owners);
ClassDB::bind_method(D_METHOD("shape_owner_set_transform", "owner_id", "transform"), &CollisionObject2D::shape_owner_set_transform);
ClassDB::bind_method(D_METHOD("shape_owner_get_transform", "owner_id"), &CollisionObject2D::shape_owner_get_transform);
ClassDB::bind_method(D_METHOD("shape_owner_get_owner", "owner_id"), &CollisionObject2D::shape_owner_get_owner);
ClassDB::bind_method(D_METHOD("shape_owner_set_disabled", "owner_id", "disabled"), &CollisionObject2D::shape_owner_set_disabled);
ClassDB::bind_method(D_METHOD("is_shape_owner_disabled", "owner_id"), &CollisionObject2D::is_shape_owner_disabled);
ClassDB::bind_method(D_METHOD("shape_owner_set_one_way_collision", "owner_id", "enable"), &CollisionObject2D::shape_owner_set_one_way_collision);
ClassDB::bind_method(D_METHOD("is_shape_owner_one_way_collision_enabled", "owner_id"), &CollisionObject2D::is_shape_owner_one_way_collision_enabled);
ClassDB::bind_method(D_METHOD("shape_owner_set_one_way_collision_margin", "owner_id", "margin"), &CollisionObject2D::shape_owner_set_one_way_collision_margin);
ClassDB::bind_method(D_METHOD("get_shape_owner_one_way_collision_margin", "owner_id"), &CollisionObject2D::get_shape_owner_one_way_collision_margin);
ClassDB::bind_method(D_METHOD("shape_owner_add_shape", "owner_id", "shape"), &CollisionObject2D::shape_owner_add_shape);
ClassDB::bind_method(D_METHOD("shape_owner_get_shape_count", "owner_id"), &CollisionObject2D::shape_owner_get_shape_count);
ClassDB::bind_method(D_METHOD("shape_owner_get_shape", "owner_id", "shape_id"), &CollisionObject2D::shape_owner_get_shape);
ClassDB::bind_method(D_METHOD("shape_owner_get_shape_index", "owner_id", "shape_id"), &CollisionObject2D::shape_owner_get_shape_index);
ClassDB::bind_method(D_METHOD("shape_owner_remove_shape", "owner_id", "shape_id"), &CollisionObject2D::shape_owner_remove_shape);
ClassDB::bind_method(D_METHOD("shape_owner_clear_shapes", "owner_id"), &CollisionObject2D::shape_owner_clear_shapes);
ClassDB::bind_method(D_METHOD("shape_find_owner", "shape_index"), &CollisionObject2D::shape_find_owner);
GDVIRTUAL_BIND(_input_event, "viewport", "event", "shape_idx");
GDVIRTUAL_BIND(_mouse_enter);
GDVIRTUAL_BIND(_mouse_exit);
GDVIRTUAL_BIND(_mouse_shape_enter, "shape_idx");
GDVIRTUAL_BIND(_mouse_shape_exit, "shape_idx");
ADD_SIGNAL(MethodInfo("input_event", PropertyInfo(Variant::OBJECT, "viewport", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::INT, "shape_idx")));
ADD_SIGNAL(MethodInfo("mouse_entered"));
ADD_SIGNAL(MethodInfo("mouse_exited"));
ADD_SIGNAL(MethodInfo("mouse_shape_entered", PropertyInfo(Variant::INT, "shape_idx")));
ADD_SIGNAL(MethodInfo("mouse_shape_exited", PropertyInfo(Variant::INT, "shape_idx")));
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_2D_PHYSICS), "set_collision_layer", "get_collision_layer");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_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_pickable"), "set_pickable", "is_pickable");
BIND_ENUM_CONSTANT(DISABLE_MODE_REMOVE);
BIND_ENUM_CONSTANT(DISABLE_MODE_MAKE_STATIC);
BIND_ENUM_CONSTANT(DISABLE_MODE_KEEP_ACTIVE);
}
CollisionObject2D::CollisionObject2D(RID p_rid, bool p_area) {
rid = p_rid;
area = p_area;
pickable = true;
set_notify_transform(true);
set_hide_clip_children(true);
total_subshapes = 0;
only_update_transform_changes = false;
if (p_area) {
PhysicsServer2D::get_singleton()->area_attach_object_instance_id(rid, get_instance_id());
} else {
PhysicsServer2D::get_singleton()->body_attach_object_instance_id(rid, get_instance_id());
PhysicsServer2D::get_singleton()->body_set_mode(rid, body_mode);
}
}
CollisionObject2D::CollisionObject2D() {
//owner=
set_notify_transform(true);
}
CollisionObject2D::~CollisionObject2D() {
ERR_FAIL_NULL(PhysicsServer2D::get_singleton());
PhysicsServer2D::get_singleton()->free(rid);
}

View file

@ -0,0 +1,179 @@
/**************************************************************************/
/* collision_object_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef COLLISION_OBJECT_2D_H
#define COLLISION_OBJECT_2D_H
#include "scene/2d/node_2d.h"
#include "scene/main/viewport.h"
#include "scene/resources/2d/shape_2d.h"
#include "servers/physics_server_2d.h"
class CollisionObject2D : public Node2D {
GDCLASS(CollisionObject2D, Node2D);
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;
bool pickable = false;
DisableMode disable_mode = DISABLE_MODE_REMOVE;
PhysicsServer2D::BodyMode body_mode = PhysicsServer2D::BODY_MODE_STATIC;
struct ShapeData {
ObjectID owner_id;
Transform2D xform;
struct Shape {
Ref<Shape2D> shape;
int index = 0;
};
Vector<Shape> shapes;
bool disabled = false;
bool one_way_collision = false;
real_t one_way_collision_margin = 0.0;
};
int total_subshapes = 0;
RBMap<uint32_t, ShapeData> shapes;
bool only_update_transform_changes = false; // This is used for sync to physics.
void _apply_disabled();
void _apply_enabled();
protected:
_FORCE_INLINE_ void lock_callback() { callback_lock++; }
_FORCE_INLINE_ void unlock_callback() {
ERR_FAIL_COND(callback_lock == 0);
callback_lock--;
}
CollisionObject2D(RID p_rid, bool p_area);
void _notification(int p_what);
static void _bind_methods();
void _update_pickable();
friend class Viewport;
void _input_event_call(Viewport *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape);
void _mouse_enter();
void _mouse_exit();
void _mouse_shape_enter(int p_shape);
void _mouse_shape_exit(int p_shape);
void set_only_update_transform_changes(bool p_enable);
bool is_only_update_transform_changes_enabled() const;
void set_body_mode(PhysicsServer2D::BodyMode p_mode);
virtual void _space_changed(const RID &p_new_space);
GDVIRTUAL3(_input_event, Viewport *, Ref<InputEvent>, int)
GDVIRTUAL0(_mouse_enter)
GDVIRTUAL0(_mouse_exit)
GDVIRTUAL1(_mouse_shape_enter, int)
GDVIRTUAL1(_mouse_shape_exit, int)
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 Transform2D &p_transform);
Transform2D 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_set_one_way_collision(uint32_t p_owner, bool p_enable);
bool is_shape_owner_one_way_collision_enabled(uint32_t p_owner) const;
void shape_owner_set_one_way_collision_margin(uint32_t p_owner, real_t p_margin);
real_t get_shape_owner_one_way_collision_margin(uint32_t p_owner) const;
void shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2D> &p_shape);
int shape_owner_get_shape_count(uint32_t p_owner) const;
Ref<Shape2D> 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_pickable(bool p_enabled);
bool is_pickable() const;
PackedStringArray get_configuration_warnings() const override;
_FORCE_INLINE_ RID get_rid() const { return rid; }
CollisionObject2D();
~CollisionObject2D();
};
VARIANT_ENUM_CAST(CollisionObject2D::DisableMode);
#endif // COLLISION_OBJECT_2D_H

View file

@ -0,0 +1,323 @@
/**************************************************************************/
/* collision_polygon_2d.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_2d.h"
#include "core/math/geometry_2d.h"
#include "scene/2d/physics/area_2d.h"
#include "scene/2d/physics/collision_object_2d.h"
#include "scene/resources/2d/concave_polygon_shape_2d.h"
#include "scene/resources/2d/convex_polygon_shape_2d.h"
#include "thirdparty/misc/polypartition.h"
void CollisionPolygon2D::_build_polygon() {
collision_object->shape_owner_clear_shapes(owner_id);
bool solids = build_mode == BUILD_SOLIDS;
if (solids) {
if (polygon.size() < 3) {
return;
}
//here comes the sun, lalalala
//decompose concave into multiple convex polygons and add them
Vector<Vector<Vector2>> decomp = _decompose_in_convex();
for (int i = 0; i < decomp.size(); i++) {
Ref<ConvexPolygonShape2D> convex = memnew(ConvexPolygonShape2D);
convex->set_points(decomp[i]);
collision_object->shape_owner_add_shape(owner_id, convex);
}
} else {
if (polygon.size() < 2) {
return;
}
Ref<ConcavePolygonShape2D> concave = memnew(ConcavePolygonShape2D);
Vector<Vector2> segments;
segments.resize(polygon.size() * 2);
Vector2 *w = segments.ptrw();
for (int i = 0; i < polygon.size(); i++) {
w[(i << 1) + 0] = polygon[i];
w[(i << 1) + 1] = polygon[(i + 1) % polygon.size()];
}
concave->set_segments(segments);
collision_object->shape_owner_add_shape(owner_id, concave);
}
}
Vector<Vector<Vector2>> CollisionPolygon2D::_decompose_in_convex() {
Vector<Vector<Vector2>> decomp = Geometry2D::decompose_polygon_in_convex(polygon);
return decomp;
}
void CollisionPolygon2D::_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);
collision_object->shape_owner_set_one_way_collision(owner_id, one_way_collision);
collision_object->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin);
}
void CollisionPolygon2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PARENTED: {
collision_object = Object::cast_to<CollisionObject2D>(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);
}
} break;
case NOTIFICATION_UNPARENTED: {
if (collision_object) {
collision_object->remove_shape_owner(owner_id);
}
owner_id = 0;
collision_object = nullptr;
} break;
case NOTIFICATION_DRAW: {
ERR_FAIL_COND(!is_inside_tree());
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
break;
}
if (polygon.size() > 2) {
#ifdef TOOLS_ENABLED
if (build_mode == BUILD_SOLIDS) {
Vector<Vector<Vector2>> decomp = _decompose_in_convex();
Color c(0.4, 0.9, 0.1);
for (int i = 0; i < decomp.size(); i++) {
c.set_hsv(Math::fmod(c.get_h() + 0.738, 1), c.get_s(), c.get_v(), 0.5);
draw_colored_polygon(decomp[i], c);
}
}
#endif
const Color stroke_color = get_tree()->get_debug_collisions_color();
draw_polyline(polygon, stroke_color);
// Draw the last segment.
draw_line(polygon[polygon.size() - 1], polygon[0], stroke_color);
}
if (one_way_collision) {
Color dcol = get_tree()->get_debug_collisions_color(); //0.9,0.2,0.2,0.4);
dcol.a = 1.0;
Vector2 line_to(0, 20);
draw_line(Vector2(), line_to, dcol, 3);
real_t tsize = 8;
Vector<Vector2> pts = {
line_to + Vector2(0, tsize),
line_to + Vector2(Math_SQRT12 * tsize, 0),
line_to + Vector2(-Math_SQRT12 * tsize, 0)
};
Vector<Color> cols{ dcol, dcol, dcol };
draw_primitive(pts, cols, Vector<Vector2>()); //small arrow
}
} break;
}
}
void CollisionPolygon2D::set_polygon(const Vector<Point2> &p_polygon) {
polygon = p_polygon;
{
for (int i = 0; i < polygon.size(); i++) {
if (i == 0) {
aabb = Rect2(polygon[i], Size2());
} else {
aabb.expand_to(polygon[i]);
}
}
if (aabb == Rect2()) {
aabb = Rect2(-10, -10, 20, 20);
} else {
aabb.position -= aabb.size * 0.3;
aabb.size += aabb.size * 0.6;
}
}
if (collision_object) {
_build_polygon();
_update_in_shape_owner();
}
queue_redraw();
update_configuration_warnings();
}
Vector<Point2> CollisionPolygon2D::get_polygon() const {
return polygon;
}
void CollisionPolygon2D::set_build_mode(BuildMode p_mode) {
ERR_FAIL_INDEX((int)p_mode, 2);
build_mode = p_mode;
if (collision_object) {
_build_polygon();
_update_in_shape_owner();
}
queue_redraw();
update_configuration_warnings();
}
CollisionPolygon2D::BuildMode CollisionPolygon2D::get_build_mode() const {
return build_mode;
}
#ifdef TOOLS_ENABLED
Rect2 CollisionPolygon2D::_edit_get_rect() const {
return aabb;
}
bool CollisionPolygon2D::_edit_use_rect() const {
return true;
}
bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return Geometry2D::is_point_in_polygon(p_point, Variant(polygon));
}
#endif
PackedStringArray CollisionPolygon2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (!Object::cast_to<CollisionObject2D>(get_parent())) {
warnings.push_back(RTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape."));
}
int polygon_count = polygon.size();
if (polygon_count == 0) {
warnings.push_back(RTR("An empty CollisionPolygon2D has no effect on collision."));
} else {
bool solids = build_mode == BUILD_SOLIDS;
if (solids) {
if (polygon_count < 3) {
warnings.push_back(RTR("Invalid polygon. At least 3 points are needed in 'Solids' build mode."));
}
} else if (polygon_count < 2) {
warnings.push_back(RTR("Invalid polygon. At least 2 points are needed in 'Segments' build mode."));
}
}
if (one_way_collision && Object::cast_to<Area2D>(get_parent())) {
warnings.push_back(RTR("The One Way Collision property will be ignored when the collision object is an Area2D."));
}
return warnings;
}
void CollisionPolygon2D::set_disabled(bool p_disabled) {
disabled = p_disabled;
queue_redraw();
if (collision_object) {
collision_object->shape_owner_set_disabled(owner_id, p_disabled);
}
}
bool CollisionPolygon2D::is_disabled() const {
return disabled;
}
void CollisionPolygon2D::set_one_way_collision(bool p_enable) {
one_way_collision = p_enable;
queue_redraw();
if (collision_object) {
collision_object->shape_owner_set_one_way_collision(owner_id, p_enable);
}
update_configuration_warnings();
}
bool CollisionPolygon2D::is_one_way_collision_enabled() const {
return one_way_collision;
}
void CollisionPolygon2D::set_one_way_collision_margin(real_t p_margin) {
one_way_collision_margin = p_margin;
if (collision_object) {
collision_object->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin);
}
}
real_t CollisionPolygon2D::get_one_way_collision_margin() const {
return one_way_collision_margin;
}
void CollisionPolygon2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &CollisionPolygon2D::set_polygon);
ClassDB::bind_method(D_METHOD("get_polygon"), &CollisionPolygon2D::get_polygon);
ClassDB::bind_method(D_METHOD("set_build_mode", "build_mode"), &CollisionPolygon2D::set_build_mode);
ClassDB::bind_method(D_METHOD("get_build_mode"), &CollisionPolygon2D::get_build_mode);
ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &CollisionPolygon2D::set_disabled);
ClassDB::bind_method(D_METHOD("is_disabled"), &CollisionPolygon2D::is_disabled);
ClassDB::bind_method(D_METHOD("set_one_way_collision", "enabled"), &CollisionPolygon2D::set_one_way_collision);
ClassDB::bind_method(D_METHOD("is_one_way_collision_enabled"), &CollisionPolygon2D::is_one_way_collision_enabled);
ClassDB::bind_method(D_METHOD("set_one_way_collision_margin", "margin"), &CollisionPolygon2D::set_one_way_collision_margin);
ClassDB::bind_method(D_METHOD("get_one_way_collision_margin"), &CollisionPolygon2D::get_one_way_collision_margin);
ADD_PROPERTY(PropertyInfo(Variant::INT, "build_mode", PROPERTY_HINT_ENUM, "Solids,Segments"), "set_build_mode", "get_build_mode");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_way_collision"), "set_one_way_collision", "is_one_way_collision_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "one_way_collision_margin", PROPERTY_HINT_RANGE, "0,128,0.1,suffix:px"), "set_one_way_collision_margin", "get_one_way_collision_margin");
BIND_ENUM_CONSTANT(BUILD_SOLIDS);
BIND_ENUM_CONSTANT(BUILD_SEGMENTS);
}
CollisionPolygon2D::CollisionPolygon2D() {
set_notify_local_transform(true);
set_hide_clip_children(true);
}

View file

@ -0,0 +1,96 @@
/**************************************************************************/
/* collision_polygon_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef COLLISION_POLYGON_2D_H
#define COLLISION_POLYGON_2D_H
#include "scene/2d/node_2d.h"
class CollisionObject2D;
class CollisionPolygon2D : public Node2D {
GDCLASS(CollisionPolygon2D, Node2D);
public:
enum BuildMode {
BUILD_SOLIDS,
BUILD_SEGMENTS,
};
protected:
Rect2 aabb = Rect2(-10, -10, 20, 20);
BuildMode build_mode = BUILD_SOLIDS;
Vector<Point2> polygon;
uint32_t owner_id = 0;
CollisionObject2D *collision_object = nullptr;
bool disabled = false;
bool one_way_collision = false;
real_t one_way_collision_margin = 1.0;
Vector<Vector<Vector2>> _decompose_in_convex();
void _build_polygon();
void _update_in_shape_owner(bool p_xform_only = false);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#endif
void set_build_mode(BuildMode p_mode);
BuildMode get_build_mode() const;
void set_polygon(const Vector<Point2> &p_polygon);
Vector<Point2> get_polygon() const;
PackedStringArray get_configuration_warnings() const override;
void set_disabled(bool p_disabled);
bool is_disabled() const;
void set_one_way_collision(bool p_enable);
bool is_one_way_collision_enabled() const;
void set_one_way_collision_margin(real_t p_margin);
real_t get_one_way_collision_margin() const;
CollisionPolygon2D();
};
VARIANT_ENUM_CAST(CollisionPolygon2D::BuildMode);
#endif // COLLISION_POLYGON_2D_H

View file

@ -0,0 +1,294 @@
/**************************************************************************/
/* collision_shape_2d.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_2d.h"
#include "scene/2d/physics/area_2d.h"
#include "scene/2d/physics/collision_object_2d.h"
#include "scene/resources/2d/concave_polygon_shape_2d.h"
#include "scene/resources/2d/convex_polygon_shape_2d.h"
void CollisionShape2D::_shape_changed() {
queue_redraw();
}
void CollisionShape2D::_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);
collision_object->shape_owner_set_one_way_collision(owner_id, one_way_collision);
collision_object->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin);
}
Color CollisionShape2D::_get_default_debug_color() const {
SceneTree *st = SceneTree::get_singleton();
return st ? st->get_debug_collisions_color() : Color();
}
void CollisionShape2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PARENTED: {
collision_object = Object::cast_to<CollisionObject2D>(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);
}
} break;
case NOTIFICATION_UNPARENTED: {
if (collision_object) {
collision_object->remove_shape_owner(owner_id);
}
owner_id = 0;
collision_object = nullptr;
} break;
case NOTIFICATION_DRAW: {
ERR_FAIL_COND(!is_inside_tree());
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
break;
}
if (!shape.is_valid()) {
break;
}
rect = Rect2();
Color draw_col = debug_color;
if (disabled) {
float g = draw_col.get_v();
draw_col.r = g;
draw_col.g = g;
draw_col.b = g;
draw_col.a *= 0.5;
}
shape->draw(get_canvas_item(), draw_col);
rect = shape->get_rect();
rect = rect.grow(3);
if (one_way_collision) {
// Draw an arrow indicating the one-way collision direction
draw_col = debug_color.inverted();
if (disabled) {
draw_col = draw_col.darkened(0.25);
}
Vector2 line_to(0, 20);
draw_line(Vector2(), line_to, draw_col, 2);
real_t tsize = 8;
Vector<Vector2> pts{
line_to + Vector2(0, tsize),
line_to + Vector2(Math_SQRT12 * tsize, 0),
line_to + Vector2(-Math_SQRT12 * tsize, 0)
};
Vector<Color> cols{ draw_col, draw_col, draw_col };
draw_primitive(pts, cols, Vector<Vector2>());
}
} break;
}
}
void CollisionShape2D::set_shape(const Ref<Shape2D> &p_shape) {
if (p_shape == shape) {
return;
}
if (shape.is_valid()) {
shape->disconnect_changed(callable_mp(this, &CollisionShape2D::_shape_changed));
}
shape = p_shape;
queue_redraw();
if (collision_object) {
collision_object->shape_owner_clear_shapes(owner_id);
if (shape.is_valid()) {
collision_object->shape_owner_add_shape(owner_id, shape);
}
_update_in_shape_owner();
}
if (shape.is_valid()) {
shape->connect_changed(callable_mp(this, &CollisionShape2D::_shape_changed));
}
update_configuration_warnings();
}
Ref<Shape2D> CollisionShape2D::get_shape() const {
return shape;
}
bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
if (!shape.is_valid()) {
return false;
}
return shape->_edit_is_selected_on_click(p_point, p_tolerance);
}
PackedStringArray CollisionShape2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
CollisionObject2D *col_object = Object::cast_to<CollisionObject2D>(get_parent());
if (col_object == nullptr) {
warnings.push_back(RTR("CollisionShape2D only serves to provide a collision shape to a CollisionObject2D derived node.\nPlease only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape."));
}
if (!shape.is_valid()) {
warnings.push_back(RTR("A shape must be provided for CollisionShape2D to function. Please create a shape resource for it!"));
}
if (one_way_collision && Object::cast_to<Area2D>(col_object)) {
warnings.push_back(RTR("The One Way Collision property will be ignored when the collision object is an Area2D."));
}
Ref<ConvexPolygonShape2D> convex = shape;
Ref<ConcavePolygonShape2D> concave = shape;
if (convex.is_valid() || concave.is_valid()) {
warnings.push_back(RTR("Polygon-based shapes are not meant be used nor edited directly through the CollisionShape2D node. Please use the CollisionPolygon2D node instead."));
}
return warnings;
}
void CollisionShape2D::set_disabled(bool p_disabled) {
disabled = p_disabled;
queue_redraw();
if (collision_object) {
collision_object->shape_owner_set_disabled(owner_id, p_disabled);
}
}
bool CollisionShape2D::is_disabled() const {
return disabled;
}
void CollisionShape2D::set_one_way_collision(bool p_enable) {
one_way_collision = p_enable;
queue_redraw();
if (collision_object) {
collision_object->shape_owner_set_one_way_collision(owner_id, p_enable);
}
update_configuration_warnings();
}
bool CollisionShape2D::is_one_way_collision_enabled() const {
return one_way_collision;
}
void CollisionShape2D::set_one_way_collision_margin(real_t p_margin) {
one_way_collision_margin = p_margin;
if (collision_object) {
collision_object->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin);
}
}
real_t CollisionShape2D::get_one_way_collision_margin() const {
return one_way_collision_margin;
}
void CollisionShape2D::set_debug_color(const Color &p_color) {
debug_color = p_color;
queue_redraw();
}
Color CollisionShape2D::get_debug_color() const {
return debug_color;
}
bool CollisionShape2D::_property_can_revert(const StringName &p_name) const {
if (p_name == "debug_color") {
return true;
}
return false;
}
bool CollisionShape2D::_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 CollisionShape2D::_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 CollisionShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_shape", "shape"), &CollisionShape2D::set_shape);
ClassDB::bind_method(D_METHOD("get_shape"), &CollisionShape2D::get_shape);
ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &CollisionShape2D::set_disabled);
ClassDB::bind_method(D_METHOD("is_disabled"), &CollisionShape2D::is_disabled);
ClassDB::bind_method(D_METHOD("set_one_way_collision", "enabled"), &CollisionShape2D::set_one_way_collision);
ClassDB::bind_method(D_METHOD("is_one_way_collision_enabled"), &CollisionShape2D::is_one_way_collision_enabled);
ClassDB::bind_method(D_METHOD("set_one_way_collision_margin", "margin"), &CollisionShape2D::set_one_way_collision_margin);
ClassDB::bind_method(D_METHOD("get_one_way_collision_margin"), &CollisionShape2D::get_one_way_collision_margin);
ClassDB::bind_method(D_METHOD("set_debug_color", "color"), &CollisionShape2D::set_debug_color);
ClassDB::bind_method(D_METHOD("get_debug_color"), &CollisionShape2D::get_debug_color);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D"), "set_shape", "get_shape");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_way_collision"), "set_one_way_collision", "is_one_way_collision_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "one_way_collision_margin", PROPERTY_HINT_RANGE, "0,128,0.1,suffix:px"), "set_one_way_collision_margin", "get_one_way_collision_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());
}
CollisionShape2D::CollisionShape2D() {
set_notify_local_transform(true);
set_hide_clip_children(true);
debug_color = _get_default_debug_color();
}

View file

@ -0,0 +1,88 @@
/**************************************************************************/
/* collision_shape_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef COLLISION_SHAPE_2D_H
#define COLLISION_SHAPE_2D_H
#include "scene/2d/node_2d.h"
#include "scene/resources/2d/shape_2d.h"
class CollisionObject2D;
class CollisionShape2D : public Node2D {
GDCLASS(CollisionShape2D, Node2D);
Ref<Shape2D> shape;
Rect2 rect = Rect2(-Point2(10, 10), Point2(20, 20));
uint32_t owner_id = 0;
CollisionObject2D *collision_object = nullptr;
bool disabled = false;
bool one_way_collision = false;
real_t one_way_collision_margin = 1.0;
Color debug_color;
void _shape_changed();
void _update_in_shape_owner(bool p_xform_only = false);
Color _get_default_debug_color() const;
protected:
void _notification(int p_what);
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;
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#else
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
#endif // TOOLS_ENABLED
void set_shape(const Ref<Shape2D> &p_shape);
Ref<Shape2D> get_shape() const;
void set_disabled(bool p_disabled);
bool is_disabled() const;
void set_one_way_collision(bool p_enable);
bool is_one_way_collision_enabled() const;
void set_one_way_collision_margin(real_t p_margin);
real_t get_one_way_collision_margin() const;
void set_debug_color(const Color &p_color);
Color get_debug_color() const;
PackedStringArray get_configuration_warnings() const override;
CollisionShape2D();
};
#endif // COLLISION_SHAPE_2D_H

View file

@ -0,0 +1,5 @@
#!/usr/bin/env python
Import("env")
env.add_source_files(env.scene_sources, "*.cpp")

View file

@ -0,0 +1,128 @@
/**************************************************************************/
/* damped_spring_joint_2d.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 "damped_spring_joint_2d.h"
#include "scene/2d/physics/physics_body_2d.h"
void DampedSpringJoint2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
if (!is_inside_tree()) {
break;
}
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
break;
}
draw_line(Point2(-10, 0), Point2(+10, 0), Color(0.7, 0.6, 0.0, 0.5), 3);
draw_line(Point2(-10, length), Point2(+10, length), Color(0.7, 0.6, 0.0, 0.5), 3);
draw_line(Point2(0, 0), Point2(0, length), Color(0.7, 0.6, 0.0, 0.5), 3);
} break;
}
}
void DampedSpringJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) {
Transform2D gt = get_global_transform();
Vector2 anchor_A = gt.get_origin();
Vector2 anchor_B = gt.xform(Vector2(0, length));
PhysicsServer2D::get_singleton()->joint_make_damped_spring(p_joint, anchor_A, anchor_B, body_a->get_rid(), body_b->get_rid());
if (rest_length) {
PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(p_joint, PhysicsServer2D::DAMPED_SPRING_REST_LENGTH, rest_length);
}
PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(p_joint, PhysicsServer2D::DAMPED_SPRING_STIFFNESS, stiffness);
PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(p_joint, PhysicsServer2D::DAMPED_SPRING_DAMPING, damping);
}
void DampedSpringJoint2D::set_length(real_t p_length) {
length = p_length;
queue_redraw();
}
real_t DampedSpringJoint2D::get_length() const {
return length;
}
void DampedSpringJoint2D::set_rest_length(real_t p_rest_length) {
rest_length = p_rest_length;
queue_redraw();
if (is_configured()) {
PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(get_rid(), PhysicsServer2D::DAMPED_SPRING_REST_LENGTH, p_rest_length ? p_rest_length : length);
}
}
real_t DampedSpringJoint2D::get_rest_length() const {
return rest_length;
}
void DampedSpringJoint2D::set_stiffness(real_t p_stiffness) {
stiffness = p_stiffness;
queue_redraw();
if (is_configured()) {
PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(get_rid(), PhysicsServer2D::DAMPED_SPRING_STIFFNESS, p_stiffness);
}
}
real_t DampedSpringJoint2D::get_stiffness() const {
return stiffness;
}
void DampedSpringJoint2D::set_damping(real_t p_damping) {
damping = p_damping;
queue_redraw();
if (is_configured()) {
PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(get_rid(), PhysicsServer2D::DAMPED_SPRING_DAMPING, p_damping);
}
}
real_t DampedSpringJoint2D::get_damping() const {
return damping;
}
void DampedSpringJoint2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_length", "length"), &DampedSpringJoint2D::set_length);
ClassDB::bind_method(D_METHOD("get_length"), &DampedSpringJoint2D::get_length);
ClassDB::bind_method(D_METHOD("set_rest_length", "rest_length"), &DampedSpringJoint2D::set_rest_length);
ClassDB::bind_method(D_METHOD("get_rest_length"), &DampedSpringJoint2D::get_rest_length);
ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &DampedSpringJoint2D::set_stiffness);
ClassDB::bind_method(D_METHOD("get_stiffness"), &DampedSpringJoint2D::get_stiffness);
ClassDB::bind_method(D_METHOD("set_damping", "damping"), &DampedSpringJoint2D::set_damping);
ClassDB::bind_method(D_METHOD("get_damping"), &DampedSpringJoint2D::get_damping);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1,65535,1,exp,suffix:px"), "set_length", "get_length");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rest_length", PROPERTY_HINT_RANGE, "0,65535,1,exp,suffix:px"), "set_rest_length", "get_rest_length");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness", PROPERTY_HINT_RANGE, "0.1,64,0.1,exp"), "set_stiffness", "get_stiffness");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0.01,16,0.01,exp"), "set_damping", "get_damping");
}
DampedSpringJoint2D::DampedSpringJoint2D() {
}

View file

@ -0,0 +1,67 @@
/**************************************************************************/
/* damped_spring_joint_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DAMPED_SPRING_JOINT_2D_H
#define DAMPED_SPRING_JOINT_2D_H
#include "scene/2d/physics/joints/joint_2d.h"
class PhysicsBody2D;
class DampedSpringJoint2D : public Joint2D {
GDCLASS(DampedSpringJoint2D, Joint2D);
real_t stiffness = 20.0;
real_t damping = 1.0;
real_t rest_length = 0.0;
real_t length = 50.0;
protected:
void _notification(int p_what);
virtual void _configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) override;
static void _bind_methods();
public:
void set_length(real_t p_length);
real_t get_length() const;
void set_rest_length(real_t p_rest_length);
real_t get_rest_length() const;
void set_damping(real_t p_damping);
real_t get_damping() const;
void set_stiffness(real_t p_stiffness);
real_t get_stiffness() const;
DampedSpringJoint2D();
};
#endif // DAMPED_SPRING_JOINT_2D_H

View file

@ -0,0 +1,92 @@
/**************************************************************************/
/* groove_joint_2d.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 "groove_joint_2d.h"
#include "scene/2d/physics/physics_body_2d.h"
void GrooveJoint2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
if (!is_inside_tree()) {
break;
}
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
break;
}
draw_line(Point2(-10, 0), Point2(+10, 0), Color(0.7, 0.6, 0.0, 0.5), 3);
draw_line(Point2(-10, length), Point2(+10, length), Color(0.7, 0.6, 0.0, 0.5), 3);
draw_line(Point2(0, 0), Point2(0, length), Color(0.7, 0.6, 0.0, 0.5), 3);
draw_line(Point2(-10, initial_offset), Point2(+10, initial_offset), Color(0.8, 0.8, 0.9, 0.5), 5);
} break;
}
}
void GrooveJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) {
Transform2D gt = get_global_transform();
Vector2 groove_A1 = gt.get_origin();
Vector2 groove_A2 = gt.xform(Vector2(0, length));
Vector2 anchor_B = gt.xform(Vector2(0, initial_offset));
PhysicsServer2D::get_singleton()->joint_make_groove(p_joint, groove_A1, groove_A2, anchor_B, body_a->get_rid(), body_b->get_rid());
}
void GrooveJoint2D::set_length(real_t p_length) {
length = p_length;
queue_redraw();
}
real_t GrooveJoint2D::get_length() const {
return length;
}
void GrooveJoint2D::set_initial_offset(real_t p_initial_offset) {
initial_offset = p_initial_offset;
queue_redraw();
}
real_t GrooveJoint2D::get_initial_offset() const {
return initial_offset;
}
void GrooveJoint2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_length", "length"), &GrooveJoint2D::set_length);
ClassDB::bind_method(D_METHOD("get_length"), &GrooveJoint2D::get_length);
ClassDB::bind_method(D_METHOD("set_initial_offset", "offset"), &GrooveJoint2D::set_initial_offset);
ClassDB::bind_method(D_METHOD("get_initial_offset"), &GrooveJoint2D::get_initial_offset);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1,65535,1,exp,suffix:px"), "set_length", "get_length");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "initial_offset", PROPERTY_HINT_RANGE, "1,65535,1,exp,suffix:px"), "set_initial_offset", "get_initial_offset");
}
GrooveJoint2D::GrooveJoint2D() {
}

View file

@ -0,0 +1,59 @@
/**************************************************************************/
/* groove_joint_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GROOVE_JOINT_2D_H
#define GROOVE_JOINT_2D_H
#include "scene/2d/physics/joints/joint_2d.h"
class PhysicsBody2D;
class GrooveJoint2D : public Joint2D {
GDCLASS(GrooveJoint2D, Joint2D);
real_t length = 50.0;
real_t initial_offset = 25.0;
protected:
void _notification(int p_what);
virtual void _configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) override;
static void _bind_methods();
public:
void set_length(real_t p_length);
real_t get_length() const;
void set_initial_offset(real_t p_initial_offset);
real_t get_initial_offset() const;
GrooveJoint2D();
};
#endif // GROOVE_JOINT_2D_H

View file

@ -0,0 +1,257 @@
/**************************************************************************/
/* joint_2d.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_2d.h"
#include "scene/2d/physics/physics_body_2d.h"
void Joint2D::_disconnect_signals() {
Node *node_a = get_node_or_null(a);
PhysicsBody2D *body_a = Object::cast_to<PhysicsBody2D>(node_a);
if (body_a) {
body_a->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree));
}
Node *node_b = get_node_or_null(b);
PhysicsBody2D *body_b = Object::cast_to<PhysicsBody2D>(node_b);
if (body_b) {
body_b->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree));
}
}
void Joint2D::_body_exit_tree() {
_disconnect_signals();
_update_joint(true);
update_configuration_warnings();
}
void Joint2D::_update_joint(bool p_only_free) {
if (ba.is_valid() && bb.is_valid() && exclude_from_collision) {
PhysicsServer2D::get_singleton()->joint_disable_collisions_between_bodies(joint, false);
}
ba = RID();
bb = RID();
configured = false;
if (p_only_free || !is_inside_tree()) {
PhysicsServer2D::get_singleton()->joint_clear(joint);
warning = String();
return;
}
Node *node_a = get_node_or_null(a);
Node *node_b = get_node_or_null(b);
PhysicsBody2D *body_a = Object::cast_to<PhysicsBody2D>(node_a);
PhysicsBody2D *body_b = Object::cast_to<PhysicsBody2D>(node_b);
bool valid = false;
if (node_a && !body_a && node_b && !body_b) {
warning = RTR("Node A and Node B must be PhysicsBody2Ds");
} else if (node_a && !body_a) {
warning = RTR("Node A must be a PhysicsBody2D");
} else if (node_b && !body_b) {
warning = RTR("Node B must be a PhysicsBody2D");
} else if (!body_a || !body_b) {
warning = RTR("Joint is not connected to two PhysicsBody2Ds");
} else if (body_a == body_b) {
warning = RTR("Node A and Node B must be different PhysicsBody2Ds");
} else {
warning = String();
valid = true;
}
update_configuration_warnings();
if (!valid) {
PhysicsServer2D::get_singleton()->joint_clear(joint);
return;
}
if (body_a) {
body_a->force_update_transform();
}
if (body_b) {
body_b->force_update_transform();
}
configured = true;
_configure_joint(joint, body_a, body_b);
ERR_FAIL_COND_MSG(!joint.is_valid(), "Failed to configure the joint.");
PhysicsServer2D::get_singleton()->joint_set_param(joint, PhysicsServer2D::JOINT_PARAM_BIAS, bias);
ba = body_a->get_rid();
bb = body_b->get_rid();
if (!body_a->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree))) {
body_a->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree));
}
if (!body_b->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree))) {
body_b->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree));
}
PhysicsServer2D::get_singleton()->joint_disable_collisions_between_bodies(joint, exclude_from_collision);
}
void Joint2D::set_node_a(const NodePath &p_node_a) {
if (a == p_node_a) {
return;
}
if (is_configured()) {
_disconnect_signals();
}
a = p_node_a;
if (Engine::get_singleton()->is_editor_hint()) {
// When in editor, the setter may be called as a result of node rename.
// It happens before the node actually changes its name, which triggers false warning.
callable_mp(this, &Joint2D::_update_joint).call_deferred(false);
} else {
_update_joint();
}
}
NodePath Joint2D::get_node_a() const {
return a;
}
void Joint2D::set_node_b(const NodePath &p_node_b) {
if (b == p_node_b) {
return;
}
if (is_configured()) {
_disconnect_signals();
}
b = p_node_b;
if (Engine::get_singleton()->is_editor_hint()) {
callable_mp(this, &Joint2D::_update_joint).call_deferred(false);
} else {
_update_joint();
}
}
NodePath Joint2D::get_node_b() const {
return b;
}
void Joint2D::_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 Joint2D::set_bias(real_t p_bias) {
bias = p_bias;
if (joint.is_valid()) {
PhysicsServer2D::get_singleton()->joint_set_param(joint, PhysicsServer2D::JOINT_PARAM_BIAS, bias);
}
}
real_t Joint2D::get_bias() const {
return bias;
}
void Joint2D::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 Joint2D::get_exclude_nodes_from_collision() const {
return exclude_from_collision;
}
PackedStringArray Joint2D::get_configuration_warnings() const {
PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!warning.is_empty()) {
warnings.push_back(warning);
}
return warnings;
}
void Joint2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_node_a", "node"), &Joint2D::set_node_a);
ClassDB::bind_method(D_METHOD("get_node_a"), &Joint2D::get_node_a);
ClassDB::bind_method(D_METHOD("set_node_b", "node"), &Joint2D::set_node_b);
ClassDB::bind_method(D_METHOD("get_node_b"), &Joint2D::get_node_b);
ClassDB::bind_method(D_METHOD("set_bias", "bias"), &Joint2D::set_bias);
ClassDB::bind_method(D_METHOD("get_bias"), &Joint2D::get_bias);
ClassDB::bind_method(D_METHOD("set_exclude_nodes_from_collision", "enable"), &Joint2D::set_exclude_nodes_from_collision);
ClassDB::bind_method(D_METHOD("get_exclude_nodes_from_collision"), &Joint2D::get_exclude_nodes_from_collision);
ClassDB::bind_method(D_METHOD("get_rid"), &Joint2D::get_rid);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicsBody2D"), "set_node_a", "get_node_a");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicsBody2D"), "set_node_b", "get_node_b");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0,0.9,0.001"), "set_bias", "get_bias");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_collision"), "set_exclude_nodes_from_collision", "get_exclude_nodes_from_collision");
}
Joint2D::Joint2D() {
joint = PhysicsServer2D::get_singleton()->joint_create();
set_hide_clip_children(true);
}
Joint2D::~Joint2D() {
ERR_FAIL_NULL(PhysicsServer2D::get_singleton());
PhysicsServer2D::get_singleton()->free(joint);
}

View file

@ -0,0 +1,84 @@
/**************************************************************************/
/* joint_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef JOINT_2D_H
#define JOINT_2D_H
#include "scene/2d/node_2d.h"
class PhysicsBody2D;
class Joint2D : public Node2D {
GDCLASS(Joint2D, Node2D);
RID joint;
RID ba, bb;
NodePath a;
NodePath b;
real_t bias = 0.0;
bool exclude_from_collision = true;
bool configured = false;
String warning;
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, PhysicsBody2D *body_a, PhysicsBody2D *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_bias(real_t p_bias);
real_t get_bias() const;
void set_exclude_nodes_from_collision(bool p_enable);
bool get_exclude_nodes_from_collision() const;
RID get_rid() const { return joint; }
Joint2D();
~Joint2D();
};
#endif // JOINT_2D_H

View file

@ -0,0 +1,177 @@
/**************************************************************************/
/* pin_joint_2d.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_2d.h"
#include "scene/2d/physics/physics_body_2d.h"
void PinJoint2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
if (!is_inside_tree()) {
break;
}
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
break;
}
draw_line(Point2(-10, 0), Point2(+10, 0), Color(0.7, 0.6, 0.0, 0.5), 3);
draw_line(Point2(0, -10), Point2(0, +10), Color(0.7, 0.6, 0.0, 0.5), 3);
} break;
}
}
void PinJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) {
PhysicsServer2D::get_singleton()->joint_make_pin(p_joint, get_global_position(), body_a->get_rid(), body_b ? body_b->get_rid() : RID());
PhysicsServer2D::get_singleton()->pin_joint_set_param(p_joint, PhysicsServer2D::PIN_JOINT_SOFTNESS, softness);
PhysicsServer2D::get_singleton()->pin_joint_set_param(p_joint, PhysicsServer2D::PIN_JOINT_LIMIT_UPPER, angular_limit_upper);
PhysicsServer2D::get_singleton()->pin_joint_set_param(p_joint, PhysicsServer2D::PIN_JOINT_LIMIT_LOWER, angular_limit_lower);
PhysicsServer2D::get_singleton()->pin_joint_set_param(p_joint, PhysicsServer2D::PIN_JOINT_MOTOR_TARGET_VELOCITY, motor_target_velocity);
PhysicsServer2D::get_singleton()->pin_joint_set_flag(p_joint, PhysicsServer2D::PIN_JOINT_FLAG_MOTOR_ENABLED, motor_enabled);
PhysicsServer2D::get_singleton()->pin_joint_set_flag(p_joint, PhysicsServer2D::PIN_JOINT_FLAG_ANGULAR_LIMIT_ENABLED, angular_limit_enabled);
}
void PinJoint2D::set_softness(real_t p_softness) {
if (softness == p_softness) {
return;
}
softness = p_softness;
queue_redraw();
if (is_configured()) {
PhysicsServer2D::get_singleton()->pin_joint_set_param(get_rid(), PhysicsServer2D::PIN_JOINT_SOFTNESS, p_softness);
}
}
real_t PinJoint2D::get_softness() const {
return softness;
}
void PinJoint2D::set_angular_limit_lower(real_t p_angular_limit_lower) {
if (angular_limit_lower == p_angular_limit_lower) {
return;
}
angular_limit_lower = p_angular_limit_lower;
queue_redraw();
if (is_configured()) {
PhysicsServer2D::get_singleton()->pin_joint_set_param(get_rid(), PhysicsServer2D::PIN_JOINT_LIMIT_LOWER, p_angular_limit_lower);
}
}
real_t PinJoint2D::get_angular_limit_lower() const {
return angular_limit_lower;
}
void PinJoint2D::set_angular_limit_upper(real_t p_angular_limit_upper) {
if (angular_limit_upper == p_angular_limit_upper) {
return;
}
angular_limit_upper = p_angular_limit_upper;
queue_redraw();
if (is_configured()) {
PhysicsServer2D::get_singleton()->pin_joint_set_param(get_rid(), PhysicsServer2D::PIN_JOINT_LIMIT_UPPER, p_angular_limit_upper);
}
}
real_t PinJoint2D::get_angular_limit_upper() const {
return angular_limit_upper;
}
void PinJoint2D::set_motor_target_velocity(real_t p_motor_target_velocity) {
if (motor_target_velocity == p_motor_target_velocity) {
return;
}
motor_target_velocity = p_motor_target_velocity;
queue_redraw();
if (is_configured()) {
PhysicsServer2D::get_singleton()->pin_joint_set_param(get_rid(), PhysicsServer2D::PIN_JOINT_MOTOR_TARGET_VELOCITY, motor_target_velocity);
}
}
real_t PinJoint2D::get_motor_target_velocity() const {
return motor_target_velocity;
}
void PinJoint2D::set_motor_enabled(bool p_motor_enabled) {
if (motor_enabled == p_motor_enabled) {
return;
}
motor_enabled = p_motor_enabled;
queue_redraw();
if (is_configured()) {
PhysicsServer2D::get_singleton()->pin_joint_set_flag(get_rid(), PhysicsServer2D::PIN_JOINT_FLAG_MOTOR_ENABLED, motor_enabled);
}
}
bool PinJoint2D::is_motor_enabled() const {
return motor_enabled;
}
void PinJoint2D::set_angular_limit_enabled(bool p_angular_limit_enabled) {
if (angular_limit_enabled == p_angular_limit_enabled) {
return;
}
angular_limit_enabled = p_angular_limit_enabled;
queue_redraw();
if (is_configured()) {
PhysicsServer2D::get_singleton()->pin_joint_set_flag(get_rid(), PhysicsServer2D::PIN_JOINT_FLAG_ANGULAR_LIMIT_ENABLED, angular_limit_enabled);
}
}
bool PinJoint2D::is_angular_limit_enabled() const {
return angular_limit_enabled;
}
void PinJoint2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_softness", "softness"), &PinJoint2D::set_softness);
ClassDB::bind_method(D_METHOD("get_softness"), &PinJoint2D::get_softness);
ClassDB::bind_method(D_METHOD("set_angular_limit_lower", "angular_limit_lower"), &PinJoint2D::set_angular_limit_lower);
ClassDB::bind_method(D_METHOD("get_angular_limit_lower"), &PinJoint2D::get_angular_limit_lower);
ClassDB::bind_method(D_METHOD("set_angular_limit_upper", "angular_limit_upper"), &PinJoint2D::set_angular_limit_upper);
ClassDB::bind_method(D_METHOD("get_angular_limit_upper"), &PinJoint2D::get_angular_limit_upper);
ClassDB::bind_method(D_METHOD("set_motor_target_velocity", "motor_target_velocity"), &PinJoint2D::set_motor_target_velocity);
ClassDB::bind_method(D_METHOD("get_motor_target_velocity"), &PinJoint2D::get_motor_target_velocity);
ClassDB::bind_method(D_METHOD("set_motor_enabled", "enabled"), &PinJoint2D::set_motor_enabled);
ClassDB::bind_method(D_METHOD("is_motor_enabled"), &PinJoint2D::is_motor_enabled);
ClassDB::bind_method(D_METHOD("set_angular_limit_enabled", "enabled"), &PinJoint2D::set_angular_limit_enabled);
ClassDB::bind_method(D_METHOD("is_angular_limit_enabled"), &PinJoint2D::is_angular_limit_enabled);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "softness", PROPERTY_HINT_RANGE, "0.00,16,0.01,exp"), "set_softness", "get_softness");
ADD_GROUP("Angular Limit", "angular_limit_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "angular_limit_enabled"), "set_angular_limit_enabled", "is_angular_limit_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_limit_lower", PROPERTY_HINT_RANGE, "-180,180,0.1,radians_as_degrees"), "set_angular_limit_lower", "get_angular_limit_lower");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_limit_upper", PROPERTY_HINT_RANGE, "-180,180,0.1,radians_as_degrees"), "set_angular_limit_upper", "get_angular_limit_upper");
ADD_GROUP("Motor", "motor_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "motor_enabled"), "set_motor_enabled", "is_motor_enabled");
ADD_PROPERTY(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_motor_target_velocity", "get_motor_target_velocity");
}
PinJoint2D::PinJoint2D() {
}

View file

@ -0,0 +1,71 @@
/**************************************************************************/
/* pin_joint_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef PIN_JOINT_2D_H
#define PIN_JOINT_2D_H
#include "scene/2d/physics/joints/joint_2d.h"
class PhysicsBody2D;
class PinJoint2D : public Joint2D {
GDCLASS(PinJoint2D, Joint2D);
real_t softness = 0.0;
real_t angular_limit_lower = 0.0;
real_t angular_limit_upper = 0.0;
real_t motor_target_velocity = 0.0;
bool motor_enabled = false;
bool angular_limit_enabled = false;
protected:
void _notification(int p_what);
virtual void _configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) override;
static void _bind_methods();
public:
void set_softness(real_t p_softness);
real_t get_softness() const;
void set_angular_limit_lower(real_t p_angular_limit_lower);
real_t get_angular_limit_lower() const;
void set_angular_limit_upper(real_t p_angular_limit_upper);
real_t get_angular_limit_upper() const;
void set_motor_target_velocity(real_t p_motor_target_velocity);
real_t get_motor_target_velocity() const;
void set_motor_enabled(bool p_motor_enabled);
bool is_motor_enabled() const;
void set_angular_limit_enabled(bool p_angular_limit_enabled);
bool is_angular_limit_enabled() const;
PinJoint2D();
};
#endif // PIN_JOINT_2D_H

View file

@ -0,0 +1,121 @@
/**************************************************************************/
/* kinematic_collision_2d.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_2d.h"
#include "scene/2d/physics/character_body_2d.h"
#include "scene/2d/physics/physics_body_2d.h"
Vector2 KinematicCollision2D::get_position() const {
return result.collision_point;
}
Vector2 KinematicCollision2D::get_normal() const {
return result.collision_normal;
}
Vector2 KinematicCollision2D::get_travel() const {
return result.travel;
}
Vector2 KinematicCollision2D::get_remainder() const {
return result.remainder;
}
real_t KinematicCollision2D::get_angle(const Vector2 &p_up_direction) const {
ERR_FAIL_COND_V(p_up_direction == Vector2(), 0);
return result.get_angle(p_up_direction);
}
real_t KinematicCollision2D::get_depth() const {
return result.collision_depth;
}
Object *KinematicCollision2D::get_local_shape() const {
PhysicsBody2D *owner = Object::cast_to<PhysicsBody2D>(ObjectDB::get_instance(owner_id));
if (!owner) {
return nullptr;
}
uint32_t ownerid = owner->shape_find_owner(result.collision_local_shape);
return owner->shape_owner_get_owner(ownerid);
}
Object *KinematicCollision2D::get_collider() const {
if (result.collider_id.is_valid()) {
return ObjectDB::get_instance(result.collider_id);
}
return nullptr;
}
ObjectID KinematicCollision2D::get_collider_id() const {
return result.collider_id;
}
RID KinematicCollision2D::get_collider_rid() const {
return result.collider;
}
Object *KinematicCollision2D::get_collider_shape() const {
Object *collider = get_collider();
if (collider) {
CollisionObject2D *obj2d = Object::cast_to<CollisionObject2D>(collider);
if (obj2d) {
uint32_t ownerid = obj2d->shape_find_owner(result.collider_shape);
return obj2d->shape_owner_get_owner(ownerid);
}
}
return nullptr;
}
int KinematicCollision2D::get_collider_shape_index() const {
return result.collider_shape;
}
Vector2 KinematicCollision2D::get_collider_velocity() const {
return result.collider_velocity;
}
void KinematicCollision2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_position"), &KinematicCollision2D::get_position);
ClassDB::bind_method(D_METHOD("get_normal"), &KinematicCollision2D::get_normal);
ClassDB::bind_method(D_METHOD("get_travel"), &KinematicCollision2D::get_travel);
ClassDB::bind_method(D_METHOD("get_remainder"), &KinematicCollision2D::get_remainder);
ClassDB::bind_method(D_METHOD("get_angle", "up_direction"), &KinematicCollision2D::get_angle, DEFVAL(Vector2(0.0, -1.0)));
ClassDB::bind_method(D_METHOD("get_depth"), &KinematicCollision2D::get_depth);
ClassDB::bind_method(D_METHOD("get_local_shape"), &KinematicCollision2D::get_local_shape);
ClassDB::bind_method(D_METHOD("get_collider"), &KinematicCollision2D::get_collider);
ClassDB::bind_method(D_METHOD("get_collider_id"), &KinematicCollision2D::get_collider_id);
ClassDB::bind_method(D_METHOD("get_collider_rid"), &KinematicCollision2D::get_collider_rid);
ClassDB::bind_method(D_METHOD("get_collider_shape"), &KinematicCollision2D::get_collider_shape);
ClassDB::bind_method(D_METHOD("get_collider_shape_index"), &KinematicCollision2D::get_collider_shape_index);
ClassDB::bind_method(D_METHOD("get_collider_velocity"), &KinematicCollision2D::get_collider_velocity);
}

View file

@ -0,0 +1,67 @@
/**************************************************************************/
/* kinematic_collision_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef KINEMATIC_COLLISION_2D_H
#define KINEMATIC_COLLISION_2D_H
#include "core/object/ref_counted.h"
#include "servers/physics_server_2d.h"
class CharacterBody2D;
class PhysicsBody2D;
class KinematicCollision2D : public RefCounted {
GDCLASS(KinematicCollision2D, RefCounted);
ObjectID owner_id;
friend class PhysicsBody2D;
friend class CharacterBody2D;
PhysicsServer2D::MotionResult result;
protected:
static void _bind_methods();
public:
Vector2 get_position() const;
Vector2 get_normal() const;
Vector2 get_travel() const;
Vector2 get_remainder() const;
real_t get_angle(const Vector2 &p_up_direction = Vector2(0.0, -1.0)) const;
real_t get_depth() const;
Object *get_local_shape() const;
Object *get_collider() const;
ObjectID get_collider_id() const;
RID get_collider_rid() const;
Object *get_collider_shape() const;
int get_collider_shape_index() const;
Vector2 get_collider_velocity() const;
};
#endif // KINEMATIC_COLLISION_2D_H

View file

@ -0,0 +1,299 @@
/**************************************************************************/
/* physical_bone_2d.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_2d.h"
#include "scene/2d/physics/joints/joint_2d.h"
void PhysicalBone2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
// Position the RigidBody in the correct position.
if (follow_bone_when_simulating) {
_position_at_bone2d();
}
// Keep the child joint in the correct position.
if (child_joint && auto_configure_joint) {
child_joint->set_global_position(get_global_position());
}
} break;
case NOTIFICATION_READY: {
_find_skeleton_parent();
_find_joint_child();
// Configure joint.
if (child_joint && auto_configure_joint) {
_auto_configure_joint();
}
// Simulate physics if set.
if (simulate_physics) {
_start_physics_simulation();
} else {
_stop_physics_simulation();
}
set_physics_process_internal(true);
} break;
}
}
void PhysicalBone2D::_position_at_bone2d() {
// Reset to Bone2D position
if (parent_skeleton) {
Bone2D *bone_to_use = parent_skeleton->get_bone(bone2d_index);
ERR_FAIL_NULL_MSG(bone_to_use, "It's not possible to position the bone with ID: " + itos(bone2d_index) + ".");
set_global_transform(bone_to_use->get_global_transform());
}
}
void PhysicalBone2D::_find_skeleton_parent() {
Node *current_parent = get_parent();
while (current_parent != nullptr) {
Skeleton2D *potential_skeleton = Object::cast_to<Skeleton2D>(current_parent);
if (potential_skeleton) {
parent_skeleton = potential_skeleton;
break;
} else {
PhysicalBone2D *potential_parent_bone = Object::cast_to<PhysicalBone2D>(current_parent);
if (potential_parent_bone) {
current_parent = potential_parent_bone->get_parent();
} else {
current_parent = nullptr;
}
}
}
}
void PhysicalBone2D::_find_joint_child() {
for (int i = 0; i < get_child_count(); i++) {
Node *child_node = get_child(i);
Joint2D *potential_joint = Object::cast_to<Joint2D>(child_node);
if (potential_joint) {
child_joint = potential_joint;
break;
}
}
}
PackedStringArray PhysicalBone2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (!parent_skeleton) {
warnings.push_back(RTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!"));
}
if (parent_skeleton && bone2d_index <= -1) {
warnings.push_back(RTR("A PhysicalBone2D needs to be assigned to a Bone2D node in order to function! Please set a Bone2D node in the inspector."));
}
if (!child_joint) {
PhysicalBone2D *parent_bone = Object::cast_to<PhysicalBone2D>(get_parent());
if (parent_bone) {
warnings.push_back(RTR("A PhysicalBone2D node should have a Joint2D-based child node to keep bones connected! Please add a Joint2D-based node as a child to this node!"));
}
}
return warnings;
}
void PhysicalBone2D::_auto_configure_joint() {
if (!auto_configure_joint) {
return;
}
if (child_joint) {
// Node A = parent | Node B = this node
Node *parent_node = get_parent();
PhysicalBone2D *potential_parent_bone = Object::cast_to<PhysicalBone2D>(parent_node);
if (potential_parent_bone) {
child_joint->set_node_a(child_joint->get_path_to(potential_parent_bone));
child_joint->set_node_b(child_joint->get_path_to(this));
} else {
WARN_PRINT("Cannot setup joint without a parent PhysicalBone2D node.");
}
// Place the child joint at this node's position.
child_joint->set_global_position(get_global_position());
}
}
void PhysicalBone2D::_start_physics_simulation() {
if (_internal_simulate_physics) {
return;
}
// Reset to Bone2D position.
_position_at_bone2d();
// Apply the layers and masks.
PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer());
PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask());
PhysicsServer2D::get_singleton()->body_set_collision_priority(get_rid(), get_collision_priority());
// Apply the correct mode.
_apply_body_mode();
_internal_simulate_physics = true;
set_physics_process_internal(true);
}
void PhysicalBone2D::_stop_physics_simulation() {
if (_internal_simulate_physics) {
_internal_simulate_physics = false;
// Reset to Bone2D position
_position_at_bone2d();
set_physics_process_internal(false);
PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), 0);
PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), 0);
PhysicsServer2D::get_singleton()->body_set_collision_priority(get_rid(), 1.0);
PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_STATIC);
}
}
Joint2D *PhysicalBone2D::get_joint() const {
return child_joint;
}
bool PhysicalBone2D::get_auto_configure_joint() const {
return auto_configure_joint;
}
void PhysicalBone2D::set_auto_configure_joint(bool p_auto_configure) {
auto_configure_joint = p_auto_configure;
_auto_configure_joint();
}
void PhysicalBone2D::set_simulate_physics(bool p_simulate) {
if (p_simulate == simulate_physics) {
return;
}
simulate_physics = p_simulate;
if (simulate_physics) {
_start_physics_simulation();
} else {
_stop_physics_simulation();
}
}
bool PhysicalBone2D::get_simulate_physics() const {
return simulate_physics;
}
bool PhysicalBone2D::is_simulating_physics() const {
return _internal_simulate_physics;
}
void PhysicalBone2D::set_bone2d_nodepath(const NodePath &p_nodepath) {
bone2d_nodepath = p_nodepath;
notify_property_list_changed();
}
NodePath PhysicalBone2D::get_bone2d_nodepath() const {
return bone2d_nodepath;
}
void PhysicalBone2D::set_bone2d_index(int p_bone_idx) {
ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!");
if (!is_inside_tree()) {
bone2d_index = p_bone_idx;
return;
}
if (parent_skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, parent_skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
bone2d_index = p_bone_idx;
bone2d_nodepath = get_path_to(parent_skeleton->get_bone(bone2d_index));
} else {
WARN_PRINT("Cannot verify bone index...");
bone2d_index = p_bone_idx;
}
notify_property_list_changed();
}
int PhysicalBone2D::get_bone2d_index() const {
return bone2d_index;
}
void PhysicalBone2D::set_follow_bone_when_simulating(bool p_follow_bone) {
follow_bone_when_simulating = p_follow_bone;
if (_internal_simulate_physics) {
_stop_physics_simulation();
_start_physics_simulation();
}
}
bool PhysicalBone2D::get_follow_bone_when_simulating() const {
return follow_bone_when_simulating;
}
void PhysicalBone2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_joint"), &PhysicalBone2D::get_joint);
ClassDB::bind_method(D_METHOD("get_auto_configure_joint"), &PhysicalBone2D::get_auto_configure_joint);
ClassDB::bind_method(D_METHOD("set_auto_configure_joint", "auto_configure_joint"), &PhysicalBone2D::set_auto_configure_joint);
ClassDB::bind_method(D_METHOD("set_simulate_physics", "simulate_physics"), &PhysicalBone2D::set_simulate_physics);
ClassDB::bind_method(D_METHOD("get_simulate_physics"), &PhysicalBone2D::get_simulate_physics);
ClassDB::bind_method(D_METHOD("is_simulating_physics"), &PhysicalBone2D::is_simulating_physics);
ClassDB::bind_method(D_METHOD("set_bone2d_nodepath", "nodepath"), &PhysicalBone2D::set_bone2d_nodepath);
ClassDB::bind_method(D_METHOD("get_bone2d_nodepath"), &PhysicalBone2D::get_bone2d_nodepath);
ClassDB::bind_method(D_METHOD("set_bone2d_index", "bone_index"), &PhysicalBone2D::set_bone2d_index);
ClassDB::bind_method(D_METHOD("get_bone2d_index"), &PhysicalBone2D::get_bone2d_index);
ClassDB::bind_method(D_METHOD("set_follow_bone_when_simulating", "follow_bone"), &PhysicalBone2D::set_follow_bone_when_simulating);
ClassDB::bind_method(D_METHOD("get_follow_bone_when_simulating"), &PhysicalBone2D::get_follow_bone_when_simulating);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone2d_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D"), "set_bone2d_nodepath", "get_bone2d_nodepath");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone2d_index", PROPERTY_HINT_RANGE, "-1, 1000, 1"), "set_bone2d_index", "get_bone2d_index");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_configure_joint"), "set_auto_configure_joint", "get_auto_configure_joint");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "simulate_physics"), "set_simulate_physics", "get_simulate_physics");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_bone_when_simulating"), "set_follow_bone_when_simulating", "get_follow_bone_when_simulating");
}
PhysicalBone2D::PhysicalBone2D() {
// Stop the RigidBody from executing its force integration.
PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), 0);
PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), 0);
PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_STATIC);
child_joint = nullptr;
}
PhysicalBone2D::~PhysicalBone2D() {
}

View file

@ -0,0 +1,88 @@
/**************************************************************************/
/* physical_bone_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef PHYSICAL_BONE_2D_H
#define PHYSICAL_BONE_2D_H
#include "scene/2d/physics/rigid_body_2d.h"
#include "scene/2d/skeleton_2d.h"
class Joint2D;
class PhysicalBone2D : public RigidBody2D {
GDCLASS(PhysicalBone2D, RigidBody2D);
protected:
void _notification(int p_what);
static void _bind_methods();
private:
Skeleton2D *parent_skeleton = nullptr;
int bone2d_index = -1;
NodePath bone2d_nodepath;
bool follow_bone_when_simulating = false;
Joint2D *child_joint = nullptr;
bool auto_configure_joint = true;
bool simulate_physics = false;
bool _internal_simulate_physics = false;
void _find_skeleton_parent();
void _find_joint_child();
void _auto_configure_joint();
void _start_physics_simulation();
void _stop_physics_simulation();
void _position_at_bone2d();
public:
Joint2D *get_joint() const;
bool get_auto_configure_joint() const;
void set_auto_configure_joint(bool p_auto_configure);
void set_simulate_physics(bool p_simulate);
bool get_simulate_physics() const;
bool is_simulating_physics() const;
void set_bone2d_nodepath(const NodePath &p_nodepath);
NodePath get_bone2d_nodepath() const;
void set_bone2d_index(int p_bone_idx);
int get_bone2d_index() const;
void set_follow_bone_when_simulating(bool p_follow);
bool get_follow_bone_when_simulating() const;
PackedStringArray get_configuration_warnings() const override;
PhysicalBone2D();
~PhysicalBone2D();
};
#endif // PHYSICAL_BONE_2D_H

View file

@ -0,0 +1,172 @@
/**************************************************************************/
/* physics_body_2d.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 "physics_body_2d.h"
void PhysicsBody2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("move_and_collide", "motion", "test_only", "safe_margin", "recovery_as_collision"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08), DEFVAL(false));
ClassDB::bind_method(D_METHOD("test_move", "from", "motion", "collision", "safe_margin", "recovery_as_collision"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08), DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_gravity"), &PhysicsBody2D::get_gravity);
ClassDB::bind_method(D_METHOD("get_collision_exceptions"), &PhysicsBody2D::get_collision_exceptions);
ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &PhysicsBody2D::add_collision_exception_with);
ClassDB::bind_method(D_METHOD("remove_collision_exception_with", "body"), &PhysicsBody2D::remove_collision_exception_with);
}
PhysicsBody2D::PhysicsBody2D(PhysicsServer2D::BodyMode p_mode) :
CollisionObject2D(PhysicsServer2D::get_singleton()->body_create(), false) {
set_body_mode(p_mode);
set_pickable(false);
}
Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_test_only, real_t p_margin, bool p_recovery_as_collision) {
PhysicsServer2D::MotionParameters parameters(get_global_transform(), p_motion, p_margin);
parameters.recovery_as_collision = p_recovery_as_collision;
PhysicsServer2D::MotionResult result;
if (move_and_collide(parameters, result, p_test_only)) {
// Create a new instance when the cached reference is invalid or still in use in script.
if (motion_cache.is_null() || motion_cache->get_reference_count() > 1) {
motion_cache.instantiate();
motion_cache->owner_id = get_instance_id();
}
motion_cache->result = result;
return motion_cache;
}
return Ref<KinematicCollision2D>();
}
bool PhysicsBody2D::move_and_collide(const PhysicsServer2D::MotionParameters &p_parameters, PhysicsServer2D::MotionResult &r_result, bool p_test_only, bool p_cancel_sliding) {
if (is_only_update_transform_changes_enabled()) {
ERR_PRINT("Move functions do not work together with 'sync to physics' option. See the documentation for details.");
}
bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_parameters, &r_result);
// Restore direction of motion to be along original motion,
// in order to avoid sliding due to recovery,
// but only if collision depth is low enough to avoid tunneling.
if (p_cancel_sliding) {
real_t motion_length = p_parameters.motion.length();
real_t precision = 0.001;
if (colliding) {
// Can't just use margin as a threshold because collision depth is calculated on unsafe motion,
// so even in normal resting cases the depth can be a bit more than the margin.
precision += motion_length * (r_result.collision_unsafe_fraction - r_result.collision_safe_fraction);
if (r_result.collision_depth > p_parameters.margin + precision) {
p_cancel_sliding = false;
}
}
if (p_cancel_sliding) {
// When motion is null, recovery is the resulting motion.
Vector2 motion_normal;
if (motion_length > CMP_EPSILON) {
motion_normal = p_parameters.motion / motion_length;
}
// Check depth of recovery.
real_t projected_length = r_result.travel.dot(motion_normal);
Vector2 recovery = r_result.travel - motion_normal * projected_length;
real_t recovery_length = recovery.length();
// Fixes cases where canceling slide causes the motion to go too deep into the ground,
// because we're only taking rest information into account and not general recovery.
if (recovery_length < p_parameters.margin + precision) {
// Apply adjustment to motion.
r_result.travel = motion_normal * projected_length;
r_result.remainder = p_parameters.motion - r_result.travel;
}
}
}
if (!p_test_only) {
Transform2D gt = p_parameters.from;
gt.columns[2] += r_result.travel;
set_global_transform(gt);
}
return colliding;
}
bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion, const Ref<KinematicCollision2D> &r_collision, real_t p_margin, bool p_recovery_as_collision) {
ERR_FAIL_COND_V(!is_inside_tree(), false);
PhysicsServer2D::MotionResult *r = nullptr;
PhysicsServer2D::MotionResult temp_result;
if (r_collision.is_valid()) {
// Needs const_cast because method bindings don't support non-const Ref.
r = const_cast<PhysicsServer2D::MotionResult *>(&r_collision->result);
} else {
r = &temp_result;
}
PhysicsServer2D::MotionParameters parameters(p_from, p_motion, p_margin);
parameters.recovery_as_collision = p_recovery_as_collision;
return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), parameters, r);
}
Vector2 PhysicsBody2D::get_gravity() const {
PhysicsDirectBodyState2D *state = PhysicsServer2D::get_singleton()->body_get_direct_state(get_rid());
ERR_FAIL_NULL_V(state, Vector2());
return state->get_total_gravity();
}
TypedArray<PhysicsBody2D> PhysicsBody2D::get_collision_exceptions() {
List<RID> exceptions;
PhysicsServer2D::get_singleton()->body_get_collision_exceptions(get_rid(), &exceptions);
Array ret;
for (const RID &body : exceptions) {
ObjectID instance_id = PhysicsServer2D::get_singleton()->body_get_object_instance_id(body);
Object *obj = ObjectDB::get_instance(instance_id);
PhysicsBody2D *physics_body = Object::cast_to<PhysicsBody2D>(obj);
ret.append(physics_body);
}
return ret;
}
void PhysicsBody2D::add_collision_exception_with(Node *p_node) {
ERR_FAIL_NULL(p_node);
PhysicsBody2D *physics_body = Object::cast_to<PhysicsBody2D>(p_node);
ERR_FAIL_NULL_MSG(physics_body, "Collision exception only works between two nodes that inherit from PhysicsBody2D.");
PhysicsServer2D::get_singleton()->body_add_collision_exception(get_rid(), physics_body->get_rid());
}
void PhysicsBody2D::remove_collision_exception_with(Node *p_node) {
ERR_FAIL_NULL(p_node);
PhysicsBody2D *physics_body = Object::cast_to<PhysicsBody2D>(p_node);
ERR_FAIL_NULL_MSG(physics_body, "Collision exception only works between two nodes that inherit from PhysicsBody2D.");
PhysicsServer2D::get_singleton()->body_remove_collision_exception(get_rid(), physics_body->get_rid());
}

View file

@ -0,0 +1,61 @@
/**************************************************************************/
/* physics_body_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef PHYSICS_BODY_2D_H
#define PHYSICS_BODY_2D_H
#include "core/templates/vset.h"
#include "scene/2d/physics/collision_object_2d.h"
#include "scene/2d/physics/kinematic_collision_2d.h"
#include "scene/resources/physics_material.h"
#include "servers/physics_server_2d.h"
class PhysicsBody2D : public CollisionObject2D {
GDCLASS(PhysicsBody2D, CollisionObject2D);
protected:
static void _bind_methods();
PhysicsBody2D(PhysicsServer2D::BodyMode p_mode);
Ref<KinematicCollision2D> motion_cache;
Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_test_only = false, real_t p_margin = 0.08, bool p_recovery_as_collision = false);
public:
bool move_and_collide(const PhysicsServer2D::MotionParameters &p_parameters, PhysicsServer2D::MotionResult &r_result, bool p_test_only = false, bool p_cancel_sliding = true);
bool test_move(const Transform2D &p_from, const Vector2 &p_motion, const Ref<KinematicCollision2D> &r_collision = Ref<KinematicCollision2D>(), real_t p_margin = 0.08, bool p_recovery_as_collision = false);
Vector2 get_gravity() const;
TypedArray<PhysicsBody2D> get_collision_exceptions();
void add_collision_exception_with(Node *p_node); //must be physicsbody
void remove_collision_exception_with(Node *p_node);
};
#endif // PHYSICS_BODY_2D_H

View file

@ -0,0 +1,374 @@
/**************************************************************************/
/* ray_cast_2d.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 "ray_cast_2d.h"
#include "scene/2d/physics/collision_object_2d.h"
#include "scene/resources/world_2d.h"
void RayCast2D::set_target_position(const Vector2 &p_point) {
target_position = p_point;
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_collisions_hint())) {
queue_redraw();
}
}
Vector2 RayCast2D::get_target_position() const {
return target_position;
}
void RayCast2D::set_collision_mask(uint32_t p_mask) {
collision_mask = p_mask;
}
uint32_t RayCast2D::get_collision_mask() const {
return collision_mask;
}
void RayCast2D::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 RayCast2D::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));
}
bool RayCast2D::is_colliding() const {
return collided;
}
Object *RayCast2D::get_collider() const {
if (against.is_null()) {
return nullptr;
}
return ObjectDB::get_instance(against);
}
RID RayCast2D::get_collider_rid() const {
return against_rid;
}
int RayCast2D::get_collider_shape() const {
return against_shape;
}
Vector2 RayCast2D::get_collision_point() const {
return collision_point;
}
Vector2 RayCast2D::get_collision_normal() const {
return collision_normal;
}
void RayCast2D::set_enabled(bool p_enabled) {
enabled = p_enabled;
queue_redraw();
if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) {
set_physics_process_internal(p_enabled);
}
if (!p_enabled) {
collided = false;
}
}
bool RayCast2D::is_enabled() const {
return enabled;
}
void RayCast2D::set_exclude_parent_body(bool p_exclude_parent_body) {
if (exclude_parent_body == p_exclude_parent_body) {
return;
}
exclude_parent_body = p_exclude_parent_body;
if (!is_inside_tree()) {
return;
}
if (Object::cast_to<CollisionObject2D>(get_parent())) {
if (exclude_parent_body) {
exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
} else {
exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
}
}
}
bool RayCast2D::get_exclude_parent_body() const {
return exclude_parent_body;
}
void RayCast2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (enabled && !Engine::get_singleton()->is_editor_hint()) {
set_physics_process_internal(true);
} else {
set_physics_process_internal(false);
}
if (Object::cast_to<CollisionObject2D>(get_parent())) {
if (exclude_parent_body) {
exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
} else {
exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
}
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (enabled) {
set_physics_process_internal(false);
}
} break;
case NOTIFICATION_DRAW: {
ERR_FAIL_COND(!is_inside_tree());
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
break;
}
_draw_debug_shape();
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (!enabled) {
break;
}
_update_raycast_state();
} break;
}
}
void RayCast2D::_update_raycast_state() {
Ref<World2D> w2d = get_world_2d();
ERR_FAIL_COND(w2d.is_null());
PhysicsDirectSpaceState2D *dss = PhysicsServer2D::get_singleton()->space_get_direct_state(w2d->get_space());
ERR_FAIL_NULL(dss);
Transform2D gt = get_global_transform();
Vector2 to = target_position;
if (to == Vector2()) {
to = Vector2(0, 0.01);
}
PhysicsDirectSpaceState2D::RayResult rr;
bool prev_collision_state = collided;
PhysicsDirectSpaceState2D::RayParameters ray_params;
ray_params.from = gt.get_origin();
ray_params.to = gt.xform(to);
ray_params.exclude = exclude;
ray_params.collision_mask = collision_mask;
ray_params.collide_with_bodies = collide_with_bodies;
ray_params.collide_with_areas = collide_with_areas;
ray_params.hit_from_inside = hit_from_inside;
if (dss->intersect_ray(ray_params, rr)) {
collided = true;
against = rr.collider_id;
against_rid = rr.rid;
collision_point = rr.position;
collision_normal = rr.normal;
against_shape = rr.shape;
} else {
collided = false;
against = ObjectID();
against_rid = RID();
against_shape = 0;
}
if (prev_collision_state != collided) {
queue_redraw();
}
}
void RayCast2D::_draw_debug_shape() {
Color draw_col = collided ? Color(1.0, 0.01, 0) : get_tree()->get_debug_collisions_color();
if (!enabled) {
const float g = draw_col.get_v();
draw_col.r = g;
draw_col.g = g;
draw_col.b = g;
}
// Draw an arrow indicating where the RayCast is pointing to
const real_t max_arrow_size = 6;
const real_t line_width = 1.4;
bool no_line = target_position.length() < line_width;
real_t arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size);
if (no_line) {
arrow_size = target_position.length();
} else {
draw_line(Vector2(), target_position - target_position.normalized() * arrow_size, draw_col, line_width);
}
Transform2D xf;
xf.rotate(target_position.angle());
xf.translate_local(Vector2(no_line ? 0 : target_position.length() - arrow_size, 0));
Vector<Vector2> pts = {
xf.xform(Vector2(arrow_size, 0)),
xf.xform(Vector2(0, 0.5 * arrow_size)),
xf.xform(Vector2(0, -0.5 * arrow_size))
};
Vector<Color> cols = { draw_col, draw_col, draw_col };
draw_primitive(pts, cols, Vector<Vector2>());
}
void RayCast2D::force_raycast_update() {
_update_raycast_state();
}
void RayCast2D::add_exception_rid(const RID &p_rid) {
exclude.insert(p_rid);
}
void RayCast2D::add_exception(const CollisionObject2D *p_node) {
ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject2D.");
add_exception_rid(p_node->get_rid());
}
void RayCast2D::remove_exception_rid(const RID &p_rid) {
exclude.erase(p_rid);
}
void RayCast2D::remove_exception(const CollisionObject2D *p_node) {
ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject2D.");
remove_exception_rid(p_node->get_rid());
}
void RayCast2D::clear_exceptions() {
exclude.clear();
if (exclude_parent_body && is_inside_tree()) {
CollisionObject2D *parent = Object::cast_to<CollisionObject2D>(get_parent());
if (parent) {
exclude.insert(parent->get_rid());
}
}
}
void RayCast2D::set_collide_with_areas(bool p_enabled) {
collide_with_areas = p_enabled;
}
bool RayCast2D::is_collide_with_areas_enabled() const {
return collide_with_areas;
}
void RayCast2D::set_collide_with_bodies(bool p_enabled) {
collide_with_bodies = p_enabled;
}
bool RayCast2D::is_collide_with_bodies_enabled() const {
return collide_with_bodies;
}
void RayCast2D::set_hit_from_inside(bool p_enabled) {
hit_from_inside = p_enabled;
}
bool RayCast2D::is_hit_from_inside_enabled() const {
return hit_from_inside;
}
void RayCast2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &RayCast2D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &RayCast2D::is_enabled);
ClassDB::bind_method(D_METHOD("set_target_position", "local_point"), &RayCast2D::set_target_position);
ClassDB::bind_method(D_METHOD("get_target_position"), &RayCast2D::get_target_position);
ClassDB::bind_method(D_METHOD("is_colliding"), &RayCast2D::is_colliding);
ClassDB::bind_method(D_METHOD("force_raycast_update"), &RayCast2D::force_raycast_update);
ClassDB::bind_method(D_METHOD("get_collider"), &RayCast2D::get_collider);
ClassDB::bind_method(D_METHOD("get_collider_rid"), &RayCast2D::get_collider_rid);
ClassDB::bind_method(D_METHOD("get_collider_shape"), &RayCast2D::get_collider_shape);
ClassDB::bind_method(D_METHOD("get_collision_point"), &RayCast2D::get_collision_point);
ClassDB::bind_method(D_METHOD("get_collision_normal"), &RayCast2D::get_collision_normal);
ClassDB::bind_method(D_METHOD("add_exception_rid", "rid"), &RayCast2D::add_exception_rid);
ClassDB::bind_method(D_METHOD("add_exception", "node"), &RayCast2D::add_exception);
ClassDB::bind_method(D_METHOD("remove_exception_rid", "rid"), &RayCast2D::remove_exception_rid);
ClassDB::bind_method(D_METHOD("remove_exception", "node"), &RayCast2D::remove_exception);
ClassDB::bind_method(D_METHOD("clear_exceptions"), &RayCast2D::clear_exceptions);
ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &RayCast2D::set_collision_mask);
ClassDB::bind_method(D_METHOD("get_collision_mask"), &RayCast2D::get_collision_mask);
ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &RayCast2D::set_collision_mask_value);
ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &RayCast2D::get_collision_mask_value);
ClassDB::bind_method(D_METHOD("set_exclude_parent_body", "mask"), &RayCast2D::set_exclude_parent_body);
ClassDB::bind_method(D_METHOD("get_exclude_parent_body"), &RayCast2D::get_exclude_parent_body);
ClassDB::bind_method(D_METHOD("set_collide_with_areas", "enable"), &RayCast2D::set_collide_with_areas);
ClassDB::bind_method(D_METHOD("is_collide_with_areas_enabled"), &RayCast2D::is_collide_with_areas_enabled);
ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &RayCast2D::set_collide_with_bodies);
ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &RayCast2D::is_collide_with_bodies_enabled);
ClassDB::bind_method(D_METHOD("set_hit_from_inside", "enable"), &RayCast2D::set_hit_from_inside);
ClassDB::bind_method(D_METHOD("is_hit_from_inside_enabled"), &RayCast2D::is_hit_from_inside_enabled);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "target_position", PROPERTY_HINT_NONE, "suffix:px"), "set_target_position", "get_target_position");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hit_from_inside"), "set_hit_from_inside", "is_hit_from_inside_enabled");
ADD_GROUP("Collide With", "collide_with");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled");
}
RayCast2D::RayCast2D() {
set_hide_clip_children(true);
}

View file

@ -0,0 +1,109 @@
/**************************************************************************/
/* ray_cast_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef RAY_CAST_2D_H
#define RAY_CAST_2D_H
#include "scene/2d/node_2d.h"
class CollisionObject2D;
class RayCast2D : public Node2D {
GDCLASS(RayCast2D, Node2D);
bool enabled = true;
bool collided = false;
ObjectID against;
RID against_rid;
int against_shape = 0;
Vector2 collision_point;
Vector2 collision_normal;
HashSet<RID> exclude;
uint32_t collision_mask = 1;
bool exclude_parent_body = true;
Vector2 target_position = Vector2(0, 50);
bool collide_with_areas = false;
bool collide_with_bodies = true;
bool hit_from_inside = false;
void _draw_debug_shape();
protected:
void _notification(int p_what);
void _update_raycast_state();
static void _bind_methods();
public:
void set_collide_with_areas(bool p_clip);
bool is_collide_with_areas_enabled() const;
void set_collide_with_bodies(bool p_clip);
bool is_collide_with_bodies_enabled() const;
void set_hit_from_inside(bool p_enable);
bool is_hit_from_inside_enabled() const;
void set_enabled(bool p_enabled);
bool is_enabled() const;
void set_target_position(const Vector2 &p_point);
Vector2 get_target_position() const;
void set_collision_mask(uint32_t p_mask);
uint32_t get_collision_mask() 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_exclude_parent_body(bool p_exclude_parent_body);
bool get_exclude_parent_body() const;
void force_raycast_update();
bool is_colliding() const;
Object *get_collider() const;
RID get_collider_rid() const;
int get_collider_shape() const;
Vector2 get_collision_point() const;
Vector2 get_collision_normal() const;
void add_exception_rid(const RID &p_rid);
void add_exception(const CollisionObject2D *p_node);
void remove_exception_rid(const RID &p_rid);
void remove_exception(const CollisionObject2D *p_node);
void clear_exceptions();
RayCast2D();
};
#endif // RAY_CAST_2D_H

View file

@ -0,0 +1,820 @@
/**************************************************************************/
/* rigid_body_2d.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 "rigid_body_2d.h"
void RigidBody2D::_body_enter_tree(ObjectID p_id) {
Object *obj = ObjectDB::get_instance(p_id);
Node *node = Object::cast_to<Node>(obj);
ERR_FAIL_NULL(node);
ERR_FAIL_NULL(contact_monitor);
HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(p_id);
ERR_FAIL_COND(!E);
ERR_FAIL_COND(E->value.in_scene);
contact_monitor->locked = true;
E->value.in_scene = 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].local_shape);
}
contact_monitor->locked = false;
}
void RigidBody2D::_body_exit_tree(ObjectID p_id) {
Object *obj = ObjectDB::get_instance(p_id);
Node *node = Object::cast_to<Node>(obj);
ERR_FAIL_NULL(node);
ERR_FAIL_NULL(contact_monitor);
HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(p_id);
ERR_FAIL_COND(!E);
ERR_FAIL_COND(!E->value.in_scene);
E->value.in_scene = false;
contact_monitor->locked = true;
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].local_shape);
}
contact_monitor->locked = false;
}
void RigidBody2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape) {
bool body_in = p_status == 1;
ObjectID objid = p_instance;
Object *obj = ObjectDB::get_instance(objid);
Node *node = Object::cast_to<Node>(obj);
ERR_FAIL_NULL(contact_monitor);
HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(objid);
ERR_FAIL_COND(!body_in && !E);
if (body_in) {
if (!E) {
E = contact_monitor->body_map.insert(objid, BodyState());
E->value.rid = p_body;
//E->value.rc=0;
E->value.in_scene = node && node->is_inside_tree();
if (node) {
node->connect(SceneStringName(tree_entered), callable_mp(this, &RigidBody2D::_body_enter_tree).bind(objid));
node->connect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody2D::_body_exit_tree).bind(objid));
if (E->value.in_scene) {
emit_signal(SceneStringName(body_entered), node);
}
}
//E->value.rc++;
}
if (node) {
E->value.shapes.insert(ShapePair(p_body_shape, p_local_shape));
}
if (E->value.in_scene) {
emit_signal(SceneStringName(body_shape_entered), p_body, node, p_body_shape, p_local_shape);
}
} else {
//E->value.rc--;
if (node) {
E->value.shapes.erase(ShapePair(p_body_shape, p_local_shape));
}
bool in_scene = E->value.in_scene;
if (E->value.shapes.is_empty()) {
if (node) {
node->disconnect(SceneStringName(tree_entered), callable_mp(this, &RigidBody2D::_body_enter_tree));
node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody2D::_body_exit_tree));
if (in_scene) {
emit_signal(SceneStringName(body_exited), node);
}
}
contact_monitor->body_map.remove(E);
}
if (node && in_scene) {
emit_signal(SceneStringName(body_shape_exited), p_body, node, p_body_shape, p_local_shape);
}
}
}
struct _RigidBody2DInOut {
RID rid;
ObjectID id;
int shape = 0;
int local_shape = 0;
};
void RigidBody2D::_sync_body_state(PhysicsDirectBodyState2D *p_state) {
if (!freeze || freeze_mode != FREEZE_MODE_KINEMATIC) {
set_block_transform_notify(true);
set_global_transform(p_state->get_transform());
set_block_transform_notify(false);
}
linear_velocity = p_state->get_linear_velocity();
angular_velocity = p_state->get_angular_velocity();
contact_count = p_state->get_contact_count();
if (sleeping != p_state->is_sleeping()) {
sleeping = p_state->is_sleeping();
emit_signal(SceneStringName(sleeping_state_changed));
}
}
void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) {
lock_callback();
if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
_sync_body_state(p_state);
Transform2D old_transform = get_global_transform();
GDVIRTUAL_CALL(_integrate_forces, p_state);
Transform2D new_transform = get_global_transform();
if (new_transform != old_transform) {
// Update the physics server with the new transform, to prevent it from being overwritten at the sync below.
PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform);
}
}
_sync_body_state(p_state);
if (contact_monitor) {
contact_monitor->locked = true;
//untag all
int rc = 0;
for (KeyValue<ObjectID, BodyState> &E : contact_monitor->body_map) {
for (int i = 0; i < E.value.shapes.size(); i++) {
E.value.shapes[i].tagged = false;
rc++;
}
}
_RigidBody2DInOut *toadd = (_RigidBody2DInOut *)alloca(p_state->get_contact_count() * sizeof(_RigidBody2DInOut));
int toadd_count = 0; //state->get_contact_count();
RigidBody2D_RemoveAction *toremove = (RigidBody2D_RemoveAction *)alloca(rc * sizeof(RigidBody2D_RemoveAction));
int toremove_count = 0;
//put the ones to add
for (int i = 0; i < p_state->get_contact_count(); i++) {
RID col_rid = p_state->get_contact_collider(i);
ObjectID col_obj = p_state->get_contact_collider_id(i);
int local_shape = p_state->get_contact_local_shape(i);
int col_shape = p_state->get_contact_collider_shape(i);
HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(col_obj);
if (!E) {
toadd[toadd_count].rid = col_rid;
toadd[toadd_count].local_shape = local_shape;
toadd[toadd_count].id = col_obj;
toadd[toadd_count].shape = col_shape;
toadd_count++;
continue;
}
ShapePair sp(col_shape, local_shape);
int idx = E->value.shapes.find(sp);
if (idx == -1) {
toadd[toadd_count].rid = col_rid;
toadd[toadd_count].local_shape = local_shape;
toadd[toadd_count].id = col_obj;
toadd[toadd_count].shape = col_shape;
toadd_count++;
continue;
}
E->value.shapes[idx].tagged = true;
}
//put the ones to remove
for (const KeyValue<ObjectID, BodyState> &E : contact_monitor->body_map) {
for (int i = 0; i < E.value.shapes.size(); i++) {
if (!E.value.shapes[i].tagged) {
toremove[toremove_count].rid = E.value.rid;
toremove[toremove_count].body_id = E.key;
toremove[toremove_count].pair = E.value.shapes[i];
toremove_count++;
}
}
}
//process removals
for (int i = 0; i < toremove_count; i++) {
_body_inout(0, toremove[i].rid, toremove[i].body_id, toremove[i].pair.body_shape, toremove[i].pair.local_shape);
}
//process additions
for (int i = 0; i < toadd_count; i++) {
_body_inout(1, toadd[i].rid, toadd[i].id, toadd[i].shape, toadd[i].local_shape);
}
contact_monitor->locked = false;
}
unlock_callback();
}
void RigidBody2D::_apply_body_mode() {
if (freeze) {
switch (freeze_mode) {
case FREEZE_MODE_STATIC: {
set_body_mode(PhysicsServer2D::BODY_MODE_STATIC);
} break;
case FREEZE_MODE_KINEMATIC: {
set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC);
} break;
}
} else if (lock_rotation) {
set_body_mode(PhysicsServer2D::BODY_MODE_RIGID_LINEAR);
} else {
set_body_mode(PhysicsServer2D::BODY_MODE_RIGID);
}
}
void RigidBody2D::set_lock_rotation_enabled(bool p_lock_rotation) {
if (p_lock_rotation == lock_rotation) {
return;
}
lock_rotation = p_lock_rotation;
_apply_body_mode();
}
bool RigidBody2D::is_lock_rotation_enabled() const {
return lock_rotation;
}
void RigidBody2D::set_freeze_enabled(bool p_freeze) {
if (p_freeze == freeze) {
return;
}
freeze = p_freeze;
_apply_body_mode();
}
bool RigidBody2D::is_freeze_enabled() const {
return freeze;
}
void RigidBody2D::set_freeze_mode(FreezeMode p_freeze_mode) {
if (p_freeze_mode == freeze_mode) {
return;
}
freeze_mode = p_freeze_mode;
_apply_body_mode();
}
RigidBody2D::FreezeMode RigidBody2D::get_freeze_mode() const {
return freeze_mode;
}
void RigidBody2D::set_mass(real_t p_mass) {
ERR_FAIL_COND(p_mass <= 0);
mass = p_mass;
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_MASS, mass);
}
real_t RigidBody2D::get_mass() const {
return mass;
}
void RigidBody2D::set_inertia(real_t p_inertia) {
ERR_FAIL_COND(p_inertia < 0);
inertia = p_inertia;
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, inertia);
}
real_t RigidBody2D::get_inertia() const {
return inertia;
}
void RigidBody2D::set_center_of_mass_mode(CenterOfMassMode p_mode) {
if (center_of_mass_mode == p_mode) {
return;
}
center_of_mass_mode = p_mode;
switch (center_of_mass_mode) {
case CENTER_OF_MASS_MODE_AUTO: {
center_of_mass = Vector2();
PhysicsServer2D::get_singleton()->body_reset_mass_properties(get_rid());
if (inertia != 0.0) {
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, inertia);
}
} break;
case CENTER_OF_MASS_MODE_CUSTOM: {
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass);
} break;
}
notify_property_list_changed();
}
RigidBody2D::CenterOfMassMode RigidBody2D::get_center_of_mass_mode() const {
return center_of_mass_mode;
}
void RigidBody2D::set_center_of_mass(const Vector2 &p_center_of_mass) {
if (center_of_mass == p_center_of_mass) {
return;
}
ERR_FAIL_COND(center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM);
center_of_mass = p_center_of_mass;
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass);
}
const Vector2 &RigidBody2D::get_center_of_mass() const {
return center_of_mass;
}
void RigidBody2D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) {
if (physics_material_override.is_valid()) {
physics_material_override->disconnect_changed(callable_mp(this, &RigidBody2D::_reload_physics_characteristics));
}
physics_material_override = p_physics_material_override;
if (physics_material_override.is_valid()) {
physics_material_override->connect_changed(callable_mp(this, &RigidBody2D::_reload_physics_characteristics));
}
_reload_physics_characteristics();
}
Ref<PhysicsMaterial> RigidBody2D::get_physics_material_override() const {
return physics_material_override;
}
void RigidBody2D::set_gravity_scale(real_t p_gravity_scale) {
gravity_scale = p_gravity_scale;
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_GRAVITY_SCALE, gravity_scale);
}
real_t RigidBody2D::get_gravity_scale() const {
return gravity_scale;
}
void RigidBody2D::set_linear_damp_mode(DampMode p_mode) {
linear_damp_mode = p_mode;
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_LINEAR_DAMP_MODE, linear_damp_mode);
}
RigidBody2D::DampMode RigidBody2D::get_linear_damp_mode() const {
return linear_damp_mode;
}
void RigidBody2D::set_angular_damp_mode(DampMode p_mode) {
angular_damp_mode = p_mode;
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_ANGULAR_DAMP_MODE, angular_damp_mode);
}
RigidBody2D::DampMode RigidBody2D::get_angular_damp_mode() const {
return angular_damp_mode;
}
void RigidBody2D::set_linear_damp(real_t p_linear_damp) {
ERR_FAIL_COND(p_linear_damp < -1);
linear_damp = p_linear_damp;
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_LINEAR_DAMP, linear_damp);
}
real_t RigidBody2D::get_linear_damp() const {
return linear_damp;
}
void RigidBody2D::set_angular_damp(real_t p_angular_damp) {
ERR_FAIL_COND(p_angular_damp < -1);
angular_damp = p_angular_damp;
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_ANGULAR_DAMP, angular_damp);
}
real_t RigidBody2D::get_angular_damp() const {
return angular_damp;
}
void RigidBody2D::set_axis_velocity(const Vector2 &p_axis) {
Vector2 axis = p_axis.normalized();
linear_velocity -= axis * axis.dot(linear_velocity);
linear_velocity += p_axis;
PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity);
}
void RigidBody2D::set_linear_velocity(const Vector2 &p_velocity) {
linear_velocity = p_velocity;
PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity);
}
Vector2 RigidBody2D::get_linear_velocity() const {
return linear_velocity;
}
void RigidBody2D::set_angular_velocity(real_t p_velocity) {
angular_velocity = p_velocity;
PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity);
}
real_t RigidBody2D::get_angular_velocity() const {
return angular_velocity;
}
void RigidBody2D::set_use_custom_integrator(bool p_enable) {
if (custom_integrator == p_enable) {
return;
}
custom_integrator = p_enable;
PhysicsServer2D::get_singleton()->body_set_omit_force_integration(get_rid(), p_enable);
}
bool RigidBody2D::is_using_custom_integrator() {
return custom_integrator;
}
void RigidBody2D::set_sleeping(bool p_sleeping) {
sleeping = p_sleeping;
PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_SLEEPING, sleeping);
}
void RigidBody2D::set_can_sleep(bool p_active) {
can_sleep = p_active;
PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_CAN_SLEEP, p_active);
}
bool RigidBody2D::is_able_to_sleep() const {
return can_sleep;
}
bool RigidBody2D::is_sleeping() const {
return sleeping;
}
void RigidBody2D::set_max_contacts_reported(int p_amount) {
max_contacts_reported = p_amount;
PhysicsServer2D::get_singleton()->body_set_max_contacts_reported(get_rid(), p_amount);
}
int RigidBody2D::get_max_contacts_reported() const {
return max_contacts_reported;
}
int RigidBody2D::get_contact_count() const {
return contact_count;
}
void RigidBody2D::apply_central_impulse(const Vector2 &p_impulse) {
PhysicsServer2D::get_singleton()->body_apply_central_impulse(get_rid(), p_impulse);
}
void RigidBody2D::apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position) {
PhysicsServer2D::get_singleton()->body_apply_impulse(get_rid(), p_impulse, p_position);
}
void RigidBody2D::apply_torque_impulse(real_t p_torque) {
PhysicsServer2D::get_singleton()->body_apply_torque_impulse(get_rid(), p_torque);
}
void RigidBody2D::apply_central_force(const Vector2 &p_force) {
PhysicsServer2D::get_singleton()->body_apply_central_force(get_rid(), p_force);
}
void RigidBody2D::apply_force(const Vector2 &p_force, const Vector2 &p_position) {
PhysicsServer2D::get_singleton()->body_apply_force(get_rid(), p_force, p_position);
}
void RigidBody2D::apply_torque(real_t p_torque) {
PhysicsServer2D::get_singleton()->body_apply_torque(get_rid(), p_torque);
}
void RigidBody2D::add_constant_central_force(const Vector2 &p_force) {
PhysicsServer2D::get_singleton()->body_add_constant_central_force(get_rid(), p_force);
}
void RigidBody2D::add_constant_force(const Vector2 &p_force, const Vector2 &p_position) {
PhysicsServer2D::get_singleton()->body_add_constant_force(get_rid(), p_force, p_position);
}
void RigidBody2D::add_constant_torque(const real_t p_torque) {
PhysicsServer2D::get_singleton()->body_add_constant_torque(get_rid(), p_torque);
}
void RigidBody2D::set_constant_force(const Vector2 &p_force) {
PhysicsServer2D::get_singleton()->body_set_constant_force(get_rid(), p_force);
}
Vector2 RigidBody2D::get_constant_force() const {
return PhysicsServer2D::get_singleton()->body_get_constant_force(get_rid());
}
void RigidBody2D::set_constant_torque(real_t p_torque) {
PhysicsServer2D::get_singleton()->body_set_constant_torque(get_rid(), p_torque);
}
real_t RigidBody2D::get_constant_torque() const {
return PhysicsServer2D::get_singleton()->body_get_constant_torque(get_rid());
}
void RigidBody2D::set_continuous_collision_detection_mode(CCDMode p_mode) {
ccd_mode = p_mode;
PhysicsServer2D::get_singleton()->body_set_continuous_collision_detection_mode(get_rid(), PhysicsServer2D::CCDMode(p_mode));
}
RigidBody2D::CCDMode RigidBody2D::get_continuous_collision_detection_mode() const {
return ccd_mode;
}
TypedArray<Node2D> RigidBody2D::get_colliding_bodies() const {
ERR_FAIL_NULL_V(contact_monitor, TypedArray<Node2D>());
TypedArray<Node2D> ret;
ret.resize(contact_monitor->body_map.size());
int idx = 0;
for (const KeyValue<ObjectID, BodyState> &E : contact_monitor->body_map) {
Object *obj = ObjectDB::get_instance(E.key);
if (!obj) {
ret.resize(ret.size() - 1); //ops
} else {
ret[idx++] = obj;
}
}
return ret;
}
void RigidBody2D::set_contact_monitor(bool p_enabled) {
if (p_enabled == is_contact_monitor_enabled()) {
return;
}
if (!p_enabled) {
ERR_FAIL_COND_MSG(contact_monitor->locked, "Can't disable contact monitoring during in/out callback. Use call_deferred(\"set_contact_monitor\", false) instead.");
for (const KeyValue<ObjectID, BodyState> &E : contact_monitor->body_map) {
//clean up mess
Object *obj = ObjectDB::get_instance(E.key);
Node *node = Object::cast_to<Node>(obj);
if (node) {
node->disconnect(SceneStringName(tree_entered), callable_mp(this, &RigidBody2D::_body_enter_tree));
node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody2D::_body_exit_tree));
}
}
memdelete(contact_monitor);
contact_monitor = nullptr;
} else {
contact_monitor = memnew(ContactMonitor);
contact_monitor->locked = false;
}
notify_property_list_changed();
}
bool RigidBody2D::is_contact_monitor_enabled() const {
return contact_monitor != nullptr;
}
void RigidBody2D::_notification(int p_what) {
#ifdef TOOLS_ENABLED
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (Engine::get_singleton()->is_editor_hint()) {
set_notify_local_transform(true); // Used for warnings and only in editor.
}
} break;
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
update_configuration_warnings();
} break;
}
#endif
}
PackedStringArray RigidBody2D::get_configuration_warnings() const {
Transform2D t = get_transform();
PackedStringArray warnings = CollisionObject2D::get_configuration_warnings();
if (ABS(t.columns[0].length() - 1.0) > 0.05 || ABS(t.columns[1].length() - 1.0) > 0.05) {
warnings.push_back(RTR("Size changes to RigidBody2D will be overridden by the physics engine when running.\nChange the size in children collision shapes instead."));
}
return warnings;
}
void RigidBody2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mass", "mass"), &RigidBody2D::set_mass);
ClassDB::bind_method(D_METHOD("get_mass"), &RigidBody2D::get_mass);
ClassDB::bind_method(D_METHOD("get_inertia"), &RigidBody2D::get_inertia);
ClassDB::bind_method(D_METHOD("set_inertia", "inertia"), &RigidBody2D::set_inertia);
ClassDB::bind_method(D_METHOD("set_center_of_mass_mode", "mode"), &RigidBody2D::set_center_of_mass_mode);
ClassDB::bind_method(D_METHOD("get_center_of_mass_mode"), &RigidBody2D::get_center_of_mass_mode);
ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &RigidBody2D::set_center_of_mass);
ClassDB::bind_method(D_METHOD("get_center_of_mass"), &RigidBody2D::get_center_of_mass);
ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &RigidBody2D::set_physics_material_override);
ClassDB::bind_method(D_METHOD("get_physics_material_override"), &RigidBody2D::get_physics_material_override);
ClassDB::bind_method(D_METHOD("set_gravity_scale", "gravity_scale"), &RigidBody2D::set_gravity_scale);
ClassDB::bind_method(D_METHOD("get_gravity_scale"), &RigidBody2D::get_gravity_scale);
ClassDB::bind_method(D_METHOD("set_linear_damp_mode", "linear_damp_mode"), &RigidBody2D::set_linear_damp_mode);
ClassDB::bind_method(D_METHOD("get_linear_damp_mode"), &RigidBody2D::get_linear_damp_mode);
ClassDB::bind_method(D_METHOD("set_angular_damp_mode", "angular_damp_mode"), &RigidBody2D::set_angular_damp_mode);
ClassDB::bind_method(D_METHOD("get_angular_damp_mode"), &RigidBody2D::get_angular_damp_mode);
ClassDB::bind_method(D_METHOD("set_linear_damp", "linear_damp"), &RigidBody2D::set_linear_damp);
ClassDB::bind_method(D_METHOD("get_linear_damp"), &RigidBody2D::get_linear_damp);
ClassDB::bind_method(D_METHOD("set_angular_damp", "angular_damp"), &RigidBody2D::set_angular_damp);
ClassDB::bind_method(D_METHOD("get_angular_damp"), &RigidBody2D::get_angular_damp);
ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &RigidBody2D::set_linear_velocity);
ClassDB::bind_method(D_METHOD("get_linear_velocity"), &RigidBody2D::get_linear_velocity);
ClassDB::bind_method(D_METHOD("set_angular_velocity", "angular_velocity"), &RigidBody2D::set_angular_velocity);
ClassDB::bind_method(D_METHOD("get_angular_velocity"), &RigidBody2D::get_angular_velocity);
ClassDB::bind_method(D_METHOD("set_max_contacts_reported", "amount"), &RigidBody2D::set_max_contacts_reported);
ClassDB::bind_method(D_METHOD("get_max_contacts_reported"), &RigidBody2D::get_max_contacts_reported);
ClassDB::bind_method(D_METHOD("get_contact_count"), &RigidBody2D::get_contact_count);
ClassDB::bind_method(D_METHOD("set_use_custom_integrator", "enable"), &RigidBody2D::set_use_custom_integrator);
ClassDB::bind_method(D_METHOD("is_using_custom_integrator"), &RigidBody2D::is_using_custom_integrator);
ClassDB::bind_method(D_METHOD("set_contact_monitor", "enabled"), &RigidBody2D::set_contact_monitor);
ClassDB::bind_method(D_METHOD("is_contact_monitor_enabled"), &RigidBody2D::is_contact_monitor_enabled);
ClassDB::bind_method(D_METHOD("set_continuous_collision_detection_mode", "mode"), &RigidBody2D::set_continuous_collision_detection_mode);
ClassDB::bind_method(D_METHOD("get_continuous_collision_detection_mode"), &RigidBody2D::get_continuous_collision_detection_mode);
ClassDB::bind_method(D_METHOD("set_axis_velocity", "axis_velocity"), &RigidBody2D::set_axis_velocity);
ClassDB::bind_method(D_METHOD("apply_central_impulse", "impulse"), &RigidBody2D::apply_central_impulse, Vector2());
ClassDB::bind_method(D_METHOD("apply_impulse", "impulse", "position"), &RigidBody2D::apply_impulse, Vector2());
ClassDB::bind_method(D_METHOD("apply_torque_impulse", "torque"), &RigidBody2D::apply_torque_impulse);
ClassDB::bind_method(D_METHOD("apply_central_force", "force"), &RigidBody2D::apply_central_force);
ClassDB::bind_method(D_METHOD("apply_force", "force", "position"), &RigidBody2D::apply_force, Vector2());
ClassDB::bind_method(D_METHOD("apply_torque", "torque"), &RigidBody2D::apply_torque);
ClassDB::bind_method(D_METHOD("add_constant_central_force", "force"), &RigidBody2D::add_constant_central_force);
ClassDB::bind_method(D_METHOD("add_constant_force", "force", "position"), &RigidBody2D::add_constant_force, Vector2());
ClassDB::bind_method(D_METHOD("add_constant_torque", "torque"), &RigidBody2D::add_constant_torque);
ClassDB::bind_method(D_METHOD("set_constant_force", "force"), &RigidBody2D::set_constant_force);
ClassDB::bind_method(D_METHOD("get_constant_force"), &RigidBody2D::get_constant_force);
ClassDB::bind_method(D_METHOD("set_constant_torque", "torque"), &RigidBody2D::set_constant_torque);
ClassDB::bind_method(D_METHOD("get_constant_torque"), &RigidBody2D::get_constant_torque);
ClassDB::bind_method(D_METHOD("set_sleeping", "sleeping"), &RigidBody2D::set_sleeping);
ClassDB::bind_method(D_METHOD("is_sleeping"), &RigidBody2D::is_sleeping);
ClassDB::bind_method(D_METHOD("set_can_sleep", "able_to_sleep"), &RigidBody2D::set_can_sleep);
ClassDB::bind_method(D_METHOD("is_able_to_sleep"), &RigidBody2D::is_able_to_sleep);
ClassDB::bind_method(D_METHOD("set_lock_rotation_enabled", "lock_rotation"), &RigidBody2D::set_lock_rotation_enabled);
ClassDB::bind_method(D_METHOD("is_lock_rotation_enabled"), &RigidBody2D::is_lock_rotation_enabled);
ClassDB::bind_method(D_METHOD("set_freeze_enabled", "freeze_mode"), &RigidBody2D::set_freeze_enabled);
ClassDB::bind_method(D_METHOD("is_freeze_enabled"), &RigidBody2D::is_freeze_enabled);
ClassDB::bind_method(D_METHOD("set_freeze_mode", "freeze_mode"), &RigidBody2D::set_freeze_mode);
ClassDB::bind_method(D_METHOD("get_freeze_mode"), &RigidBody2D::get_freeze_mode);
ClassDB::bind_method(D_METHOD("get_colliding_bodies"), &RigidBody2D::get_colliding_bodies);
GDVIRTUAL_BIND(_integrate_forces, "state");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.001,1000,0.001,or_greater,exp,suffix:kg"), "set_mass", "get_mass");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-8,8,0.001,or_less,or_greater"), "set_gravity_scale", "get_gravity_scale");
ADD_GROUP("Mass Distribution", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom"), "set_center_of_mass_mode", "get_center_of_mass_mode");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "center_of_mass", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_less,or_greater,suffix:px"), "set_center_of_mass", "get_center_of_mass");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_RANGE, U"0,1000,0.01,or_greater,exp,suffix:kg\u22C5px\u00B2"), "set_inertia", "get_inertia");
ADD_GROUP("Deactivation", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sleeping"), "set_sleeping", "is_sleeping");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "can_sleep"), "set_can_sleep", "is_able_to_sleep");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "lock_rotation"), "set_lock_rotation_enabled", "is_lock_rotation_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "freeze"), "set_freeze_enabled", "is_freeze_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "freeze_mode", PROPERTY_HINT_ENUM, "Static,Kinematic"), "set_freeze_mode", "get_freeze_mode");
ADD_GROUP("Solver", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator");
ADD_PROPERTY(PropertyInfo(Variant::INT, "continuous_cd", PROPERTY_HINT_ENUM, "Disabled,Cast Ray,Cast Shape"), "set_continuous_collision_detection_mode", "get_continuous_collision_detection_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "contact_monitor"), "set_contact_monitor", "is_contact_monitor_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_contacts_reported", PROPERTY_HINT_RANGE, "0,64,1,or_greater"), "set_max_contacts_reported", "get_max_contacts_reported");
ADD_GROUP("Linear", "linear_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity", PROPERTY_HINT_NONE, "suffix:px/s"), "set_linear_velocity", "get_linear_velocity");
ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_linear_damp_mode", "get_linear_damp_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp");
ADD_GROUP("Angular", "angular_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_NONE, U"radians_as_degrees,suffix:\u00B0/s"), "set_angular_velocity", "get_angular_velocity");
ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_angular_damp_mode", "get_angular_damp_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp");
ADD_GROUP("Constant Forces", "constant_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_force", PROPERTY_HINT_NONE, U"suffix:kg\u22C5px/s\u00B2"), "set_constant_force", "get_constant_force");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_torque", PROPERTY_HINT_NONE, U"suffix:kg\u22C5px\u00B2/s\u00B2/rad"), "set_constant_torque", "get_constant_torque");
ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), 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, "Node"), 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, "Node")));
ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("sleeping_state_changed"));
BIND_ENUM_CONSTANT(FREEZE_MODE_STATIC);
BIND_ENUM_CONSTANT(FREEZE_MODE_KINEMATIC);
BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO);
BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM);
BIND_ENUM_CONSTANT(DAMP_MODE_COMBINE);
BIND_ENUM_CONSTANT(DAMP_MODE_REPLACE);
BIND_ENUM_CONSTANT(CCD_MODE_DISABLED);
BIND_ENUM_CONSTANT(CCD_MODE_CAST_RAY);
BIND_ENUM_CONSTANT(CCD_MODE_CAST_SHAPE);
}
void RigidBody2D::_validate_property(PropertyInfo &p_property) const {
if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM && p_property.name == "center_of_mass") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
if (!contact_monitor && p_property.name == "max_contacts_reported") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
RigidBody2D::RigidBody2D() :
PhysicsBody2D(PhysicsServer2D::BODY_MODE_RIGID) {
PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), callable_mp(this, &RigidBody2D::_body_state_changed));
}
RigidBody2D::~RigidBody2D() {
if (contact_monitor) {
memdelete(contact_monitor);
}
}
void RigidBody2D::_reload_physics_characteristics() {
if (physics_material_override.is_null()) {
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, 0);
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, 1);
} else {
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material_override->computed_bounce());
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, physics_material_override->computed_friction());
}
}

View file

@ -0,0 +1,249 @@
/**************************************************************************/
/* rigid_body_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef RIGID_BODY_2D_H
#define RIGID_BODY_2D_H
#include "scene/2d/physics/static_body_2d.h"
class RigidBody2D : public PhysicsBody2D {
GDCLASS(RigidBody2D, PhysicsBody2D);
public:
enum FreezeMode {
FREEZE_MODE_STATIC,
FREEZE_MODE_KINEMATIC,
};
enum CenterOfMassMode {
CENTER_OF_MASS_MODE_AUTO,
CENTER_OF_MASS_MODE_CUSTOM,
};
enum DampMode {
DAMP_MODE_COMBINE,
DAMP_MODE_REPLACE,
};
enum CCDMode {
CCD_MODE_DISABLED,
CCD_MODE_CAST_RAY,
CCD_MODE_CAST_SHAPE,
};
private:
bool can_sleep = true;
bool lock_rotation = false;
bool freeze = false;
FreezeMode freeze_mode = FREEZE_MODE_STATIC;
real_t mass = 1.0;
real_t inertia = 0.0;
CenterOfMassMode center_of_mass_mode = CENTER_OF_MASS_MODE_AUTO;
Vector2 center_of_mass;
Ref<PhysicsMaterial> physics_material_override;
real_t gravity_scale = 1.0;
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;
Vector2 linear_velocity;
real_t angular_velocity = 0.0;
bool sleeping = false;
int max_contacts_reported = 0;
int contact_count = 0;
bool custom_integrator = false;
CCDMode ccd_mode = CCD_MODE_DISABLED;
struct ShapePair {
int body_shape = 0;
int local_shape = 0;
bool tagged = false;
bool operator<(const ShapePair &p_sp) const {
if (body_shape == p_sp.body_shape) {
return local_shape < p_sp.local_shape;
}
return body_shape < p_sp.body_shape;
}
ShapePair() {}
ShapePair(int p_bs, int p_ls) {
body_shape = p_bs;
local_shape = p_ls;
}
};
struct RigidBody2D_RemoveAction {
RID rid;
ObjectID body_id;
ShapePair pair;
};
struct BodyState {
RID rid;
//int rc;
bool in_scene = false;
VSet<ShapePair> shapes;
};
struct ContactMonitor {
bool locked = false;
HashMap<ObjectID, BodyState> body_map;
};
ContactMonitor *contact_monitor = nullptr;
void _body_enter_tree(ObjectID p_id);
void _body_exit_tree(ObjectID p_id);
void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape);
static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state);
void _body_state_changed(PhysicsDirectBodyState2D *p_state);
void _sync_body_state(PhysicsDirectBodyState2D *p_state);
protected:
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState2D *)
void _apply_body_mode();
public:
void set_lock_rotation_enabled(bool p_lock_rotation);
bool is_lock_rotation_enabled() const;
void set_freeze_enabled(bool p_freeze);
bool is_freeze_enabled() const;
void set_freeze_mode(FreezeMode p_freeze_mode);
FreezeMode get_freeze_mode() const;
void set_mass(real_t p_mass);
real_t get_mass() const;
void set_inertia(real_t p_inertia);
real_t get_inertia() const;
void set_center_of_mass_mode(CenterOfMassMode p_mode);
CenterOfMassMode get_center_of_mass_mode() const;
void set_center_of_mass(const Vector2 &p_center_of_mass);
const Vector2 &get_center_of_mass() const;
void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override);
Ref<PhysicsMaterial> get_physics_material_override() 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_linear_velocity(const Vector2 &p_velocity);
Vector2 get_linear_velocity() const;
void set_axis_velocity(const Vector2 &p_axis);
void set_angular_velocity(real_t p_velocity);
real_t get_angular_velocity() const;
void set_use_custom_integrator(bool p_enable);
bool is_using_custom_integrator();
void set_sleeping(bool p_sleeping);
bool is_sleeping() const;
void set_can_sleep(bool p_active);
bool is_able_to_sleep() const;
void set_contact_monitor(bool p_enabled);
bool is_contact_monitor_enabled() const;
void set_max_contacts_reported(int p_amount);
int get_max_contacts_reported() const;
int get_contact_count() const;
void set_continuous_collision_detection_mode(CCDMode p_mode);
CCDMode get_continuous_collision_detection_mode() const;
void apply_central_impulse(const Vector2 &p_impulse);
void apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2());
void apply_torque_impulse(real_t p_torque);
void apply_central_force(const Vector2 &p_force);
void apply_force(const Vector2 &p_force, const Vector2 &p_position = Vector2());
void apply_torque(real_t p_torque);
void add_constant_central_force(const Vector2 &p_force);
void add_constant_force(const Vector2 &p_force, const Vector2 &p_position = Vector2());
void add_constant_torque(real_t p_torque);
void set_constant_force(const Vector2 &p_force);
Vector2 get_constant_force() const;
void set_constant_torque(real_t p_torque);
real_t get_constant_torque() const;
TypedArray<Node2D> get_colliding_bodies() const; //function for script
virtual PackedStringArray get_configuration_warnings() const override;
RigidBody2D();
~RigidBody2D();
private:
void _reload_physics_characteristics();
};
VARIANT_ENUM_CAST(RigidBody2D::FreezeMode);
VARIANT_ENUM_CAST(RigidBody2D::CenterOfMassMode);
VARIANT_ENUM_CAST(RigidBody2D::DampMode);
VARIANT_ENUM_CAST(RigidBody2D::CCDMode);
#endif // RIGID_BODY_2D_H

View file

@ -0,0 +1,484 @@
/**************************************************************************/
/* shape_cast_2d.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 "shape_cast_2d.h"
#include "core/config/engine.h"
#include "scene/2d/physics/collision_object_2d.h"
#include "scene/2d/physics/physics_body_2d.h"
#include "scene/resources/2d/circle_shape_2d.h"
#include "servers/physics_2d/godot_physics_server_2d.h"
void ShapeCast2D::set_target_position(const Vector2 &p_point) {
target_position = p_point;
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_collisions_hint())) {
queue_redraw();
}
}
Vector2 ShapeCast2D::get_target_position() const {
return target_position;
}
void ShapeCast2D::set_margin(real_t p_margin) {
margin = p_margin;
}
real_t ShapeCast2D::get_margin() const {
return margin;
}
void ShapeCast2D::set_max_results(int p_max_results) {
max_results = p_max_results;
}
int ShapeCast2D::get_max_results() const {
return max_results;
}
void ShapeCast2D::set_collision_mask(uint32_t p_mask) {
collision_mask = p_mask;
}
uint32_t ShapeCast2D::get_collision_mask() const {
return collision_mask;
}
void ShapeCast2D::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 ShapeCast2D::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));
}
int ShapeCast2D::get_collision_count() const {
return result.size();
}
bool ShapeCast2D::is_colliding() const {
return collided;
}
Object *ShapeCast2D::get_collider(int p_idx) const {
ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), nullptr, "No collider found.");
if (result[p_idx].collider_id.is_null()) {
return nullptr;
}
return ObjectDB::get_instance(result[p_idx].collider_id);
}
RID ShapeCast2D::get_collider_rid(int p_idx) const {
ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), RID(), "No collider RID found.");
return result[p_idx].rid;
}
int ShapeCast2D::get_collider_shape(int p_idx) const {
ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), -1, "No collider shape found.");
return result[p_idx].shape;
}
Vector2 ShapeCast2D::get_collision_point(int p_idx) const {
ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), Vector2(), "No collision point found.");
return result[p_idx].point;
}
Vector2 ShapeCast2D::get_collision_normal(int p_idx) const {
ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), Vector2(), "No collision normal found.");
return result[p_idx].normal;
}
real_t ShapeCast2D::get_closest_collision_safe_fraction() const {
return collision_safe_fraction;
}
real_t ShapeCast2D::get_closest_collision_unsafe_fraction() const {
return collision_unsafe_fraction;
}
void ShapeCast2D::set_enabled(bool p_enabled) {
enabled = p_enabled;
queue_redraw();
if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) {
set_physics_process_internal(p_enabled);
}
if (!p_enabled) {
collided = false;
}
}
bool ShapeCast2D::is_enabled() const {
return enabled;
}
void ShapeCast2D::set_shape(const Ref<Shape2D> &p_shape) {
if (p_shape == shape) {
return;
}
if (shape.is_valid()) {
shape->disconnect_changed(callable_mp(this, &ShapeCast2D::_shape_changed));
}
shape = p_shape;
if (shape.is_valid()) {
shape->connect_changed(callable_mp(this, &ShapeCast2D::_shape_changed));
shape_rid = shape->get_rid();
}
update_configuration_warnings();
queue_redraw();
}
Ref<Shape2D> ShapeCast2D::get_shape() const {
return shape;
}
void ShapeCast2D::set_exclude_parent_body(bool p_exclude_parent_body) {
if (exclude_parent_body == p_exclude_parent_body) {
return;
}
exclude_parent_body = p_exclude_parent_body;
if (!is_inside_tree()) {
return;
}
if (Object::cast_to<CollisionObject2D>(get_parent())) {
if (exclude_parent_body) {
exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
} else {
exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
}
}
}
bool ShapeCast2D::get_exclude_parent_body() const {
return exclude_parent_body;
}
void ShapeCast2D::_shape_changed() {
queue_redraw();
}
void ShapeCast2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (enabled && !Engine::get_singleton()->is_editor_hint()) {
set_physics_process_internal(true);
} else {
set_physics_process_internal(false);
}
if (Object::cast_to<CollisionObject2D>(get_parent())) {
if (exclude_parent_body) {
exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
} else {
exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
}
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (enabled) {
set_physics_process_internal(false);
}
} break;
case NOTIFICATION_DRAW: {
#ifdef TOOLS_ENABLED
ERR_FAIL_COND(!is_inside_tree());
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
break;
}
if (shape.is_null()) {
break;
}
Color draw_col = collided ? Color(1.0, 0.01, 0) : get_tree()->get_debug_collisions_color();
if (!enabled) {
float g = draw_col.get_v();
draw_col.r = g;
draw_col.g = g;
draw_col.b = g;
}
// Draw continuous chain of shapes along the cast.
const int steps = MAX(2, target_position.length() / shape->get_rect().get_size().length() * 4);
for (int i = 0; i <= steps; ++i) {
Vector2 t = (real_t(i) / steps) * target_position;
draw_set_transform(t, 0.0, Size2(1, 1));
shape->draw(get_canvas_item(), draw_col);
}
draw_set_transform(Vector2(), 0.0, Size2(1, 1));
// Draw an arrow indicating where the ShapeCast is pointing to.
if (target_position != Vector2()) {
const real_t max_arrow_size = 6;
const real_t line_width = 1.4;
bool no_line = target_position.length() < line_width;
real_t arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size);
if (no_line) {
arrow_size = target_position.length();
} else {
draw_line(Vector2(), target_position - target_position.normalized() * arrow_size, draw_col, line_width);
}
Transform2D xf;
xf.rotate(target_position.angle());
xf.translate_local(Vector2(no_line ? 0 : target_position.length() - arrow_size, 0));
Vector<Vector2> pts = {
xf.xform(Vector2(arrow_size, 0)),
xf.xform(Vector2(0, 0.5 * arrow_size)),
xf.xform(Vector2(0, -0.5 * arrow_size))
};
Vector<Color> cols = { draw_col, draw_col, draw_col };
draw_primitive(pts, cols, Vector<Vector2>());
}
#endif
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (!enabled) {
break;
}
_update_shapecast_state();
} break;
}
}
void ShapeCast2D::_update_shapecast_state() {
result.clear();
ERR_FAIL_COND_MSG(shape.is_null(), "Invalid shape.");
Ref<World2D> w2d = get_world_2d();
ERR_FAIL_COND(w2d.is_null());
PhysicsDirectSpaceState2D *dss = PhysicsServer2D::get_singleton()->space_get_direct_state(w2d->get_space());
ERR_FAIL_NULL(dss);
Transform2D gt = get_global_transform();
PhysicsDirectSpaceState2D::ShapeParameters params;
params.shape_rid = shape_rid;
params.transform = gt;
params.motion = gt.basis_xform(target_position);
params.margin = margin;
params.exclude = exclude;
params.collision_mask = collision_mask;
params.collide_with_bodies = collide_with_bodies;
params.collide_with_areas = collide_with_areas;
collision_safe_fraction = 0.0;
collision_unsafe_fraction = 0.0;
bool prev_collision_state = collided;
if (target_position != Vector2()) {
dss->cast_motion(params, collision_safe_fraction, collision_unsafe_fraction);
if (collision_unsafe_fraction < 1.0) {
// Move shape transform to the point of impact,
// so we can collect contact info at that point.
gt.set_origin(gt.get_origin() + params.motion * (collision_unsafe_fraction + CMP_EPSILON));
params.transform = gt;
}
}
// Regardless of whether the shape is stuck or it's moved along
// the motion vector, we'll only consider static collisions from now on.
params.motion = Vector2();
bool intersected = true;
while (intersected && result.size() < max_results) {
PhysicsDirectSpaceState2D::ShapeRestInfo info;
intersected = dss->rest_info(params, &info);
if (intersected) {
result.push_back(info);
params.exclude.insert(info.rid);
}
}
collided = !result.is_empty();
if (prev_collision_state != collided) {
queue_redraw();
}
}
void ShapeCast2D::force_shapecast_update() {
_update_shapecast_state();
}
void ShapeCast2D::add_exception_rid(const RID &p_rid) {
exclude.insert(p_rid);
}
void ShapeCast2D::add_exception(const CollisionObject2D *p_node) {
ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject2D.");
add_exception_rid(p_node->get_rid());
}
void ShapeCast2D::remove_exception_rid(const RID &p_rid) {
exclude.erase(p_rid);
}
void ShapeCast2D::remove_exception(const CollisionObject2D *p_node) {
ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject2D.");
remove_exception_rid(p_node->get_rid());
}
void ShapeCast2D::clear_exceptions() {
exclude.clear();
}
void ShapeCast2D::set_collide_with_areas(bool p_clip) {
collide_with_areas = p_clip;
}
bool ShapeCast2D::is_collide_with_areas_enabled() const {
return collide_with_areas;
}
void ShapeCast2D::set_collide_with_bodies(bool p_clip) {
collide_with_bodies = p_clip;
}
bool ShapeCast2D::is_collide_with_bodies_enabled() const {
return collide_with_bodies;
}
Array ShapeCast2D::_get_collision_result() const {
Array ret;
for (int i = 0; i < result.size(); ++i) {
const PhysicsDirectSpaceState2D::ShapeRestInfo &sri = result[i];
Dictionary col;
col["point"] = sri.point;
col["normal"] = sri.normal;
col["rid"] = sri.rid;
col["collider"] = ObjectDB::get_instance(sri.collider_id);
col["collider_id"] = sri.collider_id;
col["shape"] = sri.shape;
col["linear_velocity"] = sri.linear_velocity;
ret.push_back(col);
}
return ret;
}
PackedStringArray ShapeCast2D::get_configuration_warnings() const {
PackedStringArray warnings = Node2D::get_configuration_warnings();
if (shape.is_null()) {
warnings.push_back(RTR("This node cannot interact with other objects unless a Shape2D is assigned."));
}
return warnings;
}
void ShapeCast2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &ShapeCast2D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &ShapeCast2D::is_enabled);
ClassDB::bind_method(D_METHOD("set_shape", "shape"), &ShapeCast2D::set_shape);
ClassDB::bind_method(D_METHOD("get_shape"), &ShapeCast2D::get_shape);
ClassDB::bind_method(D_METHOD("set_target_position", "local_point"), &ShapeCast2D::set_target_position);
ClassDB::bind_method(D_METHOD("get_target_position"), &ShapeCast2D::get_target_position);
ClassDB::bind_method(D_METHOD("set_margin", "margin"), &ShapeCast2D::set_margin);
ClassDB::bind_method(D_METHOD("get_margin"), &ShapeCast2D::get_margin);
ClassDB::bind_method(D_METHOD("set_max_results", "max_results"), &ShapeCast2D::set_max_results);
ClassDB::bind_method(D_METHOD("get_max_results"), &ShapeCast2D::get_max_results);
ClassDB::bind_method(D_METHOD("is_colliding"), &ShapeCast2D::is_colliding);
ClassDB::bind_method(D_METHOD("get_collision_count"), &ShapeCast2D::get_collision_count);
ClassDB::bind_method(D_METHOD("force_shapecast_update"), &ShapeCast2D::force_shapecast_update);
ClassDB::bind_method(D_METHOD("get_collider", "index"), &ShapeCast2D::get_collider);
ClassDB::bind_method(D_METHOD("get_collider_rid", "index"), &ShapeCast2D::get_collider_rid);
ClassDB::bind_method(D_METHOD("get_collider_shape", "index"), &ShapeCast2D::get_collider_shape);
ClassDB::bind_method(D_METHOD("get_collision_point", "index"), &ShapeCast2D::get_collision_point);
ClassDB::bind_method(D_METHOD("get_collision_normal", "index"), &ShapeCast2D::get_collision_normal);
ClassDB::bind_method(D_METHOD("get_closest_collision_safe_fraction"), &ShapeCast2D::get_closest_collision_safe_fraction);
ClassDB::bind_method(D_METHOD("get_closest_collision_unsafe_fraction"), &ShapeCast2D::get_closest_collision_unsafe_fraction);
ClassDB::bind_method(D_METHOD("add_exception_rid", "rid"), &ShapeCast2D::add_exception_rid);
ClassDB::bind_method(D_METHOD("add_exception", "node"), &ShapeCast2D::add_exception);
ClassDB::bind_method(D_METHOD("remove_exception_rid", "rid"), &ShapeCast2D::remove_exception_rid);
ClassDB::bind_method(D_METHOD("remove_exception", "node"), &ShapeCast2D::remove_exception);
ClassDB::bind_method(D_METHOD("clear_exceptions"), &ShapeCast2D::clear_exceptions);
ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &ShapeCast2D::set_collision_mask);
ClassDB::bind_method(D_METHOD("get_collision_mask"), &ShapeCast2D::get_collision_mask);
ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &ShapeCast2D::set_collision_mask_value);
ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &ShapeCast2D::get_collision_mask_value);
ClassDB::bind_method(D_METHOD("set_exclude_parent_body", "mask"), &ShapeCast2D::set_exclude_parent_body);
ClassDB::bind_method(D_METHOD("get_exclude_parent_body"), &ShapeCast2D::get_exclude_parent_body);
ClassDB::bind_method(D_METHOD("set_collide_with_areas", "enable"), &ShapeCast2D::set_collide_with_areas);
ClassDB::bind_method(D_METHOD("is_collide_with_areas_enabled"), &ShapeCast2D::is_collide_with_areas_enabled);
ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &ShapeCast2D::set_collide_with_bodies);
ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &ShapeCast2D::is_collide_with_bodies_enabled);
ClassDB::bind_method(D_METHOD("_get_collision_result"), &ShapeCast2D::_get_collision_result);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D"), "set_shape", "get_shape");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "target_position", PROPERTY_HINT_NONE, "suffix:px"), "set_target_position", "get_target_position");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01,suffix:px"), "set_margin", "get_margin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_results"), "set_max_results", "get_max_results");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "_get_collision_result");
ADD_GROUP("Collide With", "collide_with");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled");
}
ShapeCast2D::ShapeCast2D() {
set_hide_clip_children(true);
}

View file

@ -0,0 +1,126 @@
/**************************************************************************/
/* shape_cast_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SHAPE_CAST_2D_H
#define SHAPE_CAST_2D_H
#include "scene/2d/node_2d.h"
#include "scene/resources/2d/shape_2d.h"
#include "scene/resources/world_2d.h"
class CollisionObject2D;
class ShapeCast2D : public Node2D {
GDCLASS(ShapeCast2D, Node2D);
bool enabled = true;
Ref<Shape2D> shape;
RID shape_rid;
Vector2 target_position = Vector2(0, 50);
HashSet<RID> exclude;
real_t margin = 0.0;
uint32_t collision_mask = 1;
bool exclude_parent_body = true;
bool collide_with_areas = false;
bool collide_with_bodies = true;
// Result
int max_results = 32;
Vector<PhysicsDirectSpaceState2D::ShapeRestInfo> result;
bool collided = false;
real_t collision_safe_fraction = 1.0;
real_t collision_unsafe_fraction = 1.0;
Array _get_collision_result() const;
void _shape_changed();
protected:
void _notification(int p_what);
void _update_shapecast_state();
static void _bind_methods();
public:
void set_collide_with_areas(bool p_clip);
bool is_collide_with_areas_enabled() const;
void set_collide_with_bodies(bool p_clip);
bool is_collide_with_bodies_enabled() const;
void set_enabled(bool p_enabled);
bool is_enabled() const;
void set_shape(const Ref<Shape2D> &p_shape);
Ref<Shape2D> get_shape() const;
void set_target_position(const Vector2 &p_point);
Vector2 get_target_position() const;
void set_margin(real_t p_margin);
real_t get_margin() const;
void set_max_results(int p_max_results);
int get_max_results() const;
void set_collision_mask(uint32_t p_mask);
uint32_t get_collision_mask() 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_exclude_parent_body(bool p_exclude_parent_body);
bool get_exclude_parent_body() const;
void force_shapecast_update();
bool is_colliding() const;
int get_collision_count() const;
Object *get_collider(int p_idx) const;
RID get_collider_rid(int p_idx) const;
int get_collider_shape(int p_idx) const;
Vector2 get_collision_point(int p_idx) const;
Vector2 get_collision_normal(int p_idx) const;
real_t get_closest_collision_safe_fraction() const;
real_t get_closest_collision_unsafe_fraction() const;
void add_exception_rid(const RID &p_rid);
void add_exception(const CollisionObject2D *p_node);
void remove_exception_rid(const RID &p_rid);
void remove_exception(const CollisionObject2D *p_node);
void clear_exceptions();
PackedStringArray get_configuration_warnings() const override;
ShapeCast2D();
};
#endif // SHAPE_CAST_2D_H

View file

@ -0,0 +1,96 @@
/**************************************************************************/
/* static_body_2d.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 "static_body_2d.h"
void StaticBody2D::set_constant_linear_velocity(const Vector2 &p_vel) {
constant_linear_velocity = p_vel;
PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity);
}
void StaticBody2D::set_constant_angular_velocity(real_t p_vel) {
constant_angular_velocity = p_vel;
PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity);
}
Vector2 StaticBody2D::get_constant_linear_velocity() const {
return constant_linear_velocity;
}
real_t StaticBody2D::get_constant_angular_velocity() const {
return constant_angular_velocity;
}
void StaticBody2D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) {
if (physics_material_override.is_valid()) {
physics_material_override->disconnect_changed(callable_mp(this, &StaticBody2D::_reload_physics_characteristics));
}
physics_material_override = p_physics_material_override;
if (physics_material_override.is_valid()) {
physics_material_override->connect_changed(callable_mp(this, &StaticBody2D::_reload_physics_characteristics));
}
_reload_physics_characteristics();
}
Ref<PhysicsMaterial> StaticBody2D::get_physics_material_override() const {
return physics_material_override;
}
void StaticBody2D::_reload_physics_characteristics() {
if (physics_material_override.is_null()) {
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, 0);
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, 1);
} else {
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material_override->computed_bounce());
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, physics_material_override->computed_friction());
}
}
void StaticBody2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody2D::set_constant_linear_velocity);
ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody2D::set_constant_angular_velocity);
ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody2D::get_constant_linear_velocity);
ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody2D::get_constant_angular_velocity);
ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody2D::set_physics_material_override);
ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody2D::get_physics_material_override);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_linear_velocity", PROPERTY_HINT_NONE, "suffix:px/s"), "set_constant_linear_velocity", "get_constant_linear_velocity");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_angular_velocity", PROPERTY_HINT_NONE, U"radians_as_degrees,suffix:\u00B0/s"), "set_constant_angular_velocity", "get_constant_angular_velocity");
}
StaticBody2D::StaticBody2D(PhysicsServer2D::BodyMode p_mode) :
PhysicsBody2D(p_mode) {
}

View file

@ -0,0 +1,64 @@
/**************************************************************************/
/* static_body_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef STATIC_BODY_2D_H
#define STATIC_BODY_2D_H
#include "scene/2d/physics/physics_body_2d.h"
class StaticBody2D : public PhysicsBody2D {
GDCLASS(StaticBody2D, PhysicsBody2D);
private:
Vector2 constant_linear_velocity;
real_t constant_angular_velocity = 0.0;
Ref<PhysicsMaterial> physics_material_override;
protected:
static void _bind_methods();
public:
void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override);
Ref<PhysicsMaterial> get_physics_material_override() const;
void set_constant_linear_velocity(const Vector2 &p_vel);
void set_constant_angular_velocity(real_t p_vel);
Vector2 get_constant_linear_velocity() const;
real_t get_constant_angular_velocity() const;
StaticBody2D(PhysicsServer2D::BodyMode p_mode = PhysicsServer2D::BODY_MODE_STATIC);
private:
void _reload_physics_characteristics();
};
#endif // STATIC_BODY_2D_H

View file

@ -0,0 +1,698 @@
/**************************************************************************/
/* polygon_2d.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 "polygon_2d.h"
#include "core/math/geometry_2d.h"
#include "skeleton_2d.h"
#ifdef TOOLS_ENABLED
Dictionary Polygon2D::_edit_get_state() const {
Dictionary state = Node2D::_edit_get_state();
state["offset"] = offset;
return state;
}
void Polygon2D::_edit_set_state(const Dictionary &p_state) {
Node2D::_edit_set_state(p_state);
set_offset(p_state["offset"]);
}
void Polygon2D::_edit_set_pivot(const Point2 &p_pivot) {
set_position(get_transform().xform(p_pivot));
set_offset(get_offset() - p_pivot);
}
Point2 Polygon2D::_edit_get_pivot() const {
return Vector2();
}
bool Polygon2D::_edit_use_pivot() const {
return true;
}
Rect2 Polygon2D::_edit_get_rect() const {
if (rect_cache_dirty) {
int l = polygon.size();
const Vector2 *r = polygon.ptr();
item_rect = Rect2();
for (int i = 0; i < l; i++) {
Vector2 pos = r[i] + offset;
if (i == 0) {
item_rect.position = pos;
} else {
item_rect.expand_to(pos);
}
}
rect_cache_dirty = false;
}
return item_rect;
}
bool Polygon2D::_edit_use_rect() const {
return polygon.size() > 0;
}
bool Polygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
Vector<Vector2> polygon2d = Variant(polygon);
if (internal_vertices > 0) {
polygon2d.resize(polygon2d.size() - internal_vertices);
}
return Geometry2D::is_point_in_polygon(p_point - get_offset(), polygon2d);
}
#endif
void Polygon2D::_validate_property(PropertyInfo &p_property) const {
if (!invert && p_property.name == "invert_border") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
void Polygon2D::_skeleton_bone_setup_changed() {
queue_redraw();
}
void Polygon2D::_notification(int p_what) {
if (p_what == NOTIFICATION_TRANSFORM_CHANGED && !Engine::get_singleton()->is_editor_hint()) {
return; // Mesh recreation for NOTIFICATION_TRANSFORM_CHANGED is only needed in editor.
}
switch (p_what) {
case NOTIFICATION_TRANSFORM_CHANGED:
case NOTIFICATION_DRAW: {
if (polygon.size() < 3) {
return;
}
Skeleton2D *skeleton_node = nullptr;
if (has_node(skeleton)) {
skeleton_node = Object::cast_to<Skeleton2D>(get_node(skeleton));
}
ObjectID new_skeleton_id;
if (skeleton_node && !invert && bone_weights.size()) {
RS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), skeleton_node->get_skeleton());
new_skeleton_id = skeleton_node->get_instance_id();
} else {
RS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), RID());
}
if (new_skeleton_id != current_skeleton_id) {
Object *old_skeleton = ObjectDB::get_instance(current_skeleton_id);
if (old_skeleton) {
old_skeleton->disconnect("bone_setup_changed", callable_mp(this, &Polygon2D::_skeleton_bone_setup_changed));
}
if (skeleton_node) {
skeleton_node->connect("bone_setup_changed", callable_mp(this, &Polygon2D::_skeleton_bone_setup_changed));
}
current_skeleton_id = new_skeleton_id;
}
Vector<Vector2> points;
Vector<Vector2> uvs;
Vector<int> bones;
Vector<float> weights;
int len = polygon.size();
if ((invert || polygons.size() == 0) && internal_vertices > 0) {
//if no polygons are around, internal vertices must not be drawn, else let them be
len -= internal_vertices;
}
if (len <= 0) {
return;
}
points.resize(len);
{
const Vector2 *polyr = polygon.ptr();
for (int i = 0; i < len; i++) {
points.write[i] = polyr[i] + offset;
}
}
if (invert) {
Rect2 bounds;
int highest_idx = -1;
real_t highest_y = -1e20;
real_t sum = 0.0;
for (int i = 0; i < len; i++) {
if (i == 0) {
bounds.position = points[i];
} else {
bounds.expand_to(points[i]);
}
if (points[i].y > highest_y) {
highest_idx = i;
highest_y = points[i].y;
}
int ni = (i + 1) % len;
sum += (points[ni].x - points[i].x) * (points[ni].y + points[i].y);
}
bounds = bounds.grow(invert_border);
Vector2 ep[7] = {
Vector2(points[highest_idx].x, points[highest_idx].y + invert_border),
Vector2(bounds.position + bounds.size),
Vector2(bounds.position + Vector2(bounds.size.x, 0)),
Vector2(bounds.position),
Vector2(bounds.position + Vector2(0, bounds.size.y)),
Vector2(points[highest_idx].x - CMP_EPSILON, points[highest_idx].y + invert_border),
Vector2(points[highest_idx].x - CMP_EPSILON, points[highest_idx].y),
};
if (sum > 0) {
SWAP(ep[1], ep[4]);
SWAP(ep[2], ep[3]);
SWAP(ep[5], ep[0]);
SWAP(ep[6], points.write[highest_idx]);
}
points.resize(points.size() + 7);
for (int i = points.size() - 1; i >= highest_idx + 7; i--) {
points.write[i] = points[i - 7];
}
for (int i = 0; i < 7; i++) {
points.write[highest_idx + i + 1] = ep[i];
}
len = points.size();
}
if (texture.is_valid()) {
Transform2D texmat(tex_rot, tex_ofs);
texmat.scale(tex_scale);
Size2 tex_size = texture->get_size();
uvs.resize(len);
if (points.size() == uv.size()) {
const Vector2 *uvr = uv.ptr();
for (int i = 0; i < len; i++) {
uvs.write[i] = texmat.xform(uvr[i]) / tex_size;
}
} else {
for (int i = 0; i < len; i++) {
uvs.write[i] = texmat.xform(points[i]) / tex_size;
}
}
}
if (skeleton_node && !invert && bone_weights.size()) {
//a skeleton is set! fill indices and weights
int vc = len;
bones.resize(vc * 4);
weights.resize(vc * 4);
int *bonesw = bones.ptrw();
float *weightsw = weights.ptrw();
for (int i = 0; i < vc * 4; i++) {
bonesw[i] = 0;
weightsw[i] = 0;
}
for (int i = 0; i < bone_weights.size(); i++) {
if (bone_weights[i].weights.size() != points.size()) {
continue; //different number of vertices, sorry not using.
}
if (!skeleton_node->has_node(bone_weights[i].path)) {
continue; //node does not exist
}
Bone2D *bone = Object::cast_to<Bone2D>(skeleton_node->get_node(bone_weights[i].path));
if (!bone) {
continue;
}
int bone_index = bone->get_index_in_skeleton();
const float *r = bone_weights[i].weights.ptr();
for (int j = 0; j < vc; j++) {
if (r[j] == 0.0) {
continue; //weight is unpainted, skip
}
//find an index with a weight
for (int k = 0; k < 4; k++) {
if (weightsw[j * 4 + k] < r[j]) {
//this is less than this weight, insert weight!
for (int l = 3; l > k; l--) {
weightsw[j * 4 + l] = weightsw[j * 4 + l - 1];
bonesw[j * 4 + l] = bonesw[j * 4 + l - 1];
}
weightsw[j * 4 + k] = r[j];
bonesw[j * 4 + k] = bone_index;
break;
}
}
}
}
//normalize the weights
for (int i = 0; i < vc; i++) {
real_t tw = 0.0;
for (int j = 0; j < 4; j++) {
tw += weightsw[i * 4 + j];
}
if (tw == 0) {
continue; //unpainted, do nothing
}
//normalize
for (int j = 0; j < 4; j++) {
weightsw[i * 4 + j] /= tw;
}
}
}
Vector<Color> colors;
colors.resize(len);
if (vertex_colors.size() == points.size()) {
const Color *color_r = vertex_colors.ptr();
for (int i = 0; i < len; i++) {
colors.write[i] = color_r[i];
}
} else {
for (int i = 0; i < len; i++) {
colors.write[i] = color;
}
}
Vector<int> index_array;
if (invert || polygons.size() == 0) {
index_array = Geometry2D::triangulate_polygon(points);
} else {
//draw individual polygons
for (int i = 0; i < polygons.size(); i++) {
Vector<int> src_indices = polygons[i];
int ic = src_indices.size();
if (ic < 3) {
continue;
}
const int *r = src_indices.ptr();
Vector<Vector2> tmp_points;
tmp_points.resize(ic);
for (int j = 0; j < ic; j++) {
int idx = r[j];
ERR_CONTINUE(idx < 0 || idx >= points.size());
tmp_points.write[j] = points[r[j]];
}
Vector<int> indices = Geometry2D::triangulate_polygon(tmp_points);
int ic2 = indices.size();
const int *r2 = indices.ptr();
int bic = index_array.size();
index_array.resize(bic + ic2);
int *w2 = index_array.ptrw();
for (int j = 0; j < ic2; j++) {
w2[j + bic] = r[r2[j]];
}
}
}
RS::get_singleton()->mesh_clear(mesh);
if (index_array.size()) {
Array arr;
arr.resize(RS::ARRAY_MAX);
arr[RS::ARRAY_VERTEX] = points;
if (uvs.size() == points.size()) {
arr[RS::ARRAY_TEX_UV] = uvs;
}
if (colors.size() == points.size()) {
arr[RS::ARRAY_COLOR] = colors;
}
if (bones.size() == points.size() * 4) {
arr[RS::ARRAY_BONES] = bones;
arr[RS::ARRAY_WEIGHTS] = weights;
}
arr[RS::ARRAY_INDEX] = index_array;
RS::SurfaceData sd;
if (skeleton_node) {
// Compute transform between mesh and skeleton for runtime AABB compute.
const Transform2D mesh_transform = get_global_transform();
const Transform2D skeleton_transform = skeleton_node->get_global_transform();
const Transform2D mesh_to_sk2d = skeleton_transform.affine_inverse() * mesh_transform;
// Convert 2d transform to 3d.
sd.mesh_to_skeleton_xform.basis.rows[0][0] = mesh_to_sk2d.columns[0][0];
sd.mesh_to_skeleton_xform.basis.rows[1][0] = mesh_to_sk2d.columns[0][1];
sd.mesh_to_skeleton_xform.origin.x = mesh_to_sk2d.get_origin().x;
sd.mesh_to_skeleton_xform.basis.rows[0][1] = mesh_to_sk2d.columns[1][0];
sd.mesh_to_skeleton_xform.basis.rows[1][1] = mesh_to_sk2d.columns[1][1];
sd.mesh_to_skeleton_xform.origin.y = mesh_to_sk2d.get_origin().y;
}
Error err = RS::get_singleton()->mesh_create_surface_data_from_arrays(&sd, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
if (err != OK) {
return;
}
RS::get_singleton()->mesh_add_surface(mesh, sd);
RS::get_singleton()->canvas_item_add_mesh(get_canvas_item(), mesh, Transform2D(), Color(1, 1, 1), texture.is_valid() ? texture->get_rid() : RID());
}
} break;
}
}
void Polygon2D::set_polygon(const Vector<Vector2> &p_polygon) {
polygon = p_polygon;
rect_cache_dirty = true;
queue_redraw();
}
Vector<Vector2> Polygon2D::get_polygon() const {
return polygon;
}
void Polygon2D::set_internal_vertex_count(int p_count) {
internal_vertices = p_count;
}
int Polygon2D::get_internal_vertex_count() const {
return internal_vertices;
}
void Polygon2D::set_uv(const Vector<Vector2> &p_uv) {
uv = p_uv;
queue_redraw();
}
Vector<Vector2> Polygon2D::get_uv() const {
return uv;
}
void Polygon2D::set_polygons(const Array &p_polygons) {
polygons = p_polygons;
queue_redraw();
}
Array Polygon2D::get_polygons() const {
return polygons;
}
void Polygon2D::set_color(const Color &p_color) {
color = p_color;
queue_redraw();
}
Color Polygon2D::get_color() const {
return color;
}
void Polygon2D::set_vertex_colors(const Vector<Color> &p_colors) {
vertex_colors = p_colors;
queue_redraw();
}
Vector<Color> Polygon2D::get_vertex_colors() const {
return vertex_colors;
}
void Polygon2D::set_texture(const Ref<Texture2D> &p_texture) {
texture = p_texture;
queue_redraw();
}
Ref<Texture2D> Polygon2D::get_texture() const {
return texture;
}
void Polygon2D::set_texture_offset(const Vector2 &p_offset) {
tex_ofs = p_offset;
queue_redraw();
}
Vector2 Polygon2D::get_texture_offset() const {
return tex_ofs;
}
void Polygon2D::set_texture_rotation(real_t p_rot) {
tex_rot = p_rot;
queue_redraw();
}
real_t Polygon2D::get_texture_rotation() const {
return tex_rot;
}
void Polygon2D::set_texture_scale(const Size2 &p_scale) {
tex_scale = p_scale;
queue_redraw();
}
Size2 Polygon2D::get_texture_scale() const {
return tex_scale;
}
void Polygon2D::set_invert(bool p_invert) {
invert = p_invert;
queue_redraw();
notify_property_list_changed();
}
bool Polygon2D::get_invert() const {
return invert;
}
void Polygon2D::set_antialiased(bool p_antialiased) {
antialiased = p_antialiased;
queue_redraw();
}
bool Polygon2D::get_antialiased() const {
return antialiased;
}
void Polygon2D::set_invert_border(real_t p_invert_border) {
invert_border = p_invert_border;
queue_redraw();
}
real_t Polygon2D::get_invert_border() const {
return invert_border;
}
void Polygon2D::set_offset(const Vector2 &p_offset) {
offset = p_offset;
rect_cache_dirty = true;
queue_redraw();
}
Vector2 Polygon2D::get_offset() const {
return offset;
}
void Polygon2D::add_bone(const NodePath &p_path, const Vector<float> &p_weights) {
Bone bone;
bone.path = p_path;
bone.weights = p_weights;
bone_weights.push_back(bone);
}
int Polygon2D::get_bone_count() const {
return bone_weights.size();
}
NodePath Polygon2D::get_bone_path(int p_index) const {
ERR_FAIL_INDEX_V(p_index, bone_weights.size(), NodePath());
return bone_weights[p_index].path;
}
Vector<float> Polygon2D::get_bone_weights(int p_index) const {
ERR_FAIL_INDEX_V(p_index, bone_weights.size(), Vector<float>());
return bone_weights[p_index].weights;
}
void Polygon2D::erase_bone(int p_idx) {
ERR_FAIL_INDEX(p_idx, bone_weights.size());
bone_weights.remove_at(p_idx);
}
void Polygon2D::clear_bones() {
bone_weights.clear();
}
void Polygon2D::set_bone_weights(int p_index, const Vector<float> &p_weights) {
ERR_FAIL_INDEX(p_index, bone_weights.size());
bone_weights.write[p_index].weights = p_weights;
queue_redraw();
}
void Polygon2D::set_bone_path(int p_index, const NodePath &p_path) {
ERR_FAIL_INDEX(p_index, bone_weights.size());
bone_weights.write[p_index].path = p_path;
queue_redraw();
}
Array Polygon2D::_get_bones() const {
Array bones;
for (int i = 0; i < get_bone_count(); i++) {
// Convert path property to String to avoid errors due to invalid node path in editor,
// because it's relative to the Skeleton2D node and not Polygon2D.
bones.push_back(String(get_bone_path(i)));
bones.push_back(get_bone_weights(i));
}
return bones;
}
void Polygon2D::_set_bones(const Array &p_bones) {
ERR_FAIL_COND(p_bones.size() & 1);
clear_bones();
for (int i = 0; i < p_bones.size(); i += 2) {
// Convert back from String to NodePath.
add_bone(NodePath(p_bones[i]), p_bones[i + 1]);
}
}
void Polygon2D::set_skeleton(const NodePath &p_skeleton) {
if (skeleton == p_skeleton) {
return;
}
skeleton = p_skeleton;
queue_redraw();
}
NodePath Polygon2D::get_skeleton() const {
return skeleton;
}
void Polygon2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &Polygon2D::set_polygon);
ClassDB::bind_method(D_METHOD("get_polygon"), &Polygon2D::get_polygon);
ClassDB::bind_method(D_METHOD("set_uv", "uv"), &Polygon2D::set_uv);
ClassDB::bind_method(D_METHOD("get_uv"), &Polygon2D::get_uv);
ClassDB::bind_method(D_METHOD("set_color", "color"), &Polygon2D::set_color);
ClassDB::bind_method(D_METHOD("get_color"), &Polygon2D::get_color);
ClassDB::bind_method(D_METHOD("set_polygons", "polygons"), &Polygon2D::set_polygons);
ClassDB::bind_method(D_METHOD("get_polygons"), &Polygon2D::get_polygons);
ClassDB::bind_method(D_METHOD("set_vertex_colors", "vertex_colors"), &Polygon2D::set_vertex_colors);
ClassDB::bind_method(D_METHOD("get_vertex_colors"), &Polygon2D::get_vertex_colors);
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &Polygon2D::set_texture);
ClassDB::bind_method(D_METHOD("get_texture"), &Polygon2D::get_texture);
ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &Polygon2D::set_texture_offset);
ClassDB::bind_method(D_METHOD("get_texture_offset"), &Polygon2D::get_texture_offset);
ClassDB::bind_method(D_METHOD("set_texture_rotation", "texture_rotation"), &Polygon2D::set_texture_rotation);
ClassDB::bind_method(D_METHOD("get_texture_rotation"), &Polygon2D::get_texture_rotation);
ClassDB::bind_method(D_METHOD("set_texture_scale", "texture_scale"), &Polygon2D::set_texture_scale);
ClassDB::bind_method(D_METHOD("get_texture_scale"), &Polygon2D::get_texture_scale);
ClassDB::bind_method(D_METHOD("set_invert_enabled", "invert"), &Polygon2D::set_invert);
ClassDB::bind_method(D_METHOD("get_invert_enabled"), &Polygon2D::get_invert);
ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &Polygon2D::set_antialiased);
ClassDB::bind_method(D_METHOD("get_antialiased"), &Polygon2D::get_antialiased);
ClassDB::bind_method(D_METHOD("set_invert_border", "invert_border"), &Polygon2D::set_invert_border);
ClassDB::bind_method(D_METHOD("get_invert_border"), &Polygon2D::get_invert_border);
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &Polygon2D::set_offset);
ClassDB::bind_method(D_METHOD("get_offset"), &Polygon2D::get_offset);
ClassDB::bind_method(D_METHOD("add_bone", "path", "weights"), &Polygon2D::add_bone);
ClassDB::bind_method(D_METHOD("get_bone_count"), &Polygon2D::get_bone_count);
ClassDB::bind_method(D_METHOD("get_bone_path", "index"), &Polygon2D::get_bone_path);
ClassDB::bind_method(D_METHOD("get_bone_weights", "index"), &Polygon2D::get_bone_weights);
ClassDB::bind_method(D_METHOD("erase_bone", "index"), &Polygon2D::erase_bone);
ClassDB::bind_method(D_METHOD("clear_bones"), &Polygon2D::clear_bones);
ClassDB::bind_method(D_METHOD("set_bone_path", "index", "path"), &Polygon2D::set_bone_path);
ClassDB::bind_method(D_METHOD("set_bone_weights", "index", "weights"), &Polygon2D::set_bone_weights);
ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &Polygon2D::set_skeleton);
ClassDB::bind_method(D_METHOD("get_skeleton"), &Polygon2D::get_skeleton);
ClassDB::bind_method(D_METHOD("set_internal_vertex_count", "internal_vertex_count"), &Polygon2D::set_internal_vertex_count);
ClassDB::bind_method(D_METHOD("get_internal_vertex_count"), &Polygon2D::get_internal_vertex_count);
ClassDB::bind_method(D_METHOD("_set_bones", "bones"), &Polygon2D::_set_bones);
ClassDB::bind_method(D_METHOD("_get_bones"), &Polygon2D::_get_bones);
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased"), "set_antialiased", "get_antialiased");
ADD_GROUP("Texture", "texture_");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_texture_offset", "get_texture_offset");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_scale", PROPERTY_HINT_LINK), "set_texture_scale", "get_texture_scale");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texture_rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians_as_degrees"), "set_texture_rotation", "get_texture_rotation");
ADD_GROUP("Skeleton", "");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton2D"), "set_skeleton", "get_skeleton");
ADD_GROUP("Invert", "invert_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert_enabled"), "set_invert_enabled", "get_invert_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "invert_border", PROPERTY_HINT_RANGE, "0.1,16384,0.1,suffix:px"), "set_invert_border", "get_invert_border");
ADD_GROUP("Data", "");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "uv"), "set_uv", "get_uv");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "vertex_colors"), "set_vertex_colors", "get_vertex_colors");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons"), "set_polygons", "get_polygons");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_bones", "_get_bones");
ADD_PROPERTY(PropertyInfo(Variant::INT, "internal_vertex_count", PROPERTY_HINT_RANGE, "0,1000"), "set_internal_vertex_count", "get_internal_vertex_count");
}
Polygon2D::Polygon2D() {
mesh = RS::get_singleton()->mesh_create();
}
Polygon2D::~Polygon2D() {
// This will free the internally-allocated mesh instance, if allocated.
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), RID());
RS::get_singleton()->free(mesh);
}

View file

@ -0,0 +1,154 @@
/**************************************************************************/
/* polygon_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef POLYGON_2D_H
#define POLYGON_2D_H
#include "scene/2d/node_2d.h"
class Polygon2D : public Node2D {
GDCLASS(Polygon2D, Node2D);
Vector<Vector2> polygon;
Vector<Vector2> uv;
Vector<Color> vertex_colors;
Array polygons;
int internal_vertices = 0;
struct Bone {
NodePath path;
Vector<float> weights;
};
Vector<Bone> bone_weights;
Color color = Color(1, 1, 1);
Ref<Texture2D> texture;
Size2 tex_scale = Vector2(1, 1);
Vector2 tex_ofs;
bool tex_tile = true;
real_t tex_rot = 0.0;
bool invert = false;
real_t invert_border = 100.0;
bool antialiased = false;
Vector2 offset;
mutable bool rect_cache_dirty = true;
mutable Rect2 item_rect;
NodePath skeleton;
ObjectID current_skeleton_id;
Array _get_bones() const;
void _set_bones(const Array &p_bones);
void _skeleton_bone_setup_changed();
RID mesh;
protected:
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
public:
#ifdef TOOLS_ENABLED
virtual Dictionary _edit_get_state() const override;
virtual void _edit_set_state(const Dictionary &p_state) override;
virtual void _edit_set_pivot(const Point2 &p_pivot) override;
virtual Point2 _edit_get_pivot() const override;
virtual bool _edit_use_pivot() const override;
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#endif
void set_polygon(const Vector<Vector2> &p_polygon);
Vector<Vector2> get_polygon() const;
void set_internal_vertex_count(int p_count);
int get_internal_vertex_count() const;
void set_uv(const Vector<Vector2> &p_uv);
Vector<Vector2> get_uv() const;
void set_polygons(const Array &p_polygons);
Array get_polygons() const;
void set_color(const Color &p_color);
Color get_color() const;
void set_vertex_colors(const Vector<Color> &p_colors);
Vector<Color> get_vertex_colors() const;
void set_texture(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture() const;
void set_texture_offset(const Vector2 &p_offset);
Vector2 get_texture_offset() const;
void set_texture_rotation(real_t p_rot);
real_t get_texture_rotation() const;
void set_texture_scale(const Size2 &p_scale);
Size2 get_texture_scale() const;
void set_invert(bool p_invert);
bool get_invert() const;
void set_antialiased(bool p_antialiased);
bool get_antialiased() const;
void set_invert_border(real_t p_invert_border);
real_t get_invert_border() const;
void set_offset(const Vector2 &p_offset);
Vector2 get_offset() const;
void add_bone(const NodePath &p_path = NodePath(), const Vector<float> &p_weights = Vector<float>());
int get_bone_count() const;
NodePath get_bone_path(int p_index) const;
Vector<float> get_bone_weights(int p_index) const;
void erase_bone(int p_idx);
void clear_bones();
void set_bone_weights(int p_index, const Vector<float> &p_weights);
void set_bone_path(int p_index, const NodePath &p_path);
void set_skeleton(const NodePath &p_skeleton);
NodePath get_skeleton() const;
Polygon2D();
~Polygon2D();
};
#endif // POLYGON_2D_H

View file

@ -0,0 +1,241 @@
/**************************************************************************/
/* remote_transform_2d.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 "remote_transform_2d.h"
void RemoteTransform2D::_update_cache() {
cache = ObjectID();
if (has_node(remote_node)) {
Node *node = get_node(remote_node);
if (!node || this == node || node->is_ancestor_of(this) || is_ancestor_of(node)) {
return;
}
cache = node->get_instance_id();
}
}
void RemoteTransform2D::_update_remote() {
if (!is_inside_tree()) {
return;
}
if (cache.is_null()) {
return;
}
Node2D *n = Object::cast_to<Node2D>(ObjectDB::get_instance(cache));
if (!n) {
return;
}
if (!n->is_inside_tree()) {
return;
}
if (!(update_remote_position || update_remote_rotation || update_remote_scale)) {
return; // The transform data of the RemoteTransform2D is not used at all.
}
//todo make faster
if (use_global_coordinates) {
if (update_remote_position && update_remote_rotation && update_remote_scale) {
n->set_global_transform(get_global_transform());
return;
}
Transform2D n_trans = n->get_global_transform();
Transform2D our_trans = get_global_transform();
// There are more steps in the operation of set_rotation, so avoid calling it.
Transform2D trans = update_remote_rotation ? our_trans : n_trans;
if (update_remote_rotation ^ update_remote_position) {
trans.set_origin(update_remote_position ? our_trans.get_origin() : n_trans.get_origin());
}
if (update_remote_rotation ^ update_remote_scale) {
trans.set_scale(update_remote_scale ? our_trans.get_scale() : n_trans.get_scale());
}
n->set_global_transform(trans);
} else {
if (update_remote_position && update_remote_rotation && update_remote_scale) {
n->set_transform(get_transform());
return;
}
Transform2D n_trans = n->get_transform();
Transform2D our_trans = get_transform();
// There are more steps in the operation of set_rotation, so avoid calling it.
Transform2D trans = update_remote_rotation ? our_trans : n_trans;
if (update_remote_rotation ^ update_remote_position) {
trans.set_origin(update_remote_position ? our_trans.get_origin() : n_trans.get_origin());
}
if (update_remote_rotation ^ update_remote_scale) {
trans.set_scale(update_remote_scale ? our_trans.get_scale() : n_trans.get_scale());
}
n->set_transform(trans);
}
}
void RemoteTransform2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
_update_cache();
} break;
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
case NOTIFICATION_TRANSFORM_CHANGED: {
if (!is_inside_tree()) {
break;
}
if (cache.is_valid()) {
_update_remote();
}
} break;
}
}
void RemoteTransform2D::set_remote_node(const NodePath &p_remote_node) {
if (remote_node == p_remote_node) {
return;
}
remote_node = p_remote_node;
if (is_inside_tree()) {
_update_cache();
_update_remote();
}
update_configuration_warnings();
}
NodePath RemoteTransform2D::get_remote_node() const {
return remote_node;
}
void RemoteTransform2D::set_use_global_coordinates(const bool p_enable) {
if (use_global_coordinates == p_enable) {
return;
}
use_global_coordinates = p_enable;
set_notify_transform(use_global_coordinates);
set_notify_local_transform(!use_global_coordinates);
_update_remote();
}
bool RemoteTransform2D::get_use_global_coordinates() const {
return use_global_coordinates;
}
void RemoteTransform2D::set_update_position(const bool p_update) {
if (update_remote_position == p_update) {
return;
}
update_remote_position = p_update;
_update_remote();
}
bool RemoteTransform2D::get_update_position() const {
return update_remote_position;
}
void RemoteTransform2D::set_update_rotation(const bool p_update) {
if (update_remote_rotation == p_update) {
return;
}
update_remote_rotation = p_update;
_update_remote();
}
bool RemoteTransform2D::get_update_rotation() const {
return update_remote_rotation;
}
void RemoteTransform2D::set_update_scale(const bool p_update) {
if (update_remote_scale == p_update) {
return;
}
update_remote_scale = p_update;
_update_remote();
}
bool RemoteTransform2D::get_update_scale() const {
return update_remote_scale;
}
void RemoteTransform2D::force_update_cache() {
_update_cache();
}
PackedStringArray RemoteTransform2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) {
warnings.push_back(RTR("Path property must point to a valid Node2D node to work."));
}
return warnings;
}
void RemoteTransform2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_remote_node", "path"), &RemoteTransform2D::set_remote_node);
ClassDB::bind_method(D_METHOD("get_remote_node"), &RemoteTransform2D::get_remote_node);
ClassDB::bind_method(D_METHOD("force_update_cache"), &RemoteTransform2D::force_update_cache);
ClassDB::bind_method(D_METHOD("set_use_global_coordinates", "use_global_coordinates"), &RemoteTransform2D::set_use_global_coordinates);
ClassDB::bind_method(D_METHOD("get_use_global_coordinates"), &RemoteTransform2D::get_use_global_coordinates);
ClassDB::bind_method(D_METHOD("set_update_position", "update_remote_position"), &RemoteTransform2D::set_update_position);
ClassDB::bind_method(D_METHOD("get_update_position"), &RemoteTransform2D::get_update_position);
ClassDB::bind_method(D_METHOD("set_update_rotation", "update_remote_rotation"), &RemoteTransform2D::set_update_rotation);
ClassDB::bind_method(D_METHOD("get_update_rotation"), &RemoteTransform2D::get_update_rotation);
ClassDB::bind_method(D_METHOD("set_update_scale", "update_remote_scale"), &RemoteTransform2D::set_update_scale);
ClassDB::bind_method(D_METHOD("get_update_scale"), &RemoteTransform2D::get_update_scale);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_remote_node", "get_remote_node");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_global_coordinates"), "set_use_global_coordinates", "get_use_global_coordinates");
ADD_GROUP("Update", "update_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "update_position"), "set_update_position", "get_update_position");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "update_rotation"), "set_update_rotation", "get_update_rotation");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "update_scale"), "set_update_scale", "get_update_scale");
}
RemoteTransform2D::RemoteTransform2D() {
set_notify_transform(use_global_coordinates);
set_notify_local_transform(!use_global_coordinates);
set_hide_clip_children(true);
}

View file

@ -0,0 +1,78 @@
/**************************************************************************/
/* remote_transform_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef REMOTE_TRANSFORM_2D_H
#define REMOTE_TRANSFORM_2D_H
#include "scene/2d/node_2d.h"
class RemoteTransform2D : public Node2D {
GDCLASS(RemoteTransform2D, Node2D);
NodePath remote_node;
ObjectID cache;
bool use_global_coordinates = true;
bool update_remote_position = true;
bool update_remote_rotation = true;
bool update_remote_scale = true;
void _update_remote();
void _update_cache();
//void _node_exited_scene();
protected:
static void _bind_methods();
void _notification(int p_what);
public:
void set_remote_node(const NodePath &p_remote_node);
NodePath get_remote_node() const;
void set_use_global_coordinates(const bool p_enable);
bool get_use_global_coordinates() const;
void set_update_position(const bool p_update);
bool get_update_position() const;
void set_update_rotation(const bool p_update);
bool get_update_rotation() const;
void set_update_scale(const bool p_update);
bool get_update_scale() const;
void force_update_cache();
PackedStringArray get_configuration_warnings() const override;
RemoteTransform2D();
};
#endif // REMOTE_TRANSFORM_2D_H

View file

@ -0,0 +1,851 @@
/**************************************************************************/
/* skeleton_2d.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 "skeleton_2d.h"
#include "core/math/transform_interpolator.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_data.h"
#include "editor/editor_settings.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
#endif //TOOLS_ENABLED
bool Bone2D::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("auto_calculate_length_and_angle")) {
set_autocalculate_length_and_angle(p_value);
} else if (path.begins_with("length")) {
set_length(p_value);
} else if (path.begins_with("bone_angle")) {
set_bone_angle(Math::deg_to_rad(real_t(p_value)));
} else if (path.begins_with("default_length")) {
set_length(p_value);
}
#ifdef TOOLS_ENABLED
else if (path.begins_with("editor_settings/show_bone_gizmo")) {
_editor_set_show_bone_gizmo(p_value);
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
bool Bone2D::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("auto_calculate_length_and_angle")) {
r_ret = get_autocalculate_length_and_angle();
} else if (path.begins_with("length")) {
r_ret = get_length();
} else if (path.begins_with("bone_angle")) {
r_ret = Math::rad_to_deg(get_bone_angle());
} else if (path.begins_with("default_length")) {
r_ret = get_length();
}
#ifdef TOOLS_ENABLED
else if (path.begins_with("editor_settings/show_bone_gizmo")) {
r_ret = _editor_get_show_bone_gizmo();
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
void Bone2D::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("auto_calculate_length_and_angle"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
if (!autocalculate_length_and_angle) {
p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("length"), PROPERTY_HINT_RANGE, "1, 1024, 1", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("bone_angle"), PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT));
}
#ifdef TOOLS_ENABLED
p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("editor_settings/show_bone_gizmo"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
#endif // TOOLS_ENABLED
}
void Bone2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
Node *parent = get_parent();
parent_bone = Object::cast_to<Bone2D>(parent);
skeleton = nullptr;
while (parent) {
skeleton = Object::cast_to<Skeleton2D>(parent);
if (skeleton) {
break;
}
if (!Object::cast_to<Bone2D>(parent)) {
break; //skeletons must be chained to Bone2Ds.
}
parent = parent->get_parent();
}
if (skeleton) {
Skeleton2D::Bone bone;
bone.bone = this;
skeleton->bones.push_back(bone);
skeleton->_make_bone_setup_dirty();
get_parent()->connect(SNAME("child_order_changed"), callable_mp(skeleton, &Skeleton2D::_make_bone_setup_dirty), CONNECT_REFERENCE_COUNTED);
}
cache_transform = get_transform();
copy_transform_to_cache = true;
#ifdef TOOLS_ENABLED
// Only draw the gizmo in the editor!
if (Engine::get_singleton()->is_editor_hint() == false) {
return;
}
queue_redraw();
#endif // TOOLS_ENABLED
} break;
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
if (skeleton) {
skeleton->_make_transform_dirty();
}
if (copy_transform_to_cache) {
cache_transform = get_transform();
}
#ifdef TOOLS_ENABLED
// Only draw the gizmo in the editor!
if (Engine::get_singleton()->is_editor_hint() == false) {
return;
}
queue_redraw();
if (get_parent()) {
Bone2D *p_bone = Object::cast_to<Bone2D>(get_parent());
if (p_bone) {
p_bone->queue_redraw();
}
}
#endif // TOOLS_ENABLED
} break;
case NOTIFICATION_EXIT_TREE: {
if (skeleton) {
for (int i = 0; i < skeleton->bones.size(); i++) {
if (skeleton->bones[i].bone == this) {
skeleton->bones.remove_at(i);
break;
}
}
skeleton->_make_bone_setup_dirty();
get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(skeleton, &Skeleton2D::_make_bone_setup_dirty));
}
parent_bone = nullptr;
set_transform(cache_transform);
} break;
case NOTIFICATION_READY: {
if (autocalculate_length_and_angle) {
calculate_length_and_rotation();
}
} break;
#ifdef TOOLS_ENABLED
case NOTIFICATION_EDITOR_PRE_SAVE:
case NOTIFICATION_EDITOR_POST_SAVE: {
Transform2D tmp_trans = get_transform();
set_transform(cache_transform);
cache_transform = tmp_trans;
} break;
// Bone2D Editor gizmo drawing.
// TODO: Bone2D gizmo drawing needs to be moved to an editor plugin.
case NOTIFICATION_DRAW: {
// Only draw the gizmo in the editor!
if (Engine::get_singleton()->is_editor_hint() == false) {
return;
}
if (editor_gizmo_rid.is_null()) {
editor_gizmo_rid = RenderingServer::get_singleton()->canvas_item_create();
RenderingServer::get_singleton()->canvas_item_set_parent(editor_gizmo_rid, get_canvas_item());
RenderingServer::get_singleton()->canvas_item_set_z_as_relative_to_parent(editor_gizmo_rid, true);
RenderingServer::get_singleton()->canvas_item_set_z_index(editor_gizmo_rid, 10);
}
RenderingServer::get_singleton()->canvas_item_clear(editor_gizmo_rid);
if (!_editor_show_bone_gizmo) {
return;
}
// Undo scaling
Transform2D editor_gizmo_trans;
editor_gizmo_trans.set_scale(Vector2(1, 1) / get_global_scale());
RenderingServer::get_singleton()->canvas_item_set_transform(editor_gizmo_rid, editor_gizmo_trans);
Color bone_color1 = EDITOR_GET("editors/2d/bone_color1");
Color bone_color2 = EDITOR_GET("editors/2d/bone_color2");
Color bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color");
Color bone_outline_color = EDITOR_GET("editors/2d/bone_outline_color");
Color bone_selected_color = EDITOR_GET("editors/2d/bone_selected_color");
bool Bone2D_found = false;
for (int i = 0; i < get_child_count(); i++) {
Bone2D *child_node = nullptr;
child_node = Object::cast_to<Bone2D>(get_child(i));
if (!child_node) {
continue;
}
Bone2D_found = true;
Vector<Vector2> bone_shape;
Vector<Vector2> bone_shape_outline;
_editor_get_bone_shape(&bone_shape, &bone_shape_outline, child_node);
Vector<Color> colors;
if (has_meta("_local_pose_override_enabled_")) {
colors.push_back(bone_ik_color);
colors.push_back(bone_ik_color);
colors.push_back(bone_ik_color);
colors.push_back(bone_ik_color);
} else {
colors.push_back(bone_color1);
colors.push_back(bone_color2);
colors.push_back(bone_color1);
colors.push_back(bone_color2);
}
Vector<Color> outline_colors;
if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) {
outline_colors.push_back(bone_selected_color);
outline_colors.push_back(bone_selected_color);
outline_colors.push_back(bone_selected_color);
outline_colors.push_back(bone_selected_color);
outline_colors.push_back(bone_selected_color);
outline_colors.push_back(bone_selected_color);
} else {
outline_colors.push_back(bone_outline_color);
outline_colors.push_back(bone_outline_color);
outline_colors.push_back(bone_outline_color);
outline_colors.push_back(bone_outline_color);
outline_colors.push_back(bone_outline_color);
outline_colors.push_back(bone_outline_color);
}
RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors);
RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors);
}
if (!Bone2D_found) {
Vector<Vector2> bone_shape;
Vector<Vector2> bone_shape_outline;
_editor_get_bone_shape(&bone_shape, &bone_shape_outline, nullptr);
Vector<Color> colors;
if (has_meta("_local_pose_override_enabled_")) {
colors.push_back(bone_ik_color);
colors.push_back(bone_ik_color);
colors.push_back(bone_ik_color);
colors.push_back(bone_ik_color);
} else {
colors.push_back(bone_color1);
colors.push_back(bone_color2);
colors.push_back(bone_color1);
colors.push_back(bone_color2);
}
Vector<Color> outline_colors;
if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) {
outline_colors.push_back(bone_selected_color);
outline_colors.push_back(bone_selected_color);
outline_colors.push_back(bone_selected_color);
outline_colors.push_back(bone_selected_color);
outline_colors.push_back(bone_selected_color);
outline_colors.push_back(bone_selected_color);
} else {
outline_colors.push_back(bone_outline_color);
outline_colors.push_back(bone_outline_color);
outline_colors.push_back(bone_outline_color);
outline_colors.push_back(bone_outline_color);
outline_colors.push_back(bone_outline_color);
outline_colors.push_back(bone_outline_color);
}
RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors);
RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors);
}
} break;
#endif // TOOLS_ENABLED
}
}
#ifdef TOOLS_ENABLED
bool Bone2D::_editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone) {
float bone_width = EDITOR_GET("editors/2d/bone_width");
float bone_outline_width = EDITOR_GET("editors/2d/bone_outline_size");
if (!is_inside_tree()) {
return false; //may have been removed
}
if (!p_other_bone && length <= 0) {
return false;
}
Vector2 rel;
if (p_other_bone) {
rel = (p_other_bone->get_global_position() - get_global_position());
rel = rel.rotated(-get_global_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation
} else {
real_t angle_to_use = get_rotation() + bone_angle;
rel = Vector2(cos(angle_to_use), sin(angle_to_use)) * (length * MIN(get_global_scale().x, get_global_scale().y));
rel = rel.rotated(-get_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation
}
Vector2 relt = rel.rotated(Math_PI * 0.5).normalized() * bone_width;
Vector2 reln = rel.normalized();
Vector2 reltn = relt.normalized();
if (p_shape) {
p_shape->clear();
p_shape->push_back(Vector2(0, 0));
p_shape->push_back(rel * 0.2 + relt);
p_shape->push_back(rel);
p_shape->push_back(rel * 0.2 - relt);
}
if (p_outline_shape) {
p_outline_shape->clear();
p_outline_shape->push_back((-reln - reltn) * bone_outline_width);
p_outline_shape->push_back((-reln + reltn) * bone_outline_width);
p_outline_shape->push_back(rel * 0.2 + relt + reltn * bone_outline_width);
p_outline_shape->push_back(rel + (reln + reltn) * bone_outline_width);
p_outline_shape->push_back(rel + (reln - reltn) * bone_outline_width);
p_outline_shape->push_back(rel * 0.2 - relt - reltn * bone_outline_width);
}
return true;
}
void Bone2D::_editor_set_show_bone_gizmo(bool p_show_gizmo) {
_editor_show_bone_gizmo = p_show_gizmo;
queue_redraw();
}
bool Bone2D::_editor_get_show_bone_gizmo() const {
return _editor_show_bone_gizmo;
}
#endif // TOOLS_ENABLED
void Bone2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_rest", "rest"), &Bone2D::set_rest);
ClassDB::bind_method(D_METHOD("get_rest"), &Bone2D::get_rest);
ClassDB::bind_method(D_METHOD("apply_rest"), &Bone2D::apply_rest);
ClassDB::bind_method(D_METHOD("get_skeleton_rest"), &Bone2D::get_skeleton_rest);
ClassDB::bind_method(D_METHOD("get_index_in_skeleton"), &Bone2D::get_index_in_skeleton);
ClassDB::bind_method(D_METHOD("set_autocalculate_length_and_angle", "auto_calculate"), &Bone2D::set_autocalculate_length_and_angle);
ClassDB::bind_method(D_METHOD("get_autocalculate_length_and_angle"), &Bone2D::get_autocalculate_length_and_angle);
ClassDB::bind_method(D_METHOD("set_length", "length"), &Bone2D::set_length);
ClassDB::bind_method(D_METHOD("get_length"), &Bone2D::get_length);
ClassDB::bind_method(D_METHOD("set_bone_angle", "angle"), &Bone2D::set_bone_angle);
ClassDB::bind_method(D_METHOD("get_bone_angle"), &Bone2D::get_bone_angle);
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest", PROPERTY_HINT_NONE, "suffix:px"), "set_rest", "get_rest");
}
void Bone2D::set_rest(const Transform2D &p_rest) {
rest = p_rest;
if (skeleton) {
skeleton->_make_bone_setup_dirty();
}
update_configuration_warnings();
}
Transform2D Bone2D::get_rest() const {
return rest;
}
Transform2D Bone2D::get_skeleton_rest() const {
if (parent_bone) {
return parent_bone->get_skeleton_rest() * rest;
} else {
return rest;
}
}
void Bone2D::apply_rest() {
set_transform(rest);
}
int Bone2D::get_index_in_skeleton() const {
ERR_FAIL_NULL_V(skeleton, -1);
skeleton->_update_bone_setup();
return skeleton_index;
}
PackedStringArray Bone2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (!skeleton) {
if (parent_bone) {
warnings.push_back(RTR("This Bone2D chain should end at a Skeleton2D node."));
} else {
warnings.push_back(RTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node."));
}
}
if (rest == Transform2D(0, 0, 0, 0, 0, 0)) {
warnings.push_back(RTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one."));
}
return warnings;
}
void Bone2D::calculate_length_and_rotation() {
// If there is at least a single child Bone2D node, we can calculate
// the length and direction. We will always just use the first Bone2D for this.
int child_count = get_child_count();
Transform2D global_inv = get_global_transform().affine_inverse();
for (int i = 0; i < child_count; i++) {
Bone2D *child = Object::cast_to<Bone2D>(get_child(i));
if (child) {
Vector2 child_local_pos = global_inv.xform(child->get_global_position());
length = child_local_pos.length();
bone_angle = child_local_pos.angle();
return; // Finished!
}
}
WARN_PRINT("No Bone2D children of node " + get_name() + ". Cannot calculate bone length or angle reliably.\nUsing transform rotation for bone angle.");
bone_angle = get_transform().get_rotation();
}
void Bone2D::set_autocalculate_length_and_angle(bool p_autocalculate) {
autocalculate_length_and_angle = p_autocalculate;
if (autocalculate_length_and_angle) {
calculate_length_and_rotation();
}
notify_property_list_changed();
}
bool Bone2D::get_autocalculate_length_and_angle() const {
return autocalculate_length_and_angle;
}
void Bone2D::set_length(real_t p_length) {
length = p_length;
#ifdef TOOLS_ENABLED
queue_redraw();
#endif // TOOLS_ENABLED
}
real_t Bone2D::get_length() const {
return length;
}
void Bone2D::set_bone_angle(real_t p_angle) {
bone_angle = p_angle;
#ifdef TOOLS_ENABLED
queue_redraw();
#endif // TOOLS_ENABLED
}
real_t Bone2D::get_bone_angle() const {
return bone_angle;
}
Bone2D::Bone2D() {
skeleton = nullptr;
parent_bone = nullptr;
skeleton_index = -1;
length = 16;
bone_angle = 0;
autocalculate_length_and_angle = true;
set_notify_local_transform(true);
set_hide_clip_children(true);
//this is a clever hack so the bone knows no rest has been set yet, allowing to show an error.
for (int i = 0; i < 3; i++) {
rest[i] = Vector2(0, 0);
}
copy_transform_to_cache = true;
}
Bone2D::~Bone2D() {
#ifdef TOOLS_ENABLED
if (!editor_gizmo_rid.is_null()) {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RenderingServer::get_singleton()->free(editor_gizmo_rid);
}
#endif // TOOLS_ENABLED
}
//////////////////////////////////////
bool Skeleton2D::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("modification_stack")) {
set_modification_stack(p_value);
return true;
}
return true;
}
bool Skeleton2D::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("modification_stack")) {
r_ret = get_modification_stack();
return true;
}
return true;
}
void Skeleton2D::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(
PropertyInfo(Variant::OBJECT, PNAME("modification_stack"),
PROPERTY_HINT_RESOURCE_TYPE,
"SkeletonModificationStack2D",
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE));
}
void Skeleton2D::_make_bone_setup_dirty() {
if (bone_setup_dirty) {
return;
}
bone_setup_dirty = true;
if (is_inside_tree()) {
callable_mp(this, &Skeleton2D::_update_bone_setup).call_deferred();
}
}
void Skeleton2D::_update_bone_setup() {
if (!bone_setup_dirty) {
return;
}
bone_setup_dirty = false;
RS::get_singleton()->skeleton_allocate_data(skeleton, bones.size(), true);
bones.sort(); //sorting so that they are always in the same order/index
for (int i = 0; i < bones.size(); i++) {
bones.write[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose
bones.write[i].bone->skeleton_index = i;
Bone2D *parent_bone = Object::cast_to<Bone2D>(bones[i].bone->get_parent());
if (parent_bone) {
bones.write[i].parent_index = parent_bone->skeleton_index;
} else {
bones.write[i].parent_index = -1;
}
bones.write[i].local_pose_override = bones[i].bone->get_skeleton_rest();
}
transform_dirty = true;
_update_transform();
emit_signal(SNAME("bone_setup_changed"));
}
void Skeleton2D::_make_transform_dirty() {
if (transform_dirty) {
return;
}
transform_dirty = true;
if (is_inside_tree()) {
callable_mp(this, &Skeleton2D::_update_transform).call_deferred();
}
}
void Skeleton2D::_update_transform() {
if (bone_setup_dirty) {
_update_bone_setup();
return; //above will update transform anyway
}
if (!transform_dirty) {
return;
}
transform_dirty = false;
for (int i = 0; i < bones.size(); i++) {
ERR_CONTINUE(bones[i].parent_index >= i);
if (bones[i].parent_index >= 0) {
bones.write[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform();
} else {
bones.write[i].accum_transform = bones[i].bone->get_transform();
}
}
for (int i = 0; i < bones.size(); i++) {
Transform2D final_xform = bones[i].accum_transform * bones[i].rest_inverse;
RS::get_singleton()->skeleton_bone_set_transform_2d(skeleton, i, final_xform);
}
}
int Skeleton2D::get_bone_count() const {
ERR_FAIL_COND_V(!is_inside_tree(), 0);
if (bone_setup_dirty) {
const_cast<Skeleton2D *>(this)->_update_bone_setup();
}
return bones.size();
}
Bone2D *Skeleton2D::get_bone(int p_idx) {
ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
ERR_FAIL_INDEX_V(p_idx, bones.size(), nullptr);
return bones[p_idx].bone;
}
void Skeleton2D::_update_process_mode() {
bool process = modification_stack.is_valid() && is_inside_tree();
if (!process) {
// We might have another reason to process.
process = is_physics_interpolated_and_enabled() && is_visible_in_tree();
}
set_process_internal(process);
set_physics_process_internal(process);
}
void Skeleton2D::_ensure_update_interpolation_data() {
uint64_t tick = Engine::get_singleton()->get_physics_frames();
if (_interpolation_data.last_update_physics_tick != tick) {
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
_interpolation_data.last_update_physics_tick = tick;
}
}
void Skeleton2D::_physics_interpolated_changed() {
_update_process_mode();
}
void Skeleton2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
if (bone_setup_dirty) {
_update_bone_setup();
}
if (transform_dirty) {
_update_transform();
}
request_ready();
} break;
case NOTIFICATION_ENTER_TREE: {
_update_process_mode();
if (is_physics_interpolated_and_enabled()) {
_interpolation_data.xform_curr = get_global_transform();
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
}
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if (is_physics_interpolated_and_enabled()) {
_ensure_update_interpolation_data();
if (Engine::get_singleton()->is_in_physics_frame()) {
_interpolation_data.xform_curr = get_global_transform();
}
} else {
RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform());
}
} break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
_interpolation_data.xform_curr = get_global_transform();
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (is_physics_interpolated_and_enabled()) {
Transform2D res;
TransformInterpolator::interpolate_transform_2d(_interpolation_data.xform_prev, _interpolation_data.xform_curr, res, Engine::get_singleton()->get_physics_interpolation_fraction());
RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, res);
}
if (modification_stack.is_valid()) {
execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process);
}
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (is_physics_interpolated_and_enabled()) {
_ensure_update_interpolation_data();
_interpolation_data.xform_curr = get_global_transform();
}
if (modification_stack.is_valid()) {
execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process);
}
} break;
case NOTIFICATION_POST_ENTER_TREE: {
set_modification_stack(modification_stack);
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
_update_process_mode();
} break;
#ifdef TOOLS_ENABLED
case NOTIFICATION_DRAW: {
if (Engine::get_singleton()->is_editor_hint()) {
if (modification_stack.is_valid()) {
modification_stack->draw_editor_gizmos();
}
}
} break;
#endif // TOOLS_ENABLED
}
}
RID Skeleton2D::get_skeleton() const {
return skeleton;
}
void Skeleton2D::set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, real_t p_amount, bool p_persistent) {
ERR_FAIL_INDEX_MSG(p_bone_idx, bones.size(), "Bone index is out of range!");
bones.write[p_bone_idx].local_pose_override = p_override;
bones.write[p_bone_idx].local_pose_override_amount = p_amount;
bones.write[p_bone_idx].local_pose_override_persistent = p_persistent;
}
Transform2D Skeleton2D::get_bone_local_pose_override(int p_bone_idx) {
ERR_FAIL_INDEX_V_MSG(p_bone_idx, bones.size(), Transform2D(), "Bone index is out of range!");
return bones[p_bone_idx].local_pose_override;
}
void Skeleton2D::set_modification_stack(Ref<SkeletonModificationStack2D> p_stack) {
if (modification_stack.is_valid()) {
modification_stack->is_setup = false;
modification_stack->set_skeleton(nullptr);
}
modification_stack = p_stack;
if (modification_stack.is_valid() && is_inside_tree()) {
modification_stack->set_skeleton(this);
modification_stack->setup();
#ifdef TOOLS_ENABLED
modification_stack->set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
}
_update_process_mode();
}
Ref<SkeletonModificationStack2D> Skeleton2D::get_modification_stack() const {
return modification_stack;
}
void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) {
if (!modification_stack.is_valid()) {
return;
}
// Do not cache the transform changes caused by the modifications!
for (int i = 0; i < bones.size(); i++) {
bones[i].bone->copy_transform_to_cache = false;
}
if (modification_stack->skeleton != this) {
modification_stack->set_skeleton(this);
}
modification_stack->execute(p_delta, p_execution_mode);
// Only apply the local pose override on _process. Otherwise, just calculate the local_pose_override and reset the transform.
if (p_execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process) {
for (int i = 0; i < bones.size(); i++) {
if (bones[i].local_pose_override_amount > 0) {
bones[i].bone->set_meta("_local_pose_override_enabled_", true);
Transform2D final_trans = bones[i].bone->cache_transform;
final_trans = final_trans.interpolate_with(bones[i].local_pose_override, bones[i].local_pose_override_amount);
bones[i].bone->set_transform(final_trans);
bones[i].bone->propagate_call("force_update_transform");
if (bones[i].local_pose_override_persistent) {
bones.write[i].local_pose_override_amount = 0.0;
}
} else {
// TODO: see if there is a way to undo the override without having to resort to setting every bone's transform.
bones[i].bone->remove_meta("_local_pose_override_enabled_");
bones[i].bone->set_transform(bones[i].bone->cache_transform);
}
}
}
// Cache any future transform changes
for (int i = 0; i < bones.size(); i++) {
bones[i].bone->copy_transform_to_cache = true;
}
#ifdef TOOLS_ENABLED
modification_stack->set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
}
void Skeleton2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_bone_count"), &Skeleton2D::get_bone_count);
ClassDB::bind_method(D_METHOD("get_bone", "idx"), &Skeleton2D::get_bone);
ClassDB::bind_method(D_METHOD("get_skeleton"), &Skeleton2D::get_skeleton);
ClassDB::bind_method(D_METHOD("set_modification_stack", "modification_stack"), &Skeleton2D::set_modification_stack);
ClassDB::bind_method(D_METHOD("get_modification_stack"), &Skeleton2D::get_modification_stack);
ClassDB::bind_method(D_METHOD("execute_modifications", "delta", "execution_mode"), &Skeleton2D::execute_modifications);
ClassDB::bind_method(D_METHOD("set_bone_local_pose_override", "bone_idx", "override_pose", "strength", "persistent"), &Skeleton2D::set_bone_local_pose_override);
ClassDB::bind_method(D_METHOD("get_bone_local_pose_override", "bone_idx"), &Skeleton2D::get_bone_local_pose_override);
ADD_SIGNAL(MethodInfo("bone_setup_changed"));
}
Skeleton2D::Skeleton2D() {
skeleton = RS::get_singleton()->skeleton_create();
set_notify_transform(true);
set_hide_clip_children(true);
}
Skeleton2D::~Skeleton2D() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(skeleton);
}

View file

@ -0,0 +1,178 @@
/**************************************************************************/
/* skeleton_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SKELETON_2D_H
#define SKELETON_2D_H
#include "scene/2d/node_2d.h"
#include "scene/resources/2d/skeleton/skeleton_modification_2d.h"
class Skeleton2D;
class Bone2D : public Node2D {
GDCLASS(Bone2D, Node2D);
friend class Skeleton2D;
#ifdef TOOLS_ENABLED
friend class AnimatedValuesBackup;
#endif
Bone2D *parent_bone = nullptr;
Skeleton2D *skeleton = nullptr;
Transform2D rest;
bool autocalculate_length_and_angle = true;
real_t length = 16;
real_t bone_angle = 0;
int skeleton_index = -1;
void calculate_length_and_rotation();
#ifdef TOOLS_ENABLED
RID editor_gizmo_rid;
bool _editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone);
bool _editor_show_bone_gizmo = true;
#endif // TOOLS ENABLED
protected:
void _notification(int p_what);
static void _bind_methods();
bool _set(const StringName &p_path, const Variant &p_value);
bool _get(const StringName &p_path, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
Transform2D cache_transform;
bool copy_transform_to_cache = true;
void set_rest(const Transform2D &p_rest);
Transform2D get_rest() const;
void apply_rest();
Transform2D get_skeleton_rest() const;
PackedStringArray get_configuration_warnings() const override;
void set_autocalculate_length_and_angle(bool p_autocalculate);
bool get_autocalculate_length_and_angle() const;
void set_length(real_t p_length);
real_t get_length() const;
void set_bone_angle(real_t p_angle);
real_t get_bone_angle() const;
int get_index_in_skeleton() const;
#ifdef TOOLS_ENABLED
void _editor_set_show_bone_gizmo(bool p_show_gizmo);
bool _editor_get_show_bone_gizmo() const;
#endif // TOOLS_ENABLED
Bone2D();
~Bone2D();
};
class SkeletonModificationStack2D;
class Skeleton2D : public Node2D {
GDCLASS(Skeleton2D, Node2D);
friend class Bone2D;
#ifdef TOOLS_ENABLED
friend class AnimatedValuesBackup;
#endif
struct Bone {
bool operator<(const Bone &p_bone) const {
return p_bone.bone->is_greater_than(bone);
}
Bone2D *bone = nullptr;
int parent_index = 0;
Transform2D accum_transform;
Transform2D rest_inverse;
//Transform2D local_pose_cache;
Transform2D local_pose_override;
real_t local_pose_override_amount = 0;
bool local_pose_override_persistent = false;
};
Vector<Bone> bones;
bool bone_setup_dirty = true;
void _make_bone_setup_dirty();
void _update_bone_setup();
bool transform_dirty = true;
void _make_transform_dirty();
void _update_transform();
RID skeleton;
Ref<SkeletonModificationStack2D> modification_stack;
///////////////////////////////////////////////////////
// INTERPOLATION
struct InterpolationData {
Transform2D xform_curr;
Transform2D xform_prev;
uint32_t last_update_physics_tick = UINT32_MAX; // Ensure tick 0 is detected as a change.
} _interpolation_data;
void _update_process_mode();
void _ensure_update_interpolation_data();
protected:
virtual void _physics_interpolated_changed() override;
///////////////////////////////////////////////////////
void _notification(int p_what);
static void _bind_methods();
bool _set(const StringName &p_path, const Variant &p_value);
bool _get(const StringName &p_path, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
int get_bone_count() const;
Bone2D *get_bone(int p_idx);
RID get_skeleton() const;
void set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, real_t p_amount, bool p_persistent = true);
Transform2D get_bone_local_pose_override(int p_bone_idx);
Ref<SkeletonModificationStack2D> get_modification_stack() const;
void set_modification_stack(Ref<SkeletonModificationStack2D> p_stack);
void execute_modifications(real_t p_delta, int p_execution_mode);
Skeleton2D();
~Skeleton2D();
};
#endif // SKELETON_2D_H

View file

@ -0,0 +1,503 @@
/**************************************************************************/
/* sprite_2d.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 "sprite_2d.h"
#include "scene/main/window.h"
#ifdef TOOLS_ENABLED
Dictionary Sprite2D::_edit_get_state() const {
Dictionary state = Node2D::_edit_get_state();
state["offset"] = offset;
return state;
}
void Sprite2D::_edit_set_state(const Dictionary &p_state) {
Node2D::_edit_set_state(p_state);
set_offset(p_state["offset"]);
}
void Sprite2D::_edit_set_pivot(const Point2 &p_pivot) {
set_offset(get_offset() - p_pivot);
set_position(get_transform().xform(p_pivot));
}
Point2 Sprite2D::_edit_get_pivot() const {
return Vector2();
}
bool Sprite2D::_edit_use_pivot() const {
return true;
}
bool Sprite2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return is_pixel_opaque(p_point);
}
Rect2 Sprite2D::_edit_get_rect() const {
return get_rect();
}
bool Sprite2D::_edit_use_rect() const {
return texture.is_valid();
}
#endif
Rect2 Sprite2D::get_anchorable_rect() const {
return get_rect();
}
void Sprite2D::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip_enabled) const {
Rect2 base_rect;
if (region_enabled) {
r_filter_clip_enabled = region_filter_clip_enabled;
base_rect = region_rect;
} else {
r_filter_clip_enabled = false;
base_rect = Rect2(0, 0, texture->get_width(), texture->get_height());
}
Size2 frame_size = base_rect.size / Size2(hframes, vframes);
Point2 frame_offset = Point2(frame % hframes, frame / hframes);
frame_offset *= frame_size;
r_src_rect.size = frame_size;
r_src_rect.position = base_rect.position + frame_offset;
Point2 dest_offset = offset;
if (centered) {
dest_offset -= frame_size / 2;
}
if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
dest_offset = (dest_offset + Point2(0.5, 0.5)).floor();
}
r_dst_rect = Rect2(dest_offset, frame_size);
if (hflip) {
r_dst_rect.size.x = -r_dst_rect.size.x;
}
if (vflip) {
r_dst_rect.size.y = -r_dst_rect.size.y;
}
}
void Sprite2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
if (texture.is_null()) {
return;
}
RID ci = get_canvas_item();
Rect2 src_rect, dst_rect;
bool filter_clip_enabled;
_get_rects(src_rect, dst_rect, filter_clip_enabled);
texture->draw_rect_region(ci, dst_rect, src_rect, Color(1, 1, 1), false, filter_clip_enabled);
} break;
}
}
void Sprite2D::set_texture(const Ref<Texture2D> &p_texture) {
if (p_texture == texture) {
return;
}
if (texture.is_valid()) {
texture->disconnect_changed(callable_mp(this, &Sprite2D::_texture_changed));
}
texture = p_texture;
if (texture.is_valid()) {
texture->connect_changed(callable_mp(this, &Sprite2D::_texture_changed));
}
queue_redraw();
emit_signal(SceneStringName(texture_changed));
item_rect_changed();
}
Ref<Texture2D> Sprite2D::get_texture() const {
return texture;
}
void Sprite2D::set_centered(bool p_center) {
if (centered == p_center) {
return;
}
centered = p_center;
queue_redraw();
item_rect_changed();
}
bool Sprite2D::is_centered() const {
return centered;
}
void Sprite2D::set_offset(const Point2 &p_offset) {
if (offset == p_offset) {
return;
}
offset = p_offset;
queue_redraw();
item_rect_changed();
}
Point2 Sprite2D::get_offset() const {
return offset;
}
void Sprite2D::set_flip_h(bool p_flip) {
if (hflip == p_flip) {
return;
}
hflip = p_flip;
queue_redraw();
}
bool Sprite2D::is_flipped_h() const {
return hflip;
}
void Sprite2D::set_flip_v(bool p_flip) {
if (vflip == p_flip) {
return;
}
vflip = p_flip;
queue_redraw();
}
bool Sprite2D::is_flipped_v() const {
return vflip;
}
void Sprite2D::set_region_enabled(bool p_region_enabled) {
if (region_enabled == p_region_enabled) {
return;
}
region_enabled = p_region_enabled;
queue_redraw();
notify_property_list_changed();
}
bool Sprite2D::is_region_enabled() const {
return region_enabled;
}
void Sprite2D::set_region_rect(const Rect2 &p_region_rect) {
if (region_rect == p_region_rect) {
return;
}
region_rect = p_region_rect;
if (region_enabled) {
item_rect_changed();
}
}
Rect2 Sprite2D::get_region_rect() const {
return region_rect;
}
void Sprite2D::set_region_filter_clip_enabled(bool p_region_filter_clip_enabled) {
if (region_filter_clip_enabled == p_region_filter_clip_enabled) {
return;
}
region_filter_clip_enabled = p_region_filter_clip_enabled;
queue_redraw();
}
bool Sprite2D::is_region_filter_clip_enabled() const {
return region_filter_clip_enabled;
}
void Sprite2D::set_frame(int p_frame) {
ERR_FAIL_INDEX(p_frame, vframes * hframes);
if (frame == p_frame) {
return;
}
frame = p_frame;
item_rect_changed();
emit_signal(SceneStringName(frame_changed));
}
int Sprite2D::get_frame() const {
return frame;
}
void Sprite2D::set_frame_coords(const Vector2i &p_coord) {
ERR_FAIL_INDEX(p_coord.x, hframes);
ERR_FAIL_INDEX(p_coord.y, vframes);
set_frame(p_coord.y * hframes + p_coord.x);
}
Vector2i Sprite2D::get_frame_coords() const {
return Vector2i(frame % hframes, frame / hframes);
}
void Sprite2D::set_vframes(int p_amount) {
ERR_FAIL_COND_MSG(p_amount < 1, "Amount of vframes cannot be smaller than 1.");
if (vframes == p_amount) {
return;
}
vframes = p_amount;
if (frame >= vframes * hframes) {
frame = 0;
}
queue_redraw();
item_rect_changed();
notify_property_list_changed();
}
int Sprite2D::get_vframes() const {
return vframes;
}
void Sprite2D::set_hframes(int p_amount) {
ERR_FAIL_COND_MSG(p_amount < 1, "Amount of hframes cannot be smaller than 1.");
if (hframes == p_amount) {
return;
}
if (vframes > 1) {
// Adjust the frame to fit new sheet dimensions.
int original_column = frame % hframes;
if (original_column >= p_amount) {
// Frame's column was dropped, reset.
frame = 0;
} else {
int original_row = frame / hframes;
frame = original_row * p_amount + original_column;
}
}
hframes = p_amount;
if (frame >= vframes * hframes) {
frame = 0;
}
queue_redraw();
item_rect_changed();
notify_property_list_changed();
}
int Sprite2D::get_hframes() const {
return hframes;
}
bool Sprite2D::is_pixel_opaque(const Point2 &p_point) const {
if (texture.is_null()) {
return false;
}
if (texture->get_size().width == 0 || texture->get_size().height == 0) {
return false;
}
Rect2 src_rect, dst_rect;
bool filter_clip_enabled;
_get_rects(src_rect, dst_rect, filter_clip_enabled);
dst_rect.size = dst_rect.size.abs();
if (!dst_rect.has_point(p_point)) {
return false;
}
Vector2 q = (p_point - dst_rect.position) / dst_rect.size;
if (hflip) {
q.x = 1.0f - q.x;
}
if (vflip) {
q.y = 1.0f - q.y;
}
q = q * src_rect.size + src_rect.position;
TextureRepeat repeat_mode = get_texture_repeat_in_tree();
bool is_repeat = repeat_mode == TEXTURE_REPEAT_ENABLED || repeat_mode == TEXTURE_REPEAT_MIRROR;
bool is_mirrored_repeat = repeat_mode == TEXTURE_REPEAT_MIRROR;
if (is_repeat) {
int mirror_x = 0;
int mirror_y = 0;
if (is_mirrored_repeat) {
mirror_x = (int)(q.x / texture->get_size().width);
mirror_y = (int)(q.y / texture->get_size().height);
}
q.x = Math::fmod(q.x, texture->get_size().width);
q.y = Math::fmod(q.y, texture->get_size().height);
if (mirror_x % 2 == 1) {
q.x = texture->get_size().width - q.x - 1;
}
if (mirror_y % 2 == 1) {
q.y = texture->get_size().height - q.y - 1;
}
} else {
q = q.min(texture->get_size() - Vector2(1, 1));
}
return texture->is_pixel_opaque((int)q.x, (int)q.y);
}
Rect2 Sprite2D::get_rect() const {
if (texture.is_null()) {
return Rect2(0, 0, 1, 1);
}
Size2i s;
if (region_enabled) {
s = region_rect.size;
} else {
s = texture->get_size();
}
s = s / Point2(hframes, vframes);
Point2 ofs = offset;
if (centered) {
ofs -= Size2(s) / 2;
}
if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
ofs = (ofs + Point2(0.5, 0.5)).floor();
}
if (s == Size2(0, 0)) {
s = Size2(1, 1);
}
return Rect2(ofs, s);
}
void Sprite2D::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "frame") {
p_property.hint = PROPERTY_HINT_RANGE;
p_property.hint_string = "0," + itos(vframes * hframes - 1) + ",1";
p_property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
if (p_property.name == "frame_coords") {
p_property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
if (!region_enabled && (p_property.name == "region_rect" || p_property.name == "region_filter_clip")) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
void Sprite2D::_texture_changed() {
// Changes to the texture need to trigger an update to make
// the editor redraw the sprite with the updated texture.
if (texture.is_valid()) {
queue_redraw();
}
}
void Sprite2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &Sprite2D::set_texture);
ClassDB::bind_method(D_METHOD("get_texture"), &Sprite2D::get_texture);
ClassDB::bind_method(D_METHOD("set_centered", "centered"), &Sprite2D::set_centered);
ClassDB::bind_method(D_METHOD("is_centered"), &Sprite2D::is_centered);
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &Sprite2D::set_offset);
ClassDB::bind_method(D_METHOD("get_offset"), &Sprite2D::get_offset);
ClassDB::bind_method(D_METHOD("set_flip_h", "flip_h"), &Sprite2D::set_flip_h);
ClassDB::bind_method(D_METHOD("is_flipped_h"), &Sprite2D::is_flipped_h);
ClassDB::bind_method(D_METHOD("set_flip_v", "flip_v"), &Sprite2D::set_flip_v);
ClassDB::bind_method(D_METHOD("is_flipped_v"), &Sprite2D::is_flipped_v);
ClassDB::bind_method(D_METHOD("set_region_enabled", "enabled"), &Sprite2D::set_region_enabled);
ClassDB::bind_method(D_METHOD("is_region_enabled"), &Sprite2D::is_region_enabled);
ClassDB::bind_method(D_METHOD("is_pixel_opaque", "pos"), &Sprite2D::is_pixel_opaque);
ClassDB::bind_method(D_METHOD("set_region_rect", "rect"), &Sprite2D::set_region_rect);
ClassDB::bind_method(D_METHOD("get_region_rect"), &Sprite2D::get_region_rect);
ClassDB::bind_method(D_METHOD("set_region_filter_clip_enabled", "enabled"), &Sprite2D::set_region_filter_clip_enabled);
ClassDB::bind_method(D_METHOD("is_region_filter_clip_enabled"), &Sprite2D::is_region_filter_clip_enabled);
ClassDB::bind_method(D_METHOD("set_frame", "frame"), &Sprite2D::set_frame);
ClassDB::bind_method(D_METHOD("get_frame"), &Sprite2D::get_frame);
ClassDB::bind_method(D_METHOD("set_frame_coords", "coords"), &Sprite2D::set_frame_coords);
ClassDB::bind_method(D_METHOD("get_frame_coords"), &Sprite2D::get_frame_coords);
ClassDB::bind_method(D_METHOD("set_vframes", "vframes"), &Sprite2D::set_vframes);
ClassDB::bind_method(D_METHOD("get_vframes"), &Sprite2D::get_vframes);
ClassDB::bind_method(D_METHOD("set_hframes", "hframes"), &Sprite2D::set_hframes);
ClassDB::bind_method(D_METHOD("get_hframes"), &Sprite2D::get_hframes);
ClassDB::bind_method(D_METHOD("get_rect"), &Sprite2D::get_rect);
ADD_SIGNAL(MethodInfo("frame_changed"));
ADD_SIGNAL(MethodInfo("texture_changed"));
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
ADD_GROUP("Offset", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "is_flipped_h");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "is_flipped_v");
ADD_GROUP("Animation", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes");
ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes");
ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords");
ADD_GROUP("Region", "region_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region_enabled", "is_region_enabled");
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect"), "set_region_rect", "get_region_rect");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_filter_clip_enabled"), "set_region_filter_clip_enabled", "is_region_filter_clip_enabled");
}
Sprite2D::Sprite2D() {
}
Sprite2D::~Sprite2D() {
}

128
engine/scene/2d/sprite_2d.h Normal file
View file

@ -0,0 +1,128 @@
/**************************************************************************/
/* sprite_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SPRITE_2D_H
#define SPRITE_2D_H
#include "scene/2d/node_2d.h"
#include "scene/resources/texture.h"
class Sprite2D : public Node2D {
GDCLASS(Sprite2D, Node2D);
Ref<Texture2D> texture;
Color specular_color;
real_t shininess = 0.0;
bool centered = true;
Point2 offset;
bool hflip = false;
bool vflip = false;
bool region_enabled = false;
Rect2 region_rect;
bool region_filter_clip_enabled = false;
int frame = 0;
int vframes = 1;
int hframes = 1;
void _get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip_enabled) const;
void _texture_changed();
protected:
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
public:
#ifdef TOOLS_ENABLED
virtual Dictionary _edit_get_state() const override;
virtual void _edit_set_state(const Dictionary &p_state) override;
virtual void _edit_set_pivot(const Point2 &p_pivot) override;
virtual Point2 _edit_get_pivot() const override;
virtual bool _edit_use_pivot() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
#endif
bool is_pixel_opaque(const Point2 &p_point) const;
void set_texture(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture() const;
void set_centered(bool p_center);
bool is_centered() const;
void set_offset(const Point2 &p_offset);
Point2 get_offset() const;
void set_flip_h(bool p_flip);
bool is_flipped_h() const;
void set_flip_v(bool p_flip);
bool is_flipped_v() const;
void set_region_enabled(bool p_enabled);
bool is_region_enabled() const;
void set_region_filter_clip_enabled(bool p_enabled);
bool is_region_filter_clip_enabled() const;
void set_region_rect(const Rect2 &p_region_rect);
Rect2 get_region_rect() const;
void set_frame(int p_frame);
int get_frame() const;
void set_frame_coords(const Vector2i &p_coord);
Vector2i get_frame_coords() const;
void set_vframes(int p_amount);
int get_vframes() const;
void set_hframes(int p_amount);
int get_hframes() const;
Rect2 get_rect() const;
virtual Rect2 get_anchorable_rect() const override;
Sprite2D();
~Sprite2D();
};
#endif // SPRITE_2D_H

View file

@ -0,0 +1,61 @@
/**************************************************************************/
/* tile_map.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
Rect2i TileMap::_get_used_rect_bind_compat_78328() {
return get_used_rect();
}
void TileMap::_set_quadrant_size_compat_81070(int p_quadrant_size) {
set_rendering_quadrant_size(p_quadrant_size);
}
int TileMap::_get_quadrant_size_compat_81070() const {
return get_rendering_quadrant_size();
}
TileMap::VisibilityMode TileMap::_get_collision_visibility_mode_bind_compat_87115() {
return get_collision_visibility_mode();
}
TileMap::VisibilityMode TileMap::_get_navigation_visibility_mode_bind_compat_87115() {
return get_navigation_visibility_mode();
}
void TileMap::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("get_used_rect"), &TileMap::_get_used_rect_bind_compat_78328);
ClassDB::bind_compatibility_method(D_METHOD("set_quadrant_size", "quadrant_size"), &TileMap::_set_quadrant_size_compat_81070);
ClassDB::bind_compatibility_method(D_METHOD("get_quadrant_size"), &TileMap::_get_quadrant_size_compat_81070);
ClassDB::bind_compatibility_method(D_METHOD("get_collision_visibility_mode"), &TileMap::_get_collision_visibility_mode_bind_compat_87115);
ClassDB::bind_compatibility_method(D_METHOD("get_navigation_visibility_mode"), &TileMap::_get_navigation_visibility_mode_bind_compat_87115);
}
#endif

1010
engine/scene/2d/tile_map.cpp Normal file

File diff suppressed because it is too large Load diff

242
engine/scene/2d/tile_map.h Normal file
View file

@ -0,0 +1,242 @@
/**************************************************************************/
/* tile_map.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TILE_MAP_H
#define TILE_MAP_H
#include "scene/2d/tile_map_layer.h"
#include "scene/property_list_helper.h"
#include "scene/resources/2d/tile_set.h"
class Control;
class TileMapLayer;
class TerrainConstraint;
enum TileMapDataFormat {
TILE_MAP_DATA_FORMAT_1 = 0,
TILE_MAP_DATA_FORMAT_2,
TILE_MAP_DATA_FORMAT_3,
TILE_MAP_DATA_FORMAT_MAX,
};
class TileMap : public Node2D {
GDCLASS(TileMap, Node2D)
public:
// Kept for compatibility, but should use TileMapLayer::VisibilityMode instead.
enum VisibilityMode {
VISIBILITY_MODE_DEFAULT,
VISIBILITY_MODE_FORCE_SHOW,
VISIBILITY_MODE_FORCE_HIDE,
};
private:
friend class TileSetPlugin;
// A compatibility enum to specify how is the data if formatted.
mutable TileMapDataFormat format = TileMapDataFormat::TILE_MAP_DATA_FORMAT_3;
// Properties.
Ref<TileSet> tile_set;
int rendering_quadrant_size = 16;
bool collision_animatable = false;
VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT;
VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT;
// Layers.
LocalVector<TileMapLayer *> layers;
static inline PropertyListHelper base_property_helper;
PropertyListHelper property_helper;
// Transforms for collision_animatable.
Transform2D last_valid_transform;
Transform2D new_transform;
void _tile_set_changed();
void _emit_changed();
// Kept for compatibility with TileMap. With TileMapLayers as individual nodes, the format is stored directly in the array.
void _set_tile_map_data_using_compatibility_format(int p_layer, TileMapDataFormat p_format, const Vector<int> &p_data);
Vector<int> _get_tile_map_data_using_compatibility_format(int p_layer) const;
void _set_layer_tile_data(int p_layer, const PackedInt32Array &p_data);
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 _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
Rect2i _get_used_rect_bind_compat_78328();
void _set_quadrant_size_compat_81070(int p_quadrant_size);
int _get_quadrant_size_compat_81070() const;
VisibilityMode _get_collision_visibility_mode_bind_compat_87115();
VisibilityMode _get_navigation_visibility_mode_bind_compat_87115();
static void _bind_compatibility_methods();
#endif
public:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
#endif
#ifndef DISABLE_DEPRECATED
void force_update(int p_layer);
#endif
void set_rendering_quadrant_size(int p_size);
int get_rendering_quadrant_size() const;
// Accessors.
void set_tileset(const Ref<TileSet> &p_tileset);
Ref<TileSet> get_tileset() const;
// Layers management.
int get_layers_count() const;
void add_layer(int p_to_pos);
void move_layer(int p_layer, int p_to_pos);
void remove_layer(int p_layer);
void set_layer_name(int p_layer, String p_name);
String get_layer_name(int p_layer) const;
void set_layer_enabled(int p_layer, bool p_visible);
bool is_layer_enabled(int p_layer) const;
void set_layer_modulate(int p_layer, Color p_modulate);
Color get_layer_modulate(int p_layer) const;
void set_layer_y_sort_enabled(int p_layer, bool p_enabled);
bool is_layer_y_sort_enabled(int p_layer) const;
void set_layer_y_sort_origin(int p_layer, int p_y_sort_origin);
int get_layer_y_sort_origin(int p_layer) const;
void set_layer_z_index(int p_layer, int p_z_index);
int get_layer_z_index(int p_layer) const;
void set_layer_navigation_enabled(int p_layer, bool p_enabled);
bool is_layer_navigation_enabled(int p_layer) const;
void set_layer_navigation_map(int p_layer, RID p_map);
RID get_layer_navigation_map(int p_layer) const;
void set_collision_animatable(bool p_collision_animatable);
bool is_collision_animatable() const;
// Debug visibility modes.
void set_collision_visibility_mode(VisibilityMode p_show_collision);
VisibilityMode get_collision_visibility_mode() const;
void set_navigation_visibility_mode(VisibilityMode p_show_navigation);
VisibilityMode get_navigation_visibility_mode() const;
// Cells accessors.
void set_cell(int p_layer, const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0);
void erase_cell(int p_layer, const Vector2i &p_coords);
int get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
Vector2i get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
int get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
// Helper method to make accessing the data easier.
TileData *get_cell_tile_data(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
// Patterns.
Ref<TileMapPattern> get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array);
Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern);
void set_pattern(int p_layer, const Vector2i &p_position, const Ref<TileMapPattern> p_pattern);
// Terrains (Not exposed).
HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints);
HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true);
HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true);
HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true);
// Terrains (exposed).
void set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true);
void set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true);
// Not exposed to users.
TileMapCell get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
int get_effective_quadrant_size(int p_layer) const;
virtual void set_y_sort_enabled(bool p_enable) override;
Vector2 map_to_local(const Vector2i &p_pos) const;
Vector2i local_to_map(const Vector2 &p_pos) const;
bool is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const;
Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const;
TypedArray<Vector2i> get_used_cells(int p_layer) const;
TypedArray<Vector2i> get_used_cells_by_id(int p_layer, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) const;
Rect2i get_used_rect() const;
// Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems.
virtual void set_light_mask(int p_light_mask) override;
virtual void set_self_modulate(const Color &p_self_modulate) override;
virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override;
virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override;
// For finding tiles from collision.
Vector2i get_coords_for_body_rid(RID p_physics_body);
// For getting their layers as well.
int get_layer_for_body_rid(RID p_physics_body);
// Fixing and clearing methods.
void fix_invalid_tiles();
#ifdef TOOLS_ENABLED
// Moving layers outside of TileMap.
TileMapLayer *duplicate_layer_from_internal(int p_layer);
#endif // TOOLS_ENABLED
// Clears tiles from a given layer.
void clear_layer(int p_layer);
void clear();
// Force a TileMap update.
void update_internals();
void notify_runtime_tile_data_update(int p_layer = -1);
// Helpers?
TypedArray<Vector2i> get_surrounding_cells(const Vector2i &p_coords);
// Virtual function to modify the TileData at runtime.
GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i);
GDVIRTUAL3(_tile_data_runtime_update, int, Vector2i, TileData *);
// Configuration warnings.
PackedStringArray get_configuration_warnings() const override;
TileMap();
};
VARIANT_ENUM_CAST(TileMap::VisibilityMode);
#endif // TILE_MAP_H

File diff suppressed because it is too large Load diff

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