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

View file

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

View file

@ -0,0 +1,124 @@
/**************************************************************************/
/* capsule_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 "capsule_shape_2d.h"
#include "core/math/geometry_2d.h"
#include "servers/physics_server_2d.h"
#include "servers/rendering_server.h"
Vector<Vector2> CapsuleShape2D::_get_points() const {
Vector<Vector2> points;
const real_t turn_step = Math_TAU / 24.0;
for (int i = 0; i < 24; i++) {
Vector2 ofs = Vector2(0, (i > 6 && i <= 18) ? -height * 0.5 + radius : height * 0.5 - radius);
points.push_back(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * radius + ofs);
if (i == 6 || i == 18) {
points.push_back(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * radius - ofs);
}
}
return points;
}
bool CapsuleShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return Geometry2D::is_point_in_polygon(p_point, _get_points());
}
void CapsuleShape2D::_update_shape() {
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), Vector2(radius, height));
emit_changed();
}
void CapsuleShape2D::set_radius(real_t p_radius) {
ERR_FAIL_COND_MSG(p_radius < 0, "CapsuleShape2D radius cannot be negative.");
radius = p_radius;
if (radius > height * 0.5) {
height = radius * 2.0;
}
_update_shape();
}
real_t CapsuleShape2D::get_radius() const {
return radius;
}
void CapsuleShape2D::set_height(real_t p_height) {
ERR_FAIL_COND_MSG(p_height < 0, "CapsuleShape2D height cannot be negative.");
height = p_height;
if (radius > height * 0.5) {
radius = height * 0.5;
}
_update_shape();
}
real_t CapsuleShape2D::get_height() const {
return height;
}
void CapsuleShape2D::draw(const RID &p_to_rid, const Color &p_color) {
Vector<Vector2> points = _get_points();
Vector<Color> col = { p_color };
RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col);
if (is_collision_outline_enabled()) {
points.push_back(points[0]);
col = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col);
}
}
Rect2 CapsuleShape2D::get_rect() const {
const Vector2 half_size = Vector2(radius, height * 0.5);
return Rect2(-half_size, half_size * 2.0);
}
real_t CapsuleShape2D::get_enclosing_radius() const {
return height * 0.5;
}
void CapsuleShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CapsuleShape2D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &CapsuleShape2D::get_radius);
ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleShape2D::set_height);
ClassDB::bind_method(D_METHOD("get_height"), &CapsuleShape2D::get_height);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_radius", "get_radius");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_height", "get_height");
ADD_LINKED_PROPERTY("radius", "height");
ADD_LINKED_PROPERTY("height", "radius");
}
CapsuleShape2D::CapsuleShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->capsule_shape_create()) {
_update_shape();
}

View file

@ -0,0 +1,64 @@
/**************************************************************************/
/* capsule_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 CAPSULE_SHAPE_2D_H
#define CAPSULE_SHAPE_2D_H
#include "scene/resources/2d/shape_2d.h"
class CapsuleShape2D : public Shape2D {
GDCLASS(CapsuleShape2D, Shape2D);
real_t height = 30.0;
real_t radius = 10.0;
void _update_shape();
Vector<Vector2> _get_points() const;
protected:
static void _bind_methods();
public:
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
void set_height(real_t p_height);
real_t get_height() const;
void set_radius(real_t p_radius);
real_t get_radius() const;
virtual void draw(const RID &p_to_rid, const Color &p_color) override;
virtual Rect2 get_rect() const override;
virtual real_t get_enclosing_radius() const override;
CapsuleShape2D();
};
#endif // CAPSULE_SHAPE_2D_H

View file

@ -0,0 +1,95 @@
/**************************************************************************/
/* circle_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 "circle_shape_2d.h"
#include "servers/physics_server_2d.h"
#include "servers/rendering_server.h"
bool CircleShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return p_point.length() < get_radius() + p_tolerance;
}
void CircleShape2D::_update_shape() {
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), radius);
emit_changed();
}
void CircleShape2D::set_radius(real_t p_radius) {
ERR_FAIL_COND_MSG(p_radius < 0, "CircleShape2D radius cannot be negative.");
radius = p_radius;
_update_shape();
}
real_t CircleShape2D::get_radius() const {
return radius;
}
void CircleShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CircleShape2D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &CircleShape2D::get_radius);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_radius", "get_radius");
}
Rect2 CircleShape2D::get_rect() const {
Rect2 rect;
rect.position = -Point2(get_radius(), get_radius());
rect.size = Point2(get_radius(), get_radius()) * 2.0;
return rect;
}
real_t CircleShape2D::get_enclosing_radius() const {
return radius;
}
void CircleShape2D::draw(const RID &p_to_rid, const Color &p_color) {
Vector<Vector2> points;
points.resize(24);
const real_t turn_step = Math_TAU / 24.0;
for (int i = 0; i < 24; i++) {
points.write[i] = Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * get_radius();
}
Vector<Color> col = { p_color };
RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col);
if (is_collision_outline_enabled()) {
points.push_back(points[0]);
col = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col);
}
}
CircleShape2D::CircleShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->circle_shape_create()) {
_update_shape();
}

View file

@ -0,0 +1,58 @@
/**************************************************************************/
/* circle_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 CIRCLE_SHAPE_2D_H
#define CIRCLE_SHAPE_2D_H
#include "scene/resources/2d/shape_2d.h"
class CircleShape2D : public Shape2D {
GDCLASS(CircleShape2D, Shape2D);
real_t radius = 10.0;
void _update_shape();
protected:
static void _bind_methods();
public:
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
void set_radius(real_t p_radius);
real_t get_radius() const;
virtual void draw(const RID &p_to_rid, const Color &p_color) override;
virtual Rect2 get_rect() const override;
virtual real_t get_enclosing_radius() const override;
CircleShape2D();
};
#endif // CIRCLE_SHAPE_2D_H

View file

@ -0,0 +1,119 @@
/**************************************************************************/
/* concave_polygon_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 "concave_polygon_shape_2d.h"
#include "core/math/geometry_2d.h"
#include "servers/physics_server_2d.h"
#include "servers/rendering_server.h"
bool ConcavePolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
Vector<Vector2> s = get_segments();
int len = s.size();
if (len == 0 || (len % 2) == 1) {
return false;
}
const Vector2 *r = s.ptr();
for (int i = 0; i < len; i += 2) {
Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, &r[i]);
if (p_point.distance_to(closest) < p_tolerance) {
return true;
}
}
return false;
}
void ConcavePolygonShape2D::set_segments(const Vector<Vector2> &p_segments) {
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), p_segments);
emit_changed();
}
Vector<Vector2> ConcavePolygonShape2D::get_segments() const {
return PhysicsServer2D::get_singleton()->shape_get_data(get_rid());
}
void ConcavePolygonShape2D::draw(const RID &p_to_rid, const Color &p_color) {
Vector<Vector2> s = get_segments();
int len = s.size();
if (len == 0 || (len % 2) == 1) {
return;
}
const Vector2 *r = s.ptr();
for (int i = 0; i < len; i += 2) {
RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, r[i], r[i + 1], p_color, 2);
}
}
Rect2 ConcavePolygonShape2D::get_rect() const {
Vector<Vector2> s = get_segments();
int len = s.size();
if (len == 0) {
return Rect2();
}
Rect2 rect;
const Vector2 *r = s.ptr();
for (int i = 0; i < len; i++) {
if (i == 0) {
rect.position = r[i];
} else {
rect.expand_to(r[i]);
}
}
return rect;
}
real_t ConcavePolygonShape2D::get_enclosing_radius() const {
Vector<Vector2> data = get_segments();
const Vector2 *read = data.ptr();
real_t r = 0.0;
for (int i(0); i < data.size(); i++) {
r = MAX(read[i].length_squared(), r);
}
return Math::sqrt(r);
}
void ConcavePolygonShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_segments", "segments"), &ConcavePolygonShape2D::set_segments);
ClassDB::bind_method(D_METHOD("get_segments"), &ConcavePolygonShape2D::get_segments);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "segments"), "set_segments", "get_segments");
}
ConcavePolygonShape2D::ConcavePolygonShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->concave_polygon_shape_create()) {
Vector<Vector2> empty;
set_segments(empty);
}

View file

@ -0,0 +1,55 @@
/**************************************************************************/
/* concave_polygon_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 CONCAVE_POLYGON_SHAPE_2D_H
#define CONCAVE_POLYGON_SHAPE_2D_H
#include "scene/resources/2d/shape_2d.h"
class ConcavePolygonShape2D : public Shape2D {
GDCLASS(ConcavePolygonShape2D, Shape2D);
protected:
static void _bind_methods();
public:
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
void set_segments(const Vector<Vector2> &p_segments);
Vector<Vector2> get_segments() const;
virtual void draw(const RID &p_to_rid, const Color &p_color) override;
virtual Rect2 get_rect() const override;
virtual real_t get_enclosing_radius() const override;
ConcavePolygonShape2D();
};
#endif // CONCAVE_POLYGON_SHAPE_2D_H

View file

@ -0,0 +1,143 @@
/**************************************************************************/
/* convex_polygon_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 "convex_polygon_shape_2d.h"
#include "core/math/geometry_2d.h"
#include "servers/physics_server_2d.h"
#include "servers/rendering_server.h"
bool ConvexPolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return Geometry2D::is_point_in_polygon(p_point, points);
}
#ifdef DEBUG_ENABLED
// Check if point p3 is to the left of [p1, p2] segment or on it.
bool left_test(const Vector2 &p1, const Vector2 &p2, const Vector2 &p3) {
Vector2 p12 = p2 - p1;
Vector2 p13 = p3 - p1;
// If the value of the cross product is positive or zero; p3 is to the left or on the segment, respectively.
return p12.cross(p13) >= 0;
}
bool is_convex(const Vector<Vector2> &p_points) {
// Pre-condition: Polygon is in counter-clockwise order.
int polygon_size = p_points.size();
for (int i = 0; i < polygon_size && polygon_size > 3; i++) {
int j = (i + 1) % polygon_size;
int k = (j + 1) % polygon_size;
// If any consecutive three points fail left-test, then there is a concavity.
if (!left_test(p_points[i], p_points[j], p_points[k])) {
return false;
}
}
return true;
}
#endif
void ConvexPolygonShape2D::_update_shape() {
Vector<Vector2> final_points = points;
if (Geometry2D::is_polygon_clockwise(final_points)) { //needs to be counter clockwise
final_points.reverse();
}
#ifdef DEBUG_ENABLED
if (!is_convex(final_points)) {
WARN_PRINT("Concave polygon is assigned to ConvexPolygonShape2D.");
}
#endif
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), final_points);
emit_changed();
}
void ConvexPolygonShape2D::set_point_cloud(const Vector<Vector2> &p_points) {
Vector<Point2> hull = Geometry2D::convex_hull(p_points);
ERR_FAIL_COND(hull.size() < 3);
set_points(hull);
}
void ConvexPolygonShape2D::set_points(const Vector<Vector2> &p_points) {
points = p_points;
_update_shape();
}
Vector<Vector2> ConvexPolygonShape2D::get_points() const {
return points;
}
void ConvexPolygonShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_point_cloud", "point_cloud"), &ConvexPolygonShape2D::set_point_cloud);
ClassDB::bind_method(D_METHOD("set_points", "points"), &ConvexPolygonShape2D::set_points);
ClassDB::bind_method(D_METHOD("get_points"), &ConvexPolygonShape2D::get_points);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "points"), "set_points", "get_points");
}
void ConvexPolygonShape2D::draw(const RID &p_to_rid, const Color &p_color) {
if (points.size() < 3) {
return;
}
Vector<Color> col = { p_color };
RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col);
if (is_collision_outline_enabled()) {
col = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col);
// Draw the last segment.
RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, points[points.size() - 1], points[0], Color(p_color, 1.0));
}
}
Rect2 ConvexPolygonShape2D::get_rect() const {
Rect2 rect;
for (int i = 0; i < points.size(); i++) {
if (i == 0) {
rect.position = points[i];
} else {
rect.expand_to(points[i]);
}
}
return rect;
}
real_t ConvexPolygonShape2D::get_enclosing_radius() const {
real_t r = 0.0;
for (int i(0); i < get_points().size(); i++) {
r = MAX(get_points()[i].length_squared(), r);
}
return Math::sqrt(r);
}
ConvexPolygonShape2D::ConvexPolygonShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->convex_polygon_shape_create()) {
}

View file

@ -0,0 +1,59 @@
/**************************************************************************/
/* convex_polygon_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 CONVEX_POLYGON_SHAPE_2D_H
#define CONVEX_POLYGON_SHAPE_2D_H
#include "scene/resources/2d/shape_2d.h"
class ConvexPolygonShape2D : public Shape2D {
GDCLASS(ConvexPolygonShape2D, Shape2D);
Vector<Vector2> points;
void _update_shape();
protected:
static void _bind_methods();
public:
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
void set_point_cloud(const Vector<Vector2> &p_points);
void set_points(const Vector<Vector2> &p_points);
Vector<Vector2> get_points() const;
virtual void draw(const RID &p_to_rid, const Color &p_color) override;
virtual Rect2 get_rect() const override;
virtual real_t get_enclosing_radius() const override;
ConvexPolygonShape2D();
};
#endif // CONVEX_POLYGON_SHAPE_2D_H

View file

@ -0,0 +1,304 @@
/**************************************************************************/
/* navigation_mesh_source_geometry_data_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_mesh_source_geometry_data_2d.h"
#include "scene/resources/mesh.h"
void NavigationMeshSourceGeometryData2D::clear() {
RWLockWrite write_lock(geometry_rwlock);
traversable_outlines.clear();
obstruction_outlines.clear();
_projected_obstructions.clear();
}
bool NavigationMeshSourceGeometryData2D::has_data() {
RWLockRead read_lock(geometry_rwlock);
return traversable_outlines.size();
};
void NavigationMeshSourceGeometryData2D::clear_projected_obstructions() {
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.clear();
}
void NavigationMeshSourceGeometryData2D::_set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines) {
RWLockWrite write_lock(geometry_rwlock);
traversable_outlines = p_traversable_outlines;
}
void NavigationMeshSourceGeometryData2D::_set_obstruction_outlines(const Vector<Vector<Vector2>> &p_obstruction_outlines) {
RWLockWrite write_lock(geometry_rwlock);
obstruction_outlines = p_obstruction_outlines;
}
const Vector<Vector<Vector2>> &NavigationMeshSourceGeometryData2D::_get_traversable_outlines() const {
RWLockRead read_lock(geometry_rwlock);
return traversable_outlines;
}
const Vector<Vector<Vector2>> &NavigationMeshSourceGeometryData2D::_get_obstruction_outlines() const {
RWLockRead read_lock(geometry_rwlock);
return obstruction_outlines;
}
void NavigationMeshSourceGeometryData2D::_add_traversable_outline(const Vector<Vector2> &p_shape_outline) {
if (p_shape_outline.size() > 1) {
RWLockWrite write_lock(geometry_rwlock);
traversable_outlines.push_back(p_shape_outline);
}
}
void NavigationMeshSourceGeometryData2D::_add_obstruction_outline(const Vector<Vector2> &p_shape_outline) {
if (p_shape_outline.size() > 1) {
RWLockWrite write_lock(geometry_rwlock);
obstruction_outlines.push_back(p_shape_outline);
}
}
void NavigationMeshSourceGeometryData2D::set_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines) {
RWLockWrite write_lock(geometry_rwlock);
traversable_outlines.resize(p_traversable_outlines.size());
for (int i = 0; i < p_traversable_outlines.size(); i++) {
traversable_outlines.write[i] = p_traversable_outlines[i];
}
}
TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_traversable_outlines() const {
RWLockRead read_lock(geometry_rwlock);
TypedArray<Vector<Vector2>> typed_array_traversable_outlines;
typed_array_traversable_outlines.resize(traversable_outlines.size());
for (int i = 0; i < typed_array_traversable_outlines.size(); i++) {
typed_array_traversable_outlines[i] = traversable_outlines[i];
}
return typed_array_traversable_outlines;
}
void NavigationMeshSourceGeometryData2D::set_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines) {
RWLockWrite write_lock(geometry_rwlock);
obstruction_outlines.resize(p_obstruction_outlines.size());
for (int i = 0; i < p_obstruction_outlines.size(); i++) {
obstruction_outlines.write[i] = p_obstruction_outlines[i];
}
}
TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_obstruction_outlines() const {
RWLockRead read_lock(geometry_rwlock);
TypedArray<Vector<Vector2>> typed_array_obstruction_outlines;
typed_array_obstruction_outlines.resize(obstruction_outlines.size());
for (int i = 0; i < typed_array_obstruction_outlines.size(); i++) {
typed_array_obstruction_outlines[i] = obstruction_outlines[i];
}
return typed_array_obstruction_outlines;
}
void NavigationMeshSourceGeometryData2D::append_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines) {
RWLockWrite write_lock(geometry_rwlock);
int traversable_outlines_size = traversable_outlines.size();
traversable_outlines.resize(traversable_outlines_size + p_traversable_outlines.size());
for (int i = traversable_outlines_size; i < p_traversable_outlines.size(); i++) {
traversable_outlines.write[i] = p_traversable_outlines[i];
}
}
void NavigationMeshSourceGeometryData2D::append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines) {
RWLockWrite write_lock(geometry_rwlock);
int obstruction_outlines_size = obstruction_outlines.size();
obstruction_outlines.resize(obstruction_outlines_size + p_obstruction_outlines.size());
for (int i = obstruction_outlines_size; i < p_obstruction_outlines.size(); i++) {
obstruction_outlines.write[i] = p_obstruction_outlines[i];
}
}
void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVector2Array &p_shape_outline) {
if (p_shape_outline.size() > 1) {
RWLockWrite write_lock(geometry_rwlock);
Vector<Vector2> traversable_outline;
traversable_outline.resize(p_shape_outline.size());
for (int i = 0; i < p_shape_outline.size(); i++) {
traversable_outline.write[i] = p_shape_outline[i];
}
traversable_outlines.push_back(traversable_outline);
}
}
void NavigationMeshSourceGeometryData2D::add_obstruction_outline(const PackedVector2Array &p_shape_outline) {
if (p_shape_outline.size() > 1) {
RWLockWrite write_lock(geometry_rwlock);
Vector<Vector2> obstruction_outline;
obstruction_outline.resize(p_shape_outline.size());
for (int i = 0; i < p_shape_outline.size(); i++) {
obstruction_outline.write[i] = p_shape_outline[i];
}
obstruction_outlines.push_back(obstruction_outline);
}
}
void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry) {
ERR_FAIL_NULL(p_other_geometry);
Vector<Vector<Vector2>> other_traversable_outlines;
Vector<Vector<Vector2>> other_obstruction_outlines;
Vector<ProjectedObstruction> other_projected_obstructions;
p_other_geometry->get_data(other_traversable_outlines, other_obstruction_outlines, other_projected_obstructions);
RWLockWrite write_lock(geometry_rwlock);
traversable_outlines.append_array(other_traversable_outlines);
obstruction_outlines.append_array(other_obstruction_outlines);
_projected_obstructions.append_array(other_projected_obstructions);
}
void NavigationMeshSourceGeometryData2D::add_projected_obstruction(const Vector<Vector2> &p_vertices, bool p_carve) {
ERR_FAIL_COND(p_vertices.size() < 2);
ProjectedObstruction projected_obstruction;
projected_obstruction.vertices.resize(p_vertices.size() * 2);
projected_obstruction.carve = p_carve;
float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw();
int vertex_index = 0;
for (const Vector2 &vertex : p_vertices) {
obstruction_vertices_ptrw[vertex_index++] = vertex.x;
obstruction_vertices_ptrw[vertex_index++] = vertex.y;
}
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.push_back(projected_obstruction);
}
void NavigationMeshSourceGeometryData2D::set_projected_obstructions(const Array &p_array) {
clear_projected_obstructions();
for (int i = 0; i < p_array.size(); i++) {
Dictionary data = p_array[i];
ERR_FAIL_COND(!data.has("version"));
uint32_t po_version = data["version"];
if (po_version == 1) {
ERR_FAIL_COND(!data.has("vertices"));
ERR_FAIL_COND(!data.has("carve"));
}
ProjectedObstruction projected_obstruction;
projected_obstruction.vertices = Vector<float>(data["vertices"]);
projected_obstruction.carve = data["carve"];
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.push_back(projected_obstruction);
}
}
Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> NavigationMeshSourceGeometryData2D::_get_projected_obstructions() const {
RWLockRead read_lock(geometry_rwlock);
return _projected_obstructions;
}
Array NavigationMeshSourceGeometryData2D::get_projected_obstructions() const {
RWLockRead read_lock(geometry_rwlock);
Array ret;
ret.resize(_projected_obstructions.size());
for (int i = 0; i < _projected_obstructions.size(); i++) {
const ProjectedObstruction &projected_obstruction = _projected_obstructions[i];
Dictionary data;
data["version"] = (int)ProjectedObstruction::VERSION;
data["vertices"] = projected_obstruction.vertices;
data["carve"] = projected_obstruction.carve;
ret[i] = data;
}
return ret;
}
bool NavigationMeshSourceGeometryData2D::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "projected_obstructions") {
set_projected_obstructions(p_value);
return true;
}
return false;
}
bool NavigationMeshSourceGeometryData2D::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "projected_obstructions") {
r_ret = get_projected_obstructions();
return true;
}
return false;
}
void NavigationMeshSourceGeometryData2D::set_data(const Vector<Vector<Vector2>> &p_traversable_outlines, const Vector<Vector<Vector2>> &p_obstruction_outlines, Vector<ProjectedObstruction> &p_projected_obstructions) {
RWLockWrite write_lock(geometry_rwlock);
traversable_outlines = p_traversable_outlines;
obstruction_outlines = p_obstruction_outlines;
_projected_obstructions = p_projected_obstructions;
}
void NavigationMeshSourceGeometryData2D::get_data(Vector<Vector<Vector2>> &r_traversable_outlines, Vector<Vector<Vector2>> &r_obstruction_outlines, Vector<ProjectedObstruction> &r_projected_obstructions) {
RWLockRead read_lock(geometry_rwlock);
r_traversable_outlines = traversable_outlines;
r_obstruction_outlines = obstruction_outlines;
r_projected_obstructions = _projected_obstructions;
}
void NavigationMeshSourceGeometryData2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData2D::clear);
ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData2D::has_data);
ClassDB::bind_method(D_METHOD("set_traversable_outlines", "traversable_outlines"), &NavigationMeshSourceGeometryData2D::set_traversable_outlines);
ClassDB::bind_method(D_METHOD("get_traversable_outlines"), &NavigationMeshSourceGeometryData2D::get_traversable_outlines);
ClassDB::bind_method(D_METHOD("set_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::set_obstruction_outlines);
ClassDB::bind_method(D_METHOD("get_obstruction_outlines"), &NavigationMeshSourceGeometryData2D::get_obstruction_outlines);
ClassDB::bind_method(D_METHOD("append_traversable_outlines", "traversable_outlines"), &NavigationMeshSourceGeometryData2D::append_traversable_outlines);
ClassDB::bind_method(D_METHOD("append_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::append_obstruction_outlines);
ClassDB::bind_method(D_METHOD("add_traversable_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_traversable_outline);
ClassDB::bind_method(D_METHOD("add_obstruction_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_obstruction_outline);
ClassDB::bind_method(D_METHOD("merge", "other_geometry"), &NavigationMeshSourceGeometryData2D::merge);
ClassDB::bind_method(D_METHOD("add_projected_obstruction", "vertices", "carve"), &NavigationMeshSourceGeometryData2D::add_projected_obstruction);
ClassDB::bind_method(D_METHOD("clear_projected_obstructions"), &NavigationMeshSourceGeometryData2D::clear_projected_obstructions);
ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData2D::set_projected_obstructions);
ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData2D::get_projected_obstructions);
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "traversable_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_traversable_outlines", "get_traversable_outlines");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "obstruction_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_obstruction_outlines", "get_obstruction_outlines");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions");
}

View file

@ -0,0 +1,110 @@
/**************************************************************************/
/* navigation_mesh_source_geometry_data_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_MESH_SOURCE_GEOMETRY_DATA_2D_H
#define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H
#include "core/os/rw_lock.h"
#include "scene/2d/node_2d.h"
#include "scene/resources/2d/navigation_polygon.h"
class NavigationMeshSourceGeometryData2D : public Resource {
GDCLASS(NavigationMeshSourceGeometryData2D, Resource);
RWLock geometry_rwlock;
Vector<Vector<Vector2>> traversable_outlines;
Vector<Vector<Vector2>> obstruction_outlines;
public:
struct ProjectedObstruction;
private:
Vector<ProjectedObstruction> _projected_obstructions;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
static void _bind_methods();
public:
struct ProjectedObstruction {
static inline uint32_t VERSION = 1; // Increase when format changes so we can detect outdated formats and provide compatibility.
Vector<float> vertices;
bool carve = false;
};
void _set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines);
const Vector<Vector<Vector2>> &_get_traversable_outlines() const;
void _set_obstruction_outlines(const Vector<Vector<Vector2>> &p_obstruction_outlines);
const Vector<Vector<Vector2>> &_get_obstruction_outlines() const;
void _add_traversable_outline(const Vector<Vector2> &p_shape_outline);
void _add_obstruction_outline(const Vector<Vector2> &p_shape_outline);
// kept root node transform here on the geometry data
// if we add this transform to all exposed functions we need to break comp on all functions later
// when navmesh changes from global transform to relative to navregion
// but if it stays here we can just remove it and change the internal functions only
Transform2D root_node_transform;
void set_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines);
TypedArray<Vector<Vector2>> get_traversable_outlines() const;
void set_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines);
TypedArray<Vector<Vector2>> get_obstruction_outlines() const;
void append_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines);
void append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines);
void add_traversable_outline(const PackedVector2Array &p_shape_outline);
void add_obstruction_outline(const PackedVector2Array &p_shape_outline);
bool has_data();
void clear();
void clear_projected_obstructions();
void add_projected_obstruction(const Vector<Vector2> &p_vertices, bool p_carve);
Vector<ProjectedObstruction> _get_projected_obstructions() const;
void set_projected_obstructions(const Array &p_array);
Array get_projected_obstructions() const;
void merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry);
void set_data(const Vector<Vector<Vector2>> &p_traversable_outlines, const Vector<Vector<Vector2>> &p_obstruction_outlines, Vector<ProjectedObstruction> &p_projected_obstructions);
void get_data(Vector<Vector<Vector2>> &r_traversable_outlines, Vector<Vector<Vector2>> &r_obstruction_outlines, Vector<ProjectedObstruction> &r_projected_obstructions);
NavigationMeshSourceGeometryData2D() {}
~NavigationMeshSourceGeometryData2D() { clear(); }
};
#endif // NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H

View file

@ -0,0 +1,587 @@
/**************************************************************************/
/* navigation_polygon.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_polygon.h"
#include "core/math/geometry_2d.h"
#include "core/os/mutex.h"
#include "servers/navigation_server_2d.h"
#include "thirdparty/misc/polypartition.h"
#ifdef TOOLS_ENABLED
Rect2 NavigationPolygon::_edit_get_rect() const {
RWLockRead read_lock(rwlock);
if (rect_cache_dirty) {
item_rect = Rect2();
bool first = true;
for (int i = 0; i < outlines.size(); i++) {
const Vector<Vector2> &outline = outlines[i];
const int outline_size = outline.size();
if (outline_size < 3) {
continue;
}
const Vector2 *p = outline.ptr();
for (int j = 0; j < outline_size; j++) {
if (first) {
item_rect = Rect2(p[j], Vector2(0, 0));
first = false;
} else {
item_rect.expand_to(p[j]);
}
}
}
rect_cache_dirty = false;
}
return item_rect;
}
bool NavigationPolygon::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
RWLockRead read_lock(rwlock);
for (int i = 0; i < outlines.size(); i++) {
const Vector<Vector2> &outline = outlines[i];
const int outline_size = outline.size();
if (outline_size < 3) {
continue;
}
if (Geometry2D::is_point_in_polygon(p_point, Variant(outline))) {
return true;
}
}
return false;
}
#endif
void NavigationPolygon::set_vertices(const Vector<Vector2> &p_vertices) {
RWLockWrite write_lock(rwlock);
{
MutexLock lock(navigation_mesh_generation);
navigation_mesh.unref();
}
vertices = p_vertices;
rect_cache_dirty = true;
}
Vector<Vector2> NavigationPolygon::get_vertices() const {
RWLockRead read_lock(rwlock);
return vertices;
}
void NavigationPolygon::_set_polygons(const TypedArray<Vector<int32_t>> &p_array) {
RWLockWrite write_lock(rwlock);
{
MutexLock lock(navigation_mesh_generation);
navigation_mesh.unref();
}
polygons.resize(p_array.size());
for (int i = 0; i < p_array.size(); i++) {
polygons.write[i].indices = p_array[i];
}
}
TypedArray<Vector<int32_t>> NavigationPolygon::_get_polygons() const {
RWLockRead read_lock(rwlock);
TypedArray<Vector<int32_t>> ret;
ret.resize(polygons.size());
for (int i = 0; i < ret.size(); i++) {
ret[i] = polygons[i].indices;
}
return ret;
}
void NavigationPolygon::_set_outlines(const TypedArray<Vector<Vector2>> &p_array) {
RWLockWrite write_lock(rwlock);
outlines.resize(p_array.size());
for (int i = 0; i < p_array.size(); i++) {
outlines.write[i] = p_array[i];
}
rect_cache_dirty = true;
}
TypedArray<Vector<Vector2>> NavigationPolygon::_get_outlines() const {
RWLockRead read_lock(rwlock);
TypedArray<Vector<Vector2>> ret;
ret.resize(outlines.size());
for (int i = 0; i < ret.size(); i++) {
ret[i] = outlines[i];
}
return ret;
}
void NavigationPolygon::add_polygon(const Vector<int> &p_polygon) {
RWLockWrite write_lock(rwlock);
Polygon polygon;
polygon.indices = p_polygon;
polygons.push_back(polygon);
{
MutexLock lock(navigation_mesh_generation);
navigation_mesh.unref();
}
}
void NavigationPolygon::add_outline_at_index(const Vector<Vector2> &p_outline, int p_index) {
RWLockWrite write_lock(rwlock);
outlines.insert(p_index, p_outline);
rect_cache_dirty = true;
}
int NavigationPolygon::get_polygon_count() const {
RWLockRead read_lock(rwlock);
return polygons.size();
}
Vector<int> NavigationPolygon::get_polygon(int p_idx) {
RWLockRead read_lock(rwlock);
ERR_FAIL_INDEX_V(p_idx, polygons.size(), Vector<int>());
return polygons[p_idx].indices;
}
void NavigationPolygon::clear_polygons() {
RWLockWrite write_lock(rwlock);
polygons.clear();
{
MutexLock lock(navigation_mesh_generation);
navigation_mesh.unref();
}
}
void NavigationPolygon::clear() {
RWLockWrite write_lock(rwlock);
polygons.clear();
vertices.clear();
{
MutexLock lock(navigation_mesh_generation);
navigation_mesh.unref();
}
}
void NavigationPolygon::set_data(const Vector<Vector2> &p_vertices, const Vector<Vector<int>> &p_polygons) {
RWLockWrite write_lock(rwlock);
vertices = p_vertices;
polygons.resize(p_polygons.size());
for (int i = 0; i < p_polygons.size(); i++) {
polygons.write[i].indices = p_polygons[i];
}
{
MutexLock lock(navigation_mesh_generation);
navigation_mesh.unref();
}
}
void NavigationPolygon::get_data(Vector<Vector2> &r_vertices, Vector<Vector<int>> &r_polygons) {
RWLockRead read_lock(rwlock);
r_vertices = vertices;
r_polygons.resize(polygons.size());
for (int i = 0; i < polygons.size(); i++) {
r_polygons.write[i] = polygons[i].indices;
}
}
Ref<NavigationMesh> NavigationPolygon::get_navigation_mesh() {
MutexLock lock(navigation_mesh_generation);
if (navigation_mesh.is_null()) {
navigation_mesh.instantiate();
Vector<Vector3> verts;
Vector<Vector<int>> polys;
{
verts.resize(get_vertices().size());
Vector3 *w = verts.ptrw();
const Vector2 *r = get_vertices().ptr();
for (int i(0); i < get_vertices().size(); i++) {
w[i] = Vector3(r[i].x, 0.0, r[i].y);
}
}
for (int i(0); i < get_polygon_count(); i++) {
polys.push_back(get_polygon(i));
}
navigation_mesh->set_data(verts, polys);
navigation_mesh->set_cell_size(cell_size); // Needed to not fail the cell size check on the server
}
return navigation_mesh;
}
void NavigationPolygon::add_outline(const Vector<Vector2> &p_outline) {
RWLockWrite write_lock(rwlock);
outlines.push_back(p_outline);
rect_cache_dirty = true;
}
int NavigationPolygon::get_outline_count() const {
RWLockRead read_lock(rwlock);
return outlines.size();
}
void NavigationPolygon::set_outline(int p_idx, const Vector<Vector2> &p_outline) {
RWLockWrite write_lock(rwlock);
ERR_FAIL_INDEX(p_idx, outlines.size());
outlines.write[p_idx] = p_outline;
rect_cache_dirty = true;
}
void NavigationPolygon::remove_outline(int p_idx) {
RWLockWrite write_lock(rwlock);
ERR_FAIL_INDEX(p_idx, outlines.size());
outlines.remove_at(p_idx);
rect_cache_dirty = true;
}
Vector<Vector2> NavigationPolygon::get_outline(int p_idx) const {
RWLockRead read_lock(rwlock);
ERR_FAIL_INDEX_V(p_idx, outlines.size(), Vector<Vector2>());
return outlines[p_idx];
}
void NavigationPolygon::clear_outlines() {
RWLockWrite write_lock(rwlock);
outlines.clear();
rect_cache_dirty = true;
}
#ifndef DISABLE_DEPRECATED
void NavigationPolygon::make_polygons_from_outlines() {
RWLockWrite write_lock(rwlock);
WARN_PRINT("Function make_polygons_from_outlines() is deprecated."
"\nUse NavigationServer2D.parse_source_geometry_data() and NavigationServer2D.bake_from_source_geometry_data() instead.");
{
MutexLock lock(navigation_mesh_generation);
navigation_mesh.unref();
}
List<TPPLPoly> in_poly, out_poly;
Vector2 outside_point(-1e10, -1e10);
for (int i = 0; i < outlines.size(); i++) {
Vector<Vector2> ol = outlines[i];
int olsize = ol.size();
if (olsize < 3) {
continue;
}
const Vector2 *r = ol.ptr();
for (int j = 0; j < olsize; j++) {
outside_point = outside_point.max(r[j]);
}
}
outside_point += Vector2(0.7239784, 0.819238); //avoid precision issues
for (int i = 0; i < outlines.size(); i++) {
Vector<Vector2> ol = outlines[i];
int olsize = ol.size();
if (olsize < 3) {
continue;
}
const Vector2 *r = ol.ptr();
int interscount = 0;
//test if this is an outer outline
for (int k = 0; k < outlines.size(); k++) {
if (i == k) {
continue; //no self intersect
}
Vector<Vector2> ol2 = outlines[k];
int olsize2 = ol2.size();
if (olsize2 < 3) {
continue;
}
const Vector2 *r2 = ol2.ptr();
for (int l = 0; l < olsize2; l++) {
if (Geometry2D::segment_intersects_segment(r[0], outside_point, r2[l], r2[(l + 1) % olsize2], nullptr)) {
interscount++;
}
}
}
bool outer = (interscount % 2) == 0;
TPPLPoly tp;
tp.Init(olsize);
for (int j = 0; j < olsize; j++) {
tp[j] = r[j];
}
if (outer) {
tp.SetOrientation(TPPL_ORIENTATION_CCW);
} else {
tp.SetOrientation(TPPL_ORIENTATION_CW);
tp.SetHole(true);
}
in_poly.push_back(tp);
}
TPPLPartition tpart;
if (tpart.ConvexPartition_HM(&in_poly, &out_poly) == 0) { //failed!
ERR_PRINT("NavigationPolygon: Convex partition failed! Failed to convert outlines to a valid NavigationMesh."
"\nNavigationPolygon outlines can not overlap vertices or edges inside same outline or with other outlines or have any intersections."
"\nAdd the outmost and largest outline first. To add holes inside this outline add the smaller outlines with same winding order.");
return;
}
polygons.clear();
vertices.clear();
HashMap<Vector2, int> points;
for (List<TPPLPoly>::Element *I = out_poly.front(); I; I = I->next()) {
TPPLPoly &tp = I->get();
struct Polygon p;
for (int64_t i = 0; i < tp.GetNumPoints(); i++) {
HashMap<Vector2, int>::Iterator E = points.find(tp[i]);
if (!E) {
E = points.insert(tp[i], vertices.size());
vertices.push_back(tp[i]);
}
p.indices.push_back(E->value);
}
polygons.push_back(p);
}
emit_changed();
}
#endif // DISABLE_DEPRECATED
void NavigationPolygon::set_cell_size(real_t p_cell_size) {
cell_size = p_cell_size;
get_navigation_mesh()->set_cell_size(cell_size);
}
real_t NavigationPolygon::get_cell_size() const {
return cell_size;
}
void NavigationPolygon::set_border_size(real_t p_value) {
ERR_FAIL_COND(p_value < 0.0);
border_size = p_value;
}
real_t NavigationPolygon::get_border_size() const {
return border_size;
}
void NavigationPolygon::set_parsed_geometry_type(ParsedGeometryType p_geometry_type) {
ERR_FAIL_INDEX(p_geometry_type, PARSED_GEOMETRY_MAX);
parsed_geometry_type = p_geometry_type;
notify_property_list_changed();
}
NavigationPolygon::ParsedGeometryType NavigationPolygon::get_parsed_geometry_type() const {
return parsed_geometry_type;
}
void NavigationPolygon::set_parsed_collision_mask(uint32_t p_mask) {
parsed_collision_mask = p_mask;
}
uint32_t NavigationPolygon::get_parsed_collision_mask() const {
return parsed_collision_mask;
}
void NavigationPolygon::set_parsed_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_parsed_collision_mask();
if (p_value) {
mask |= 1 << (p_layer_number - 1);
} else {
mask &= ~(1 << (p_layer_number - 1));
}
set_parsed_collision_mask(mask);
}
bool NavigationPolygon::get_parsed_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_parsed_collision_mask() & (1 << (p_layer_number - 1));
}
void NavigationPolygon::set_source_geometry_mode(SourceGeometryMode p_geometry_mode) {
ERR_FAIL_INDEX(p_geometry_mode, SOURCE_GEOMETRY_MAX);
source_geometry_mode = p_geometry_mode;
notify_property_list_changed();
}
NavigationPolygon::SourceGeometryMode NavigationPolygon::get_source_geometry_mode() const {
return source_geometry_mode;
}
void NavigationPolygon::set_source_geometry_group_name(const StringName &p_group_name) {
source_geometry_group_name = p_group_name;
}
StringName NavigationPolygon::get_source_geometry_group_name() const {
return source_geometry_group_name;
}
void NavigationPolygon::set_agent_radius(real_t p_value) {
ERR_FAIL_COND(p_value < 0);
agent_radius = p_value;
}
real_t NavigationPolygon::get_agent_radius() const {
return agent_radius;
}
void NavigationPolygon::set_baking_rect(const Rect2 &p_rect) {
baking_rect = p_rect;
emit_changed();
}
Rect2 NavigationPolygon::get_baking_rect() const {
return baking_rect;
}
void NavigationPolygon::set_baking_rect_offset(const Vector2 &p_rect_offset) {
baking_rect_offset = p_rect_offset;
emit_changed();
}
Vector2 NavigationPolygon::get_baking_rect_offset() const {
return baking_rect_offset;
}
void NavigationPolygon::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationPolygon::set_vertices);
ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationPolygon::get_vertices);
ClassDB::bind_method(D_METHOD("add_polygon", "polygon"), &NavigationPolygon::add_polygon);
ClassDB::bind_method(D_METHOD("get_polygon_count"), &NavigationPolygon::get_polygon_count);
ClassDB::bind_method(D_METHOD("get_polygon", "idx"), &NavigationPolygon::get_polygon);
ClassDB::bind_method(D_METHOD("clear_polygons"), &NavigationPolygon::clear_polygons);
ClassDB::bind_method(D_METHOD("get_navigation_mesh"), &NavigationPolygon::get_navigation_mesh);
ClassDB::bind_method(D_METHOD("add_outline", "outline"), &NavigationPolygon::add_outline);
ClassDB::bind_method(D_METHOD("add_outline_at_index", "outline", "index"), &NavigationPolygon::add_outline_at_index);
ClassDB::bind_method(D_METHOD("get_outline_count"), &NavigationPolygon::get_outline_count);
ClassDB::bind_method(D_METHOD("set_outline", "idx", "outline"), &NavigationPolygon::set_outline);
ClassDB::bind_method(D_METHOD("get_outline", "idx"), &NavigationPolygon::get_outline);
ClassDB::bind_method(D_METHOD("remove_outline", "idx"), &NavigationPolygon::remove_outline);
ClassDB::bind_method(D_METHOD("clear_outlines"), &NavigationPolygon::clear_outlines);
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("make_polygons_from_outlines"), &NavigationPolygon::make_polygons_from_outlines);
#endif // DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("_set_polygons", "polygons"), &NavigationPolygon::_set_polygons);
ClassDB::bind_method(D_METHOD("_get_polygons"), &NavigationPolygon::_get_polygons);
ClassDB::bind_method(D_METHOD("_set_outlines", "outlines"), &NavigationPolygon::_set_outlines);
ClassDB::bind_method(D_METHOD("_get_outlines"), &NavigationPolygon::_get_outlines);
ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &NavigationPolygon::set_cell_size);
ClassDB::bind_method(D_METHOD("get_cell_size"), &NavigationPolygon::get_cell_size);
ClassDB::bind_method(D_METHOD("set_border_size", "border_size"), &NavigationPolygon::set_border_size);
ClassDB::bind_method(D_METHOD("get_border_size"), &NavigationPolygon::get_border_size);
ClassDB::bind_method(D_METHOD("set_parsed_geometry_type", "geometry_type"), &NavigationPolygon::set_parsed_geometry_type);
ClassDB::bind_method(D_METHOD("get_parsed_geometry_type"), &NavigationPolygon::get_parsed_geometry_type);
ClassDB::bind_method(D_METHOD("set_parsed_collision_mask", "mask"), &NavigationPolygon::set_parsed_collision_mask);
ClassDB::bind_method(D_METHOD("get_parsed_collision_mask"), &NavigationPolygon::get_parsed_collision_mask);
ClassDB::bind_method(D_METHOD("set_parsed_collision_mask_value", "layer_number", "value"), &NavigationPolygon::set_parsed_collision_mask_value);
ClassDB::bind_method(D_METHOD("get_parsed_collision_mask_value", "layer_number"), &NavigationPolygon::get_parsed_collision_mask_value);
ClassDB::bind_method(D_METHOD("set_source_geometry_mode", "geometry_mode"), &NavigationPolygon::set_source_geometry_mode);
ClassDB::bind_method(D_METHOD("get_source_geometry_mode"), &NavigationPolygon::get_source_geometry_mode);
ClassDB::bind_method(D_METHOD("set_source_geometry_group_name", "group_name"), &NavigationPolygon::set_source_geometry_group_name);
ClassDB::bind_method(D_METHOD("get_source_geometry_group_name"), &NavigationPolygon::get_source_geometry_group_name);
ClassDB::bind_method(D_METHOD("set_agent_radius", "agent_radius"), &NavigationPolygon::set_agent_radius);
ClassDB::bind_method(D_METHOD("get_agent_radius"), &NavigationPolygon::get_agent_radius);
ClassDB::bind_method(D_METHOD("set_baking_rect", "rect"), &NavigationPolygon::set_baking_rect);
ClassDB::bind_method(D_METHOD("get_baking_rect"), &NavigationPolygon::get_baking_rect);
ClassDB::bind_method(D_METHOD("set_baking_rect_offset", "rect_offset"), &NavigationPolygon::set_baking_rect_offset);
ClassDB::bind_method(D_METHOD("get_baking_rect_offset"), &NavigationPolygon::get_baking_rect_offset);
ClassDB::bind_method(D_METHOD("clear"), &NavigationPolygon::clear);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines");
ADD_GROUP("Geometry", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "parsed_geometry_type", PROPERTY_HINT_ENUM, "Mesh Instances,Static Colliders,Meshes and Static Colliders"), "set_parsed_geometry_type", "get_parsed_geometry_type");
ADD_PROPERTY(PropertyInfo(Variant::INT, "parsed_collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_parsed_collision_mask", "get_parsed_collision_mask");
ADD_PROPERTY_DEFAULT("parsed_collision_mask", 0xFFFFFFFF);
ADD_PROPERTY(PropertyInfo(Variant::INT, "source_geometry_mode", PROPERTY_HINT_ENUM, "Root Node Children,Group With Children,Group Explicit"), "set_source_geometry_mode", "get_source_geometry_mode");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_geometry_group_name"), "set_source_geometry_group_name", "get_source_geometry_group_name");
ADD_PROPERTY_DEFAULT("source_geometry_group_name", StringName("navigation_polygon_source_geometry_group"));
ADD_GROUP("Cells", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "1.0,50.0,1.0,or_greater,suffix:px"), "set_cell_size", "get_cell_size");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "border_size", PROPERTY_HINT_RANGE, "0.0,500.0,1.0,or_greater,suffix:px"), "set_border_size", "get_border_size");
ADD_GROUP("Agents", "agent_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_radius", PROPERTY_HINT_RANGE, "0.0,500.0,0.01,or_greater,suffix:px"), "set_agent_radius", "get_agent_radius");
ADD_GROUP("Filters", "");
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "baking_rect"), "set_baking_rect", "get_baking_rect");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "baking_rect_offset"), "set_baking_rect_offset", "get_baking_rect_offset");
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MESH_INSTANCES);
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_STATIC_COLLIDERS);
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_BOTH);
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MAX);
BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_ROOT_NODE_CHILDREN);
BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN);
BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_GROUPS_EXPLICIT);
BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_MAX);
}
void NavigationPolygon::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "parsed_collision_mask") {
if (parsed_geometry_type == PARSED_GEOMETRY_MESH_INSTANCES) {
p_property.usage = PROPERTY_USAGE_NONE;
return;
}
}
if (p_property.name == "parsed_source_group_name") {
if (source_geometry_mode == SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) {
p_property.usage = PROPERTY_USAGE_NONE;
return;
}
}
}

View file

@ -0,0 +1,167 @@
/**************************************************************************/
/* navigation_polygon.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_POLYGON_H
#define NAVIGATION_POLYGON_H
#include "scene/2d/node_2d.h"
#include "scene/resources/navigation_mesh.h"
class NavigationPolygon : public Resource {
GDCLASS(NavigationPolygon, Resource);
RWLock rwlock;
Vector<Vector2> vertices;
struct Polygon {
Vector<int> indices;
};
Vector<Polygon> polygons;
Vector<Vector<Vector2>> outlines;
Vector<Vector<Vector2>> baked_outlines;
mutable Rect2 item_rect;
mutable bool rect_cache_dirty = true;
Mutex navigation_mesh_generation;
// Navigation mesh
Ref<NavigationMesh> navigation_mesh;
real_t cell_size = 1.0f; // Must match ProjectSettings default 2D cell_size.
real_t border_size = 0.0f;
Rect2 baking_rect;
Vector2 baking_rect_offset;
protected:
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
void _set_polygons(const TypedArray<Vector<int32_t>> &p_array);
TypedArray<Vector<int32_t>> _get_polygons() const;
void _set_outlines(const TypedArray<Vector<Vector2>> &p_array);
TypedArray<Vector<Vector2>> _get_outlines() const;
public:
#ifdef TOOLS_ENABLED
Rect2 _edit_get_rect() const;
bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
#endif
enum ParsedGeometryType {
PARSED_GEOMETRY_MESH_INSTANCES = 0,
PARSED_GEOMETRY_STATIC_COLLIDERS,
PARSED_GEOMETRY_BOTH,
PARSED_GEOMETRY_MAX
};
enum SourceGeometryMode {
SOURCE_GEOMETRY_ROOT_NODE_CHILDREN = 0,
SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN,
SOURCE_GEOMETRY_GROUPS_EXPLICIT,
SOURCE_GEOMETRY_MAX
};
real_t agent_radius = 10.0f;
ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_BOTH;
uint32_t parsed_collision_mask = 0xFFFFFFFF;
SourceGeometryMode source_geometry_mode = SOURCE_GEOMETRY_ROOT_NODE_CHILDREN;
StringName source_geometry_group_name = "navigation_polygon_source_geometry_group";
void set_vertices(const Vector<Vector2> &p_vertices);
Vector<Vector2> get_vertices() const;
void add_polygon(const Vector<int> &p_polygon);
int get_polygon_count() const;
void add_outline(const Vector<Vector2> &p_outline);
void add_outline_at_index(const Vector<Vector2> &p_outline, int p_index);
void set_outline(int p_idx, const Vector<Vector2> &p_outline);
Vector<Vector2> get_outline(int p_idx) const;
void remove_outline(int p_idx);
int get_outline_count() const;
void clear_outlines();
#ifndef DISABLE_DEPRECATED
void make_polygons_from_outlines();
#endif // DISABLE_DEPRECATED
void set_polygons(const Vector<Vector<int>> &p_polygons);
const Vector<Vector<int>> &get_polygons() const;
Vector<int> get_polygon(int p_idx);
void clear_polygons();
void set_parsed_geometry_type(ParsedGeometryType p_geometry_type);
ParsedGeometryType get_parsed_geometry_type() const;
void set_parsed_collision_mask(uint32_t p_mask);
uint32_t get_parsed_collision_mask() const;
void set_parsed_collision_mask_value(int p_layer_number, bool p_value);
bool get_parsed_collision_mask_value(int p_layer_number) const;
void set_source_geometry_mode(SourceGeometryMode p_geometry_mode);
SourceGeometryMode get_source_geometry_mode() const;
void set_source_geometry_group_name(const StringName &p_group_name);
StringName get_source_geometry_group_name() const;
void set_agent_radius(real_t p_value);
real_t get_agent_radius() const;
Ref<NavigationMesh> get_navigation_mesh();
void set_cell_size(real_t p_cell_size);
real_t get_cell_size() const;
void set_border_size(real_t p_value);
real_t get_border_size() const;
void set_baking_rect(const Rect2 &p_rect);
Rect2 get_baking_rect() const;
void set_baking_rect_offset(const Vector2 &p_rect_offset);
Vector2 get_baking_rect_offset() const;
void clear();
void set_data(const Vector<Vector2> &p_vertices, const Vector<Vector<int>> &p_polygons);
void get_data(Vector<Vector2> &r_vertices, Vector<Vector<int>> &r_polygons);
NavigationPolygon() {}
~NavigationPolygon() {}
};
VARIANT_ENUM_CAST(NavigationPolygon::ParsedGeometryType);
VARIANT_ENUM_CAST(NavigationPolygon::SourceGeometryMode);
#endif // NAVIGATION_POLYGON_H

View file

@ -0,0 +1,562 @@
/**************************************************************************/
/* polygon_path_finder.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_path_finder.h"
#include "core/math/geometry_2d.h"
bool PolygonPathFinder::_is_point_inside(const Vector2 &p_point) const {
int crosses = 0;
for (const Edge &E : edges) {
const Edge &e = E;
Vector2 a = points[e.points[0]].pos;
Vector2 b = points[e.points[1]].pos;
if (Geometry2D::segment_intersects_segment(a, b, p_point, outside_point, nullptr)) {
crosses++;
}
}
return crosses & 1;
}
void PolygonPathFinder::setup(const Vector<Vector2> &p_points, const Vector<int> &p_connections) {
ERR_FAIL_COND(p_connections.size() & 1);
points.clear();
edges.clear();
//insert points
int point_count = p_points.size();
points.resize(point_count + 2);
bounds = Rect2();
for (int i = 0; i < p_points.size(); i++) {
points.write[i].pos = p_points[i];
points.write[i].penalty = 0;
outside_point = i == 0 ? p_points[0] : p_points[i].max(outside_point);
if (i == 0) {
bounds.position = points[i].pos;
} else {
bounds.expand_to(points[i].pos);
}
}
outside_point.x += 20.451 + Math::randf() * 10.2039;
outside_point.y += 21.193 + Math::randf() * 12.5412;
//insert edges (which are also connections)
for (int i = 0; i < p_connections.size(); i += 2) {
Edge e(p_connections[i], p_connections[i + 1]);
ERR_FAIL_INDEX(e.points[0], point_count);
ERR_FAIL_INDEX(e.points[1], point_count);
points.write[p_connections[i]].connections.insert(p_connections[i + 1]);
points.write[p_connections[i + 1]].connections.insert(p_connections[i]);
edges.insert(e);
}
//fill the remaining connections based on visibility
for (int i = 0; i < point_count; i++) {
for (int j = i + 1; j < point_count; j++) {
if (edges.has(Edge(i, j))) {
continue; //if in edge ignore
}
Vector2 from = points[i].pos;
Vector2 to = points[j].pos;
if (!_is_point_inside(from * 0.5 + to * 0.5)) { //connection between points in inside space
continue;
}
bool valid = true;
for (const Edge &E : edges) {
const Edge &e = E;
if (e.points[0] == i || e.points[1] == i || e.points[0] == j || e.points[1] == j) {
continue;
}
Vector2 a = points[e.points[0]].pos;
Vector2 b = points[e.points[1]].pos;
if (Geometry2D::segment_intersects_segment(a, b, from, to, nullptr)) {
valid = false;
break;
}
}
if (valid) {
points.write[i].connections.insert(j);
points.write[j].connections.insert(i);
}
}
}
}
Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector2 &p_to) {
Vector<Vector2> path;
Vector2 from = p_from;
Vector2 to = p_to;
Edge ignore_from_edge(-1, -1);
Edge ignore_to_edge(-1, -1);
if (!_is_point_inside(from)) {
float closest_dist = 1e20f;
Vector2 closest_point;
for (const Edge &E : edges) {
const Edge &e = E;
Vector2 seg[2] = {
points[e.points[0]].pos,
points[e.points[1]].pos
};
Vector2 closest = Geometry2D::get_closest_point_to_segment(from, seg);
float d = from.distance_squared_to(closest);
if (d < closest_dist) {
ignore_from_edge = E;
closest_dist = d;
closest_point = closest;
}
}
from = closest_point;
};
if (!_is_point_inside(to)) {
float closest_dist = 1e20f;
Vector2 closest_point;
for (const Edge &E : edges) {
const Edge &e = E;
Vector2 seg[2] = {
points[e.points[0]].pos,
points[e.points[1]].pos
};
Vector2 closest = Geometry2D::get_closest_point_to_segment(to, seg);
float d = to.distance_squared_to(closest);
if (d < closest_dist) {
ignore_to_edge = E;
closest_dist = d;
closest_point = closest;
}
}
to = closest_point;
};
//test direct connection
{
bool can_see_eachother = true;
for (const Edge &E : edges) {
const Edge &e = E;
if (e.points[0] == ignore_from_edge.points[0] && e.points[1] == ignore_from_edge.points[1]) {
continue;
}
if (e.points[0] == ignore_to_edge.points[0] && e.points[1] == ignore_to_edge.points[1]) {
continue;
}
Vector2 a = points[e.points[0]].pos;
Vector2 b = points[e.points[1]].pos;
if (Geometry2D::segment_intersects_segment(a, b, from, to, nullptr)) {
can_see_eachother = false;
break;
}
}
if (can_see_eachother) {
path.push_back(from);
path.push_back(to);
return path;
}
}
//add to graph
int aidx = points.size() - 2;
int bidx = points.size() - 1;
points.write[aidx].pos = from;
points.write[bidx].pos = to;
points.write[aidx].distance = 0;
points.write[bidx].distance = 0;
points.write[aidx].prev = -1;
points.write[bidx].prev = -1;
points.write[aidx].penalty = 0;
points.write[bidx].penalty = 0;
for (int i = 0; i < points.size() - 2; i++) {
bool valid_a = true;
bool valid_b = true;
points.write[i].prev = -1;
points.write[i].distance = 0;
if (!_is_point_inside(from * 0.5 + points[i].pos * 0.5)) {
valid_a = false;
}
if (!_is_point_inside(to * 0.5 + points[i].pos * 0.5)) {
valid_b = false;
}
for (const Edge &E : edges) {
const Edge &e = E;
if (e.points[0] == i || e.points[1] == i) {
continue;
}
Vector2 a = points[e.points[0]].pos;
Vector2 b = points[e.points[1]].pos;
if (valid_a) {
if (e.points[0] != ignore_from_edge.points[1] &&
e.points[1] != ignore_from_edge.points[1] &&
e.points[0] != ignore_from_edge.points[0] &&
e.points[1] != ignore_from_edge.points[0]) {
if (Geometry2D::segment_intersects_segment(a, b, from, points[i].pos, nullptr)) {
valid_a = false;
}
}
}
if (valid_b) {
if (e.points[0] != ignore_to_edge.points[1] &&
e.points[1] != ignore_to_edge.points[1] &&
e.points[0] != ignore_to_edge.points[0] &&
e.points[1] != ignore_to_edge.points[0]) {
if (Geometry2D::segment_intersects_segment(a, b, to, points[i].pos, nullptr)) {
valid_b = false;
}
}
}
if (!valid_a && !valid_b) {
break;
}
}
if (valid_a) {
points.write[i].connections.insert(aidx);
points.write[aidx].connections.insert(i);
}
if (valid_b) {
points.write[i].connections.insert(bidx);
points.write[bidx].connections.insert(i);
}
}
//solve graph
HashSet<int> open_list;
points.write[aidx].distance = 0;
points.write[aidx].prev = aidx;
for (const int &E : points[aidx].connections) {
open_list.insert(E);
points.write[E].distance = from.distance_to(points[E].pos);
points.write[E].prev = aidx;
}
bool found_route = false;
while (true) {
if (open_list.size() == 0) {
print_verbose("Open list empty.");
break;
}
//check open list
int least_cost_point = -1;
float least_cost = 1e30;
//this could be faster (cache previous results)
for (const int &E : open_list) {
const Point &p = points[E];
float cost = p.distance;
cost += p.pos.distance_to(to);
cost += p.penalty;
if (cost < least_cost) {
least_cost_point = E;
least_cost = cost;
}
}
const Point &np = points[least_cost_point];
//open the neighbors for search
for (const int &E : np.connections) {
Point &p = points.write[E];
float distance = np.pos.distance_to(p.pos) + np.distance;
if (p.prev != -1) {
//oh this was visited already, can we win the cost?
if (p.distance > distance) {
p.prev = least_cost_point; //reassign previous
p.distance = distance;
}
} else {
//add to open neighbors
p.prev = least_cost_point;
p.distance = distance;
open_list.insert(E);
if (E == bidx) {
//oh my reached end! stop algorithm
found_route = true;
break;
}
}
}
if (found_route) {
break;
}
open_list.erase(least_cost_point);
}
if (found_route) {
int at = bidx;
path.push_back(points[at].pos);
do {
at = points[at].prev;
path.push_back(points[at].pos);
} while (at != aidx);
path.reverse();
}
for (int i = 0; i < points.size() - 2; i++) {
points.write[i].connections.erase(aidx);
points.write[i].connections.erase(bidx);
points.write[i].prev = -1;
points.write[i].distance = 0;
}
points.write[aidx].connections.clear();
points.write[aidx].prev = -1;
points.write[aidx].distance = 0;
points.write[bidx].connections.clear();
points.write[bidx].prev = -1;
points.write[bidx].distance = 0;
return path;
}
void PolygonPathFinder::_set_data(const Dictionary &p_data) {
ERR_FAIL_COND(!p_data.has("points"));
ERR_FAIL_COND(!p_data.has("connections"));
ERR_FAIL_COND(!p_data.has("segments"));
ERR_FAIL_COND(!p_data.has("bounds"));
Vector<Vector2> p = p_data["points"];
Array c = p_data["connections"];
ERR_FAIL_COND(c.size() != p.size());
if (c.size()) {
return;
}
int pc = p.size();
points.resize(pc + 2);
const Vector2 *pr = p.ptr();
for (int i = 0; i < pc; i++) {
points.write[i].pos = pr[i];
Vector<int> con = c[i];
const int *cr = con.ptr();
int cc = con.size();
for (int j = 0; j < cc; j++) {
points.write[i].connections.insert(cr[j]);
}
}
if (p_data.has("penalties")) {
Vector<real_t> penalties = p_data["penalties"];
if (penalties.size() == pc) {
const real_t *pr2 = penalties.ptr();
for (int i = 0; i < pc; i++) {
points.write[i].penalty = pr2[i];
}
}
}
Vector<int> segs = p_data["segments"];
int sc = segs.size();
ERR_FAIL_COND(sc & 1);
const int *sr = segs.ptr();
for (int i = 0; i < sc; i += 2) {
Edge e(sr[i], sr[i + 1]);
edges.insert(e);
}
bounds = p_data["bounds"];
}
Dictionary PolygonPathFinder::_get_data() const {
Dictionary d;
Vector<Vector2> p;
Vector<int> ind;
Array path_connections;
p.resize(MAX(0, points.size() - 2));
path_connections.resize(MAX(0, points.size() - 2));
ind.resize(edges.size() * 2);
Vector<real_t> penalties;
penalties.resize(MAX(0, points.size() - 2));
{
Vector2 *wp = p.ptrw();
real_t *pw = penalties.ptrw();
for (int i = 0; i < points.size() - 2; i++) {
wp[i] = points[i].pos;
pw[i] = points[i].penalty;
Vector<int> c;
c.resize(points[i].connections.size());
{
int *cw = c.ptrw();
int idx = 0;
for (const int &E : points[i].connections) {
cw[idx++] = E;
}
}
path_connections[i] = c;
}
}
{
int *iw = ind.ptrw();
int idx = 0;
for (const Edge &E : edges) {
iw[idx++] = E.points[0];
iw[idx++] = E.points[1];
}
}
d["bounds"] = bounds;
d["points"] = p;
d["penalties"] = penalties;
d["connections"] = path_connections;
d["segments"] = ind;
return d;
}
bool PolygonPathFinder::is_point_inside(const Vector2 &p_point) const {
return _is_point_inside(p_point);
}
Vector2 PolygonPathFinder::get_closest_point(const Vector2 &p_point) const {
float closest_dist = 1e20f;
Vector2 closest_point;
for (const Edge &E : edges) {
const Edge &e = E;
Vector2 seg[2] = {
points[e.points[0]].pos,
points[e.points[1]].pos
};
Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, seg);
float d = p_point.distance_squared_to(closest);
if (d < closest_dist) {
closest_dist = d;
closest_point = closest;
}
}
ERR_FAIL_COND_V(Math::is_equal_approx(closest_dist, 1e20f), Vector2());
return closest_point;
}
Vector<Vector2> PolygonPathFinder::get_intersections(const Vector2 &p_from, const Vector2 &p_to) const {
Vector<Vector2> inters;
for (const Edge &E : edges) {
Vector2 a = points[E.points[0]].pos;
Vector2 b = points[E.points[1]].pos;
Vector2 res;
if (Geometry2D::segment_intersects_segment(a, b, p_from, p_to, &res)) {
inters.push_back(res);
}
}
return inters;
}
Rect2 PolygonPathFinder::get_bounds() const {
return bounds;
}
void PolygonPathFinder::set_point_penalty(int p_point, float p_penalty) {
ERR_FAIL_INDEX(p_point, points.size() - 2);
points.write[p_point].penalty = p_penalty;
}
float PolygonPathFinder::get_point_penalty(int p_point) const {
ERR_FAIL_INDEX_V(p_point, points.size() - 2, 0);
return points[p_point].penalty;
}
void PolygonPathFinder::_bind_methods() {
ClassDB::bind_method(D_METHOD("setup", "points", "connections"), &PolygonPathFinder::setup);
ClassDB::bind_method(D_METHOD("find_path", "from", "to"), &PolygonPathFinder::find_path);
ClassDB::bind_method(D_METHOD("get_intersections", "from", "to"), &PolygonPathFinder::get_intersections);
ClassDB::bind_method(D_METHOD("get_closest_point", "point"), &PolygonPathFinder::get_closest_point);
ClassDB::bind_method(D_METHOD("is_point_inside", "point"), &PolygonPathFinder::is_point_inside);
ClassDB::bind_method(D_METHOD("set_point_penalty", "idx", "penalty"), &PolygonPathFinder::set_point_penalty);
ClassDB::bind_method(D_METHOD("get_point_penalty", "idx"), &PolygonPathFinder::get_point_penalty);
ClassDB::bind_method(D_METHOD("get_bounds"), &PolygonPathFinder::get_bounds);
ClassDB::bind_method(D_METHOD("_set_data", "data"), &PolygonPathFinder::_set_data);
ClassDB::bind_method(D_METHOD("_get_data"), &PolygonPathFinder::_get_data);
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
}
PolygonPathFinder::PolygonPathFinder() {
}

View file

@ -0,0 +1,98 @@
/**************************************************************************/
/* polygon_path_finder.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_PATH_FINDER_H
#define POLYGON_PATH_FINDER_H
#include "core/io/resource.h"
class PolygonPathFinder : public Resource {
GDCLASS(PolygonPathFinder, Resource);
struct Point {
Vector2 pos;
HashSet<int> connections;
float distance = 0.0;
float penalty = 0.0;
int prev = 0;
};
union Edge {
struct {
int32_t points[2];
};
uint64_t key = 0;
_FORCE_INLINE_ bool operator==(const Edge &p_edge) const {
return key == p_edge.key;
}
_FORCE_INLINE_ static uint32_t hash(const Edge &p_edge) {
return hash_one_uint64(p_edge.key);
}
Edge(int a = 0, int b = 0) {
if (a > b) {
SWAP(a, b);
}
points[0] = a;
points[1] = b;
}
};
Vector2 outside_point;
Rect2 bounds;
Vector<Point> points;
HashSet<Edge, Edge> edges;
bool _is_point_inside(const Vector2 &p_point) const;
void _set_data(const Dictionary &p_data);
Dictionary _get_data() const;
protected:
static void _bind_methods();
public:
void setup(const Vector<Vector2> &p_points, const Vector<int> &p_connections);
Vector<Vector2> find_path(const Vector2 &p_from, const Vector2 &p_to);
void set_point_penalty(int p_point, float p_penalty);
float get_point_penalty(int p_point) const;
bool is_point_inside(const Vector2 &p_point) const;
Vector2 get_closest_point(const Vector2 &p_point) const;
Vector<Vector2> get_intersections(const Vector2 &p_from, const Vector2 &p_to) const;
Rect2 get_bounds() const;
PolygonPathFinder();
};
#endif // POLYGON_PATH_FINDER_H

View file

@ -0,0 +1,107 @@
/**************************************************************************/
/* rectangle_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 "rectangle_shape_2d.h"
#include "servers/physics_server_2d.h"
#include "servers/rendering_server.h"
void RectangleShape2D::_update_shape() {
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), size * 0.5);
emit_changed();
}
#ifndef DISABLE_DEPRECATED
bool RectangleShape2D::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "extents") { // Compatibility with Godot 3.x.
// Convert to `size`, twice as big.
set_size((Size2)p_value * 2);
return true;
}
return false;
}
bool RectangleShape2D::_get(const StringName &p_name, Variant &r_property) const {
if (p_name == "extents") { // Compatibility with Godot 3.x.
// Convert to `extents`, half as big.
r_property = size / 2;
return true;
}
return false;
}
#endif // DISABLE_DEPRECATED
void RectangleShape2D::set_size(const Size2 &p_size) {
ERR_FAIL_COND_MSG(p_size.x < 0 || p_size.y < 0, "RectangleShape2D size cannot be negative.");
size = p_size;
_update_shape();
}
Size2 RectangleShape2D::get_size() const {
return size;
}
void RectangleShape2D::draw(const RID &p_to_rid, const Color &p_color) {
RenderingServer::get_singleton()->canvas_item_add_rect(p_to_rid, Rect2(-size * 0.5, size), p_color);
if (is_collision_outline_enabled()) {
// Draw an outlined rectangle to make individual shapes easier to distinguish.
Vector<Vector2> stroke_points;
stroke_points.resize(5);
stroke_points.write[0] = -size * 0.5;
stroke_points.write[1] = Vector2(size.x, -size.y) * 0.5;
stroke_points.write[2] = size * 0.5;
stroke_points.write[3] = Vector2(-size.x, size.y) * 0.5;
stroke_points.write[4] = -size * 0.5;
Vector<Color> stroke_colors = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, stroke_points, stroke_colors);
}
}
Rect2 RectangleShape2D::get_rect() const {
return Rect2(-size * 0.5, size);
}
real_t RectangleShape2D::get_enclosing_radius() const {
return size.length() / 2;
}
void RectangleShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_size", "size"), &RectangleShape2D::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &RectangleShape2D::get_size);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size");
}
RectangleShape2D::RectangleShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->rectangle_shape_create()) {
size = Size2(20, 20);
_update_shape();
}

View file

@ -0,0 +1,60 @@
/**************************************************************************/
/* rectangle_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 RECTANGLE_SHAPE_2D_H
#define RECTANGLE_SHAPE_2D_H
#include "scene/resources/2d/shape_2d.h"
class RectangleShape2D : public Shape2D {
GDCLASS(RectangleShape2D, Shape2D);
Size2 size;
void _update_shape();
protected:
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_property) const;
#endif // DISABLE_DEPRECATED
public:
void set_size(const Size2 &p_size);
Size2 get_size() const;
virtual void draw(const RID &p_to_rid, const Color &p_color) override;
virtual Rect2 get_rect() const override;
virtual real_t get_enclosing_radius() const override;
RectangleShape2D();
};
#endif // RECTANGLE_SHAPE_2D_H

View file

@ -0,0 +1,100 @@
/**************************************************************************/
/* segment_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 "segment_shape_2d.h"
#include "core/math/geometry_2d.h"
#include "servers/physics_server_2d.h"
#include "servers/rendering_server.h"
bool SegmentShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
Vector2 l[2] = { a, b };
Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, l);
return p_point.distance_to(closest) < p_tolerance;
}
void SegmentShape2D::_update_shape() {
Rect2 r;
r.position = a;
r.size = b;
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), r);
emit_changed();
}
void SegmentShape2D::set_a(const Vector2 &p_a) {
a = p_a;
_update_shape();
}
Vector2 SegmentShape2D::get_a() const {
return a;
}
void SegmentShape2D::set_b(const Vector2 &p_b) {
b = p_b;
_update_shape();
}
Vector2 SegmentShape2D::get_b() const {
return b;
}
void SegmentShape2D::draw(const RID &p_to_rid, const Color &p_color) {
RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, a, b, p_color, 3);
}
Rect2 SegmentShape2D::get_rect() const {
Rect2 rect;
rect.position = a;
rect.expand_to(b);
return rect;
}
real_t SegmentShape2D::get_enclosing_radius() const {
return (a + b).length();
}
void SegmentShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_a", "a"), &SegmentShape2D::set_a);
ClassDB::bind_method(D_METHOD("get_a"), &SegmentShape2D::get_a);
ClassDB::bind_method(D_METHOD("set_b", "b"), &SegmentShape2D::set_b);
ClassDB::bind_method(D_METHOD("get_b"), &SegmentShape2D::get_b);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "a", PROPERTY_HINT_NONE, "suffix:px"), "set_a", "get_a");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "b", PROPERTY_HINT_NONE, "suffix:px"), "set_b", "get_b");
}
SegmentShape2D::SegmentShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->segment_shape_create()) {
a = Vector2();
b = Vector2(0, 10);
_update_shape();
}

View file

@ -0,0 +1,63 @@
/**************************************************************************/
/* segment_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 SEGMENT_SHAPE_2D_H
#define SEGMENT_SHAPE_2D_H
#include "scene/resources/2d/shape_2d.h"
class SegmentShape2D : public Shape2D {
GDCLASS(SegmentShape2D, Shape2D);
Vector2 a;
Vector2 b;
void _update_shape();
protected:
static void _bind_methods();
public:
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
void set_a(const Vector2 &p_a);
void set_b(const Vector2 &p_b);
Vector2 get_a() const;
Vector2 get_b() const;
virtual void draw(const RID &p_to_rid, const Color &p_color) override;
virtual Rect2 get_rect() const override;
virtual real_t get_enclosing_radius() const override;
SegmentShape2D();
};
#endif // SEGMENT_SHAPE_2D_H

View file

@ -0,0 +1,117 @@
/**************************************************************************/
/* separation_ray_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 "separation_ray_shape_2d.h"
#include "servers/physics_server_2d.h"
#include "servers/rendering_server.h"
void SeparationRayShape2D::_update_shape() {
Dictionary d;
d["length"] = length;
d["slide_on_slope"] = slide_on_slope;
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), d);
emit_changed();
}
void SeparationRayShape2D::draw(const RID &p_to_rid, const Color &p_color) {
const Vector2 target_position = Vector2(0, get_length());
const float max_arrow_size = 6;
const float line_width = 1.4;
bool no_line = target_position.length() < line_width;
float arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size);
if (no_line) {
arrow_size = target_position.length();
} else {
RS::get_singleton()->canvas_item_add_line(p_to_rid, Vector2(), target_position - target_position.normalized() * arrow_size, p_color, 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 = { p_color, p_color, p_color };
RS::get_singleton()->canvas_item_add_primitive(p_to_rid, pts, cols, Vector<Point2>(), RID());
}
Rect2 SeparationRayShape2D::get_rect() const {
Rect2 rect;
rect.position = Vector2();
rect.expand_to(Vector2(0, length));
rect = rect.grow(Math_SQRT12 * 4);
return rect;
}
real_t SeparationRayShape2D::get_enclosing_radius() const {
return length;
}
void SeparationRayShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape2D::set_length);
ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape2D::get_length);
ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape2D::set_slide_on_slope);
ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape2D::get_slide_on_slope);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_length", "get_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope");
}
void SeparationRayShape2D::set_length(real_t p_length) {
length = p_length;
_update_shape();
}
real_t SeparationRayShape2D::get_length() const {
return length;
}
void SeparationRayShape2D::set_slide_on_slope(bool p_active) {
slide_on_slope = p_active;
_update_shape();
}
bool SeparationRayShape2D::get_slide_on_slope() const {
return slide_on_slope;
}
SeparationRayShape2D::SeparationRayShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->separation_ray_shape_create()) {
_update_shape();
}

View file

@ -0,0 +1,61 @@
/**************************************************************************/
/* separation_ray_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 SEPARATION_RAY_SHAPE_2D_H
#define SEPARATION_RAY_SHAPE_2D_H
#include "scene/resources/2d/shape_2d.h"
class SeparationRayShape2D : public Shape2D {
GDCLASS(SeparationRayShape2D, Shape2D);
real_t length = 20.0;
bool slide_on_slope = false;
void _update_shape();
protected:
static void _bind_methods();
public:
void set_length(real_t p_length);
real_t get_length() const;
void set_slide_on_slope(bool p_active);
bool get_slide_on_slope() const;
virtual void draw(const RID &p_to_rid, const Color &p_color) override;
virtual Rect2 get_rect() const override;
virtual real_t get_enclosing_radius() const override;
SeparationRayShape2D();
};
#endif // SEPARATION_RAY_SHAPE_2D_H

View file

@ -0,0 +1,124 @@
/**************************************************************************/
/* 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 "shape_2d.h"
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "servers/physics_server_2d.h"
RID Shape2D::get_rid() const {
return shape;
}
void Shape2D::set_custom_solver_bias(real_t p_bias) {
custom_bias = p_bias;
PhysicsServer2D::get_singleton()->shape_set_custom_solver_bias(shape, custom_bias);
}
real_t Shape2D::get_custom_solver_bias() const {
return custom_bias;
}
bool Shape2D::collide_with_motion(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref<Shape2D> &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion) {
ERR_FAIL_COND_V(p_shape.is_null(), false);
int r;
return PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, p_local_motion, p_shape->get_rid(), p_shape_xform, p_shape_motion, nullptr, 0, r);
}
bool Shape2D::collide(const Transform2D &p_local_xform, const Ref<Shape2D> &p_shape, const Transform2D &p_shape_xform) {
ERR_FAIL_COND_V(p_shape.is_null(), false);
int r;
return PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, Vector2(), p_shape->get_rid(), p_shape_xform, Vector2(), nullptr, 0, r);
}
PackedVector2Array Shape2D::collide_with_motion_and_get_contacts(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref<Shape2D> &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion) {
ERR_FAIL_COND_V(p_shape.is_null(), PackedVector2Array());
const int max_contacts = 16;
Vector2 result[max_contacts * 2];
int contacts = 0;
if (!PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, p_local_motion, p_shape->get_rid(), p_shape_xform, p_shape_motion, result, max_contacts, contacts)) {
return PackedVector2Array();
}
PackedVector2Array results;
results.resize(contacts * 2);
for (int i = 0; i < contacts * 2; i++) {
results.write[i] = result[i];
}
return results;
}
PackedVector2Array Shape2D::collide_and_get_contacts(const Transform2D &p_local_xform, const Ref<Shape2D> &p_shape, const Transform2D &p_shape_xform) {
ERR_FAIL_COND_V(p_shape.is_null(), PackedVector2Array());
const int max_contacts = 16;
Vector2 result[max_contacts * 2];
int contacts = 0;
if (!PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, Vector2(), p_shape->get_rid(), p_shape_xform, Vector2(), result, max_contacts, contacts)) {
return PackedVector2Array();
}
PackedVector2Array results;
results.resize(contacts * 2);
for (int i = 0; i < contacts * 2; i++) {
results.write[i] = result[i];
}
return results;
}
void Shape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_custom_solver_bias", "bias"), &Shape2D::set_custom_solver_bias);
ClassDB::bind_method(D_METHOD("get_custom_solver_bias"), &Shape2D::get_custom_solver_bias);
ClassDB::bind_method(D_METHOD("collide", "local_xform", "with_shape", "shape_xform"), &Shape2D::collide);
ClassDB::bind_method(D_METHOD("collide_with_motion", "local_xform", "local_motion", "with_shape", "shape_xform", "shape_motion"), &Shape2D::collide_with_motion);
ClassDB::bind_method(D_METHOD("collide_and_get_contacts", "local_xform", "with_shape", "shape_xform"), &Shape2D::collide_and_get_contacts);
ClassDB::bind_method(D_METHOD("collide_with_motion_and_get_contacts", "local_xform", "local_motion", "with_shape", "shape_xform", "shape_motion"), &Shape2D::collide_with_motion_and_get_contacts);
ClassDB::bind_method(D_METHOD("draw", "canvas_item", "color"), &Shape2D::draw);
ClassDB::bind_method(D_METHOD("get_rect"), &Shape2D::get_rect);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_solver_bias", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_custom_solver_bias", "get_custom_solver_bias");
}
bool Shape2D::is_collision_outline_enabled() {
return GLOBAL_GET("debug/shapes/collision/draw_2d_outlines");
}
Shape2D::Shape2D(const RID &p_rid) {
shape = p_rid;
}
Shape2D::~Shape2D() {
ERR_FAIL_NULL(PhysicsServer2D::get_singleton());
PhysicsServer2D::get_singleton()->free(shape);
}

View file

@ -0,0 +1,70 @@
/**************************************************************************/
/* 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 SHAPE_2D_H
#define SHAPE_2D_H
#include "core/io/resource.h"
class Shape2D : public Resource {
GDCLASS(Shape2D, Resource);
OBJ_SAVE_TYPE(Shape2D);
RID shape;
real_t custom_bias = 0.0;
protected:
static void _bind_methods();
Shape2D(const RID &p_rid);
public:
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { return get_rect().has_point(p_point); }
void set_custom_solver_bias(real_t p_bias);
real_t get_custom_solver_bias() const;
bool collide_with_motion(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref<Shape2D> &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion);
bool collide(const Transform2D &p_local_xform, const Ref<Shape2D> &p_shape, const Transform2D &p_shape_xform);
PackedVector2Array collide_with_motion_and_get_contacts(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref<Shape2D> &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion);
PackedVector2Array collide_and_get_contacts(const Transform2D &p_local_xform, const Ref<Shape2D> &p_shape, const Transform2D &p_shape_xform);
virtual void draw(const RID &p_to_rid, const Color &p_color) {}
virtual Rect2 get_rect() const { return Rect2(); }
/// Returns the radius of a circle that fully enclose this shape
virtual real_t get_enclosing_radius() const = 0;
virtual RID get_rid() const override;
static bool is_collision_outline_enabled();
~Shape2D();
};
#endif // SHAPE_2D_H

View file

@ -0,0 +1,244 @@
/**************************************************************************/
/* skeleton_modification_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_modification_2d.h"
#include "scene/2d/skeleton_2d.h"
#include "scene/2d/physics/collision_object_2d.h"
#include "scene/2d/physics/collision_shape_2d.h"
#include "scene/2d/physics/physical_bone_2d.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
#endif // TOOLS_ENABLED
///////////////////////////////////////
// Modification2D
///////////////////////////////////////
void SkeletonModification2D::_execute(float p_delta) {
GDVIRTUAL_CALL(_execute, p_delta);
if (!enabled) {
return;
}
}
void SkeletonModification2D::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack) {
is_setup = true;
} else {
WARN_PRINT("Could not setup modification with name " + get_name());
}
GDVIRTUAL_CALL(_setup_modification, Ref<SkeletonModificationStack2D>(p_stack));
}
void SkeletonModification2D::_draw_editor_gizmo() {
GDVIRTUAL_CALL(_draw_editor_gizmo);
}
void SkeletonModification2D::set_enabled(bool p_enabled) {
enabled = p_enabled;
#ifdef TOOLS_ENABLED
if (editor_draw_gizmo) {
if (stack) {
stack->set_editor_gizmos_dirty(true);
}
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2D::get_enabled() {
return enabled;
}
float SkeletonModification2D::clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert) {
// Map to the 0 to 360 range (in radians though) instead of the -180 to 180 range.
if (p_angle < 0) {
p_angle = Math_TAU + p_angle;
}
// Make min and max in the range of 0 to 360 (in radians), and make sure they are in the right order
if (p_min_bound < 0) {
p_min_bound = Math_TAU + p_min_bound;
}
if (p_max_bound < 0) {
p_max_bound = Math_TAU + p_max_bound;
}
if (p_min_bound > p_max_bound) {
SWAP(p_min_bound, p_max_bound);
}
bool is_beyond_bounds = (p_angle < p_min_bound || p_angle > p_max_bound);
bool is_within_bounds = (p_angle > p_min_bound && p_angle < p_max_bound);
// Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle.
if ((!p_invert && is_beyond_bounds) || (p_invert && is_within_bounds)) {
Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound));
Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound));
Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle));
if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) {
p_angle = p_min_bound;
} else {
p_angle = p_max_bound;
}
}
return p_angle;
}
void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_bone, float p_min_bound, float p_max_bound,
bool p_constraint_enabled, bool p_constraint_in_localspace, bool p_constraint_inverted) {
if (!p_operation_bone) {
return;
}
Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4);
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color");
}
#endif // TOOLS_ENABLED
float arc_angle_min = p_min_bound;
float arc_angle_max = p_max_bound;
if (arc_angle_min < 0) {
arc_angle_min = (Math_PI * 2) + arc_angle_min;
}
if (arc_angle_max < 0) {
arc_angle_max = (Math_PI * 2) + arc_angle_max;
}
if (arc_angle_min > arc_angle_max) {
SWAP(arc_angle_min, arc_angle_max);
}
arc_angle_min += p_operation_bone->get_bone_angle();
arc_angle_max += p_operation_bone->get_bone_angle();
if (p_constraint_enabled) {
if (p_constraint_in_localspace) {
Node *operation_bone_parent = p_operation_bone->get_parent();
Bone2D *operation_bone_parent_bone = Object::cast_to<Bone2D>(operation_bone_parent);
if (operation_bone_parent_bone) {
stack->skeleton->draw_set_transform(
stack->skeleton->to_local(p_operation_bone->get_global_position()),
operation_bone_parent_bone->get_global_rotation() - stack->skeleton->get_global_rotation());
} else {
stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position()));
}
} else {
stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position()));
}
if (p_constraint_inverted) {
stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(),
arc_angle_min + (Math_PI * 2), arc_angle_max, 32, bone_ik_color, 1.0);
} else {
stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(),
arc_angle_min, arc_angle_max, 32, bone_ik_color, 1.0);
}
stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_min), Math::sin(arc_angle_min)) * p_operation_bone->get_length(), bone_ik_color, 1.0);
stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_max), Math::sin(arc_angle_max)) * p_operation_bone->get_length(), bone_ik_color, 1.0);
} else {
stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position()));
stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), 0, Math_PI * 2, 32, bone_ik_color, 1.0);
stack->skeleton->draw_line(Vector2(0, 0), Vector2(1, 0) * p_operation_bone->get_length(), bone_ik_color, 1.0);
}
}
Ref<SkeletonModificationStack2D> SkeletonModification2D::get_modification_stack() {
return stack;
}
void SkeletonModification2D::set_is_setup(bool p_setup) {
is_setup = p_setup;
}
bool SkeletonModification2D::get_is_setup() const {
return is_setup;
}
void SkeletonModification2D::set_execution_mode(int p_mode) {
execution_mode = p_mode;
}
int SkeletonModification2D::get_execution_mode() const {
return execution_mode;
}
void SkeletonModification2D::set_editor_draw_gizmo(bool p_draw_gizmo) {
editor_draw_gizmo = p_draw_gizmo;
#ifdef TOOLS_ENABLED
if (is_setup) {
if (stack) {
stack->set_editor_gizmos_dirty(true);
}
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2D::get_editor_draw_gizmo() const {
return editor_draw_gizmo;
}
void SkeletonModification2D::_bind_methods() {
GDVIRTUAL_BIND(_execute, "delta");
GDVIRTUAL_BIND(_setup_modification, "modification_stack")
GDVIRTUAL_BIND(_draw_editor_gizmo)
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification2D::set_enabled);
ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification2D::get_enabled);
ClassDB::bind_method(D_METHOD("get_modification_stack"), &SkeletonModification2D::get_modification_stack);
ClassDB::bind_method(D_METHOD("set_is_setup", "is_setup"), &SkeletonModification2D::set_is_setup);
ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModification2D::get_is_setup);
ClassDB::bind_method(D_METHOD("set_execution_mode", "execution_mode"), &SkeletonModification2D::set_execution_mode);
ClassDB::bind_method(D_METHOD("get_execution_mode"), &SkeletonModification2D::get_execution_mode);
ClassDB::bind_method(D_METHOD("clamp_angle", "angle", "min", "max", "invert"), &SkeletonModification2D::clamp_angle);
ClassDB::bind_method(D_METHOD("set_editor_draw_gizmo", "draw_gizmo"), &SkeletonModification2D::set_editor_draw_gizmo);
ClassDB::bind_method(D_METHOD("get_editor_draw_gizmo"), &SkeletonModification2D::get_editor_draw_gizmo);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process,physics_process"), "set_execution_mode", "get_execution_mode");
}
void SkeletonModification2D::reset_state() {
stack = nullptr;
is_setup = false;
}
SkeletonModification2D::SkeletonModification2D() {
stack = nullptr;
is_setup = false;
}

View file

@ -0,0 +1,91 @@
/**************************************************************************/
/* skeleton_modification_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_MODIFICATION_2D_H
#define SKELETON_MODIFICATION_2D_H
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/2d/skeleton/skeleton_modification_stack_2d.h"
///////////////////////////////////////
// SkeletonModification2D
///////////////////////////////////////
class SkeletonModificationStack2D;
class Bone2D;
class SkeletonModification2D : public Resource {
GDCLASS(SkeletonModification2D, Resource);
friend class Skeleton2D;
friend class Bone2D;
protected:
static void _bind_methods();
SkeletonModificationStack2D *stack = nullptr;
int execution_mode = 0; // 0 = process
bool enabled = true;
bool is_setup = false;
bool _print_execution_error(bool p_condition, String p_message);
virtual void reset_state() override;
GDVIRTUAL1(_execute, double)
GDVIRTUAL1(_setup_modification, Ref<SkeletonModificationStack2D>)
GDVIRTUAL0(_draw_editor_gizmo)
public:
virtual void _execute(float _delta);
virtual void _setup_modification(SkeletonModificationStack2D *p_stack);
virtual void _draw_editor_gizmo();
bool editor_draw_gizmo = false;
void set_editor_draw_gizmo(bool p_draw_gizmo);
bool get_editor_draw_gizmo() const;
void set_enabled(bool p_enabled);
bool get_enabled();
Ref<SkeletonModificationStack2D> get_modification_stack();
void set_is_setup(bool p_setup);
bool get_is_setup() const;
void set_execution_mode(int p_mode);
int get_execution_mode() const;
float clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert_clamp = false);
void editor_draw_angle_constraints(Bone2D *p_operation_bone, float p_min_bound, float p_max_bound, bool p_constraint_enabled, bool p_constraint_in_localspace, bool p_constraint_inverted);
SkeletonModification2D();
};
#endif // SKELETON_MODIFICATION_2D_H

View file

@ -0,0 +1,554 @@
/**************************************************************************/
/* skeleton_modification_2d_ccdik.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_modification_2d_ccdik.h"
#include "scene/2d/skeleton_2d.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
#endif // TOOLS_ENABLED
bool SkeletonModification2DCCDIK::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("joint_data/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, ccdik_data_chain.size(), false);
if (what == "bone2d_node") {
set_ccdik_joint_bone2d_node(which, p_value);
} else if (what == "bone_index") {
set_ccdik_joint_bone_index(which, p_value);
} else if (what == "rotate_from_joint") {
set_ccdik_joint_rotate_from_joint(which, p_value);
} else if (what == "enable_constraint") {
set_ccdik_joint_enable_constraint(which, p_value);
} else if (what == "constraint_angle_min") {
set_ccdik_joint_constraint_angle_min(which, Math::deg_to_rad(float(p_value)));
} else if (what == "constraint_angle_max") {
set_ccdik_joint_constraint_angle_max(which, Math::deg_to_rad(float(p_value)));
} else if (what == "constraint_angle_invert") {
set_ccdik_joint_constraint_angle_invert(which, p_value);
} else if (what == "constraint_in_localspace") {
set_ccdik_joint_constraint_in_localspace(which, p_value);
}
#ifdef TOOLS_ENABLED
else if (what.begins_with("editor_draw_gizmo")) {
set_ccdik_joint_editor_draw_gizmo(which, p_value);
}
#endif // TOOLS_ENABLED
else {
return false;
}
}
#ifdef TOOLS_ENABLED
else if (path.begins_with("editor/draw_gizmo")) {
set_editor_draw_gizmo(p_value);
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
bool SkeletonModification2DCCDIK::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("joint_data/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, ccdik_data_chain.size(), false);
if (what == "bone2d_node") {
r_ret = get_ccdik_joint_bone2d_node(which);
} else if (what == "bone_index") {
r_ret = get_ccdik_joint_bone_index(which);
} else if (what == "rotate_from_joint") {
r_ret = get_ccdik_joint_rotate_from_joint(which);
} else if (what == "enable_constraint") {
r_ret = get_ccdik_joint_enable_constraint(which);
} else if (what == "constraint_angle_min") {
r_ret = Math::rad_to_deg(get_ccdik_joint_constraint_angle_min(which));
} else if (what == "constraint_angle_max") {
r_ret = Math::rad_to_deg(get_ccdik_joint_constraint_angle_max(which));
} else if (what == "constraint_angle_invert") {
r_ret = get_ccdik_joint_constraint_angle_invert(which);
} else if (what == "constraint_in_localspace") {
r_ret = get_ccdik_joint_constraint_in_localspace(which);
}
#ifdef TOOLS_ENABLED
else if (what.begins_with("editor_draw_gizmo")) {
r_ret = get_ccdik_joint_editor_draw_gizmo(which);
}
#endif // TOOLS_ENABLED
else {
return false;
}
}
#ifdef TOOLS_ENABLED
else if (path.begins_with("editor/draw_gizmo")) {
r_ret = get_editor_draw_gizmo();
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
void SkeletonModification2DCCDIK::_get_property_list(List<PropertyInfo> *p_list) const {
for (int i = 0; i < ccdik_data_chain.size(); i++) {
String base_string = "joint_data/" + itos(i) + "/";
p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "rotate_from_joint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
if (ccdik_data_chain[i].enable_constraint) {
p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "editor_draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
#endif // TOOLS_ENABLED
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
#endif // TOOLS_ENABLED
}
void SkeletonModification2DCCDIK::_execute(float p_delta) {
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
"Modification is not setup and therefore cannot execute!");
if (!enabled) {
return;
}
if (target_node_cache.is_null()) {
WARN_PRINT_ONCE("Target cache is out of date. Attempting to update...");
update_target_cache();
return;
}
if (tip_node_cache.is_null()) {
WARN_PRINT_ONCE("Tip cache is out of date. Attempting to update...");
update_tip_cache();
return;
}
Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache));
if (!target || !target->is_inside_tree()) {
ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!");
return;
}
Node2D *tip = Object::cast_to<Node2D>(ObjectDB::get_instance(tip_node_cache));
if (!tip || !tip->is_inside_tree()) {
ERR_PRINT_ONCE("Tip node is not in the scene tree. Cannot execute modification!");
return;
}
for (int i = 0; i < ccdik_data_chain.size(); i++) {
_execute_ccdik_joint(i, target, tip);
}
}
void SkeletonModification2DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node2D *p_target, Node2D *p_tip) {
CCDIK_Joint_Data2D ccdik_data = ccdik_data_chain[p_joint_idx];
if (ccdik_data.bone_idx < 0 || ccdik_data.bone_idx > stack->skeleton->get_bone_count()) {
ERR_PRINT_ONCE("2D CCDIK joint: bone index not found!");
return;
}
Bone2D *operation_bone = stack->skeleton->get_bone(ccdik_data.bone_idx);
Transform2D operation_transform = operation_bone->get_global_transform();
if (ccdik_data.rotate_from_joint) {
// To rotate from the joint, simply look at the target!
operation_transform.set_rotation(
operation_transform.looking_at(p_target->get_global_position()).get_rotation() - operation_bone->get_bone_angle());
} else {
// How to rotate from the tip: get the difference of rotation needed from the tip to the target, from the perspective of the joint.
// Because we are only using the offset, we do not need to account for the bone angle of the Bone2D node.
float joint_to_tip = p_tip->get_global_position().angle_to_point(operation_transform.get_origin());
float joint_to_target = p_target->get_global_position().angle_to_point(operation_transform.get_origin());
operation_transform.set_rotation(
operation_transform.get_rotation() + (joint_to_target - joint_to_tip));
}
// Reset scale
operation_transform.set_scale(operation_bone->get_global_scale());
// Apply constraints in globalspace:
if (ccdik_data.enable_constraint && !ccdik_data.constraint_in_localspace) {
operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angle_invert));
}
// Convert from a global transform to a delta and then apply the delta to the local transform.
operation_bone->set_global_transform(operation_transform);
operation_transform = operation_bone->get_transform();
// Apply constraints in localspace:
if (ccdik_data.enable_constraint && ccdik_data.constraint_in_localspace) {
operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angle_invert));
}
// Set the local pose override, and to make sure child bones are also updated, set the transform of the bone.
stack->skeleton->set_bone_local_pose_override(ccdik_data.bone_idx, operation_transform, stack->strength, true);
operation_bone->set_transform(operation_transform);
operation_bone->notification(operation_bone->NOTIFICATION_TRANSFORM_CHANGED);
}
void SkeletonModification2DCCDIK::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack != nullptr) {
is_setup = true;
update_target_cache();
update_tip_cache();
}
}
void SkeletonModification2DCCDIK::_draw_editor_gizmo() {
if (!enabled || !is_setup) {
return;
}
for (int i = 0; i < ccdik_data_chain.size(); i++) {
if (!ccdik_data_chain[i].editor_draw_gizmo) {
continue;
}
Bone2D *operation_bone = stack->skeleton->get_bone(ccdik_data_chain[i].bone_idx);
editor_draw_angle_constraints(operation_bone, ccdik_data_chain[i].constraint_angle_min, ccdik_data_chain[i].constraint_angle_max,
ccdik_data_chain[i].enable_constraint, ccdik_data_chain[i].constraint_in_localspace, ccdik_data_chain[i].constraint_angle_invert);
}
}
void SkeletonModification2DCCDIK::update_target_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
}
return;
}
target_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(target_node)) {
Node *node = stack->skeleton->get_node(target_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update target cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update target cache: node is not in the scene tree!");
target_node_cache = node->get_instance_id();
}
}
}
}
void SkeletonModification2DCCDIK::update_tip_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!");
}
return;
}
tip_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(tip_node)) {
Node *node = stack->skeleton->get_node(tip_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update tip cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update tip cache: node is not in the scene tree!");
tip_node_cache = node->get_instance_id();
}
}
}
}
void SkeletonModification2DCCDIK::ccdik_joint_update_bone2d_cache(int p_joint_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!");
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!");
}
return;
}
ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(ccdik_data_chain[p_joint_idx].bone2d_node)) {
Node *node = stack->skeleton->get_node(ccdik_data_chain[p_joint_idx].bone2d_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in the scene tree!");
ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id();
Bone2D *bone = Object::cast_to<Bone2D>(node);
if (bone) {
ccdik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton();
} else {
ERR_FAIL_MSG("CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!");
}
}
}
}
}
void SkeletonModification2DCCDIK::set_target_node(const NodePath &p_target_node) {
target_node = p_target_node;
update_target_cache();
}
NodePath SkeletonModification2DCCDIK::get_target_node() const {
return target_node;
}
void SkeletonModification2DCCDIK::set_tip_node(const NodePath &p_tip_node) {
tip_node = p_tip_node;
update_tip_cache();
}
NodePath SkeletonModification2DCCDIK::get_tip_node() const {
return tip_node;
}
void SkeletonModification2DCCDIK::set_ccdik_data_chain_length(int p_length) {
ccdik_data_chain.resize(p_length);
notify_property_list_changed();
}
int SkeletonModification2DCCDIK::get_ccdik_data_chain_length() {
return ccdik_data_chain.size();
}
void SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!");
ccdik_data_chain.write[p_joint_idx].bone2d_node = p_target_node;
ccdik_joint_update_bone2d_cache(p_joint_idx);
notify_property_list_changed();
}
NodePath SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_node(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), NodePath(), "CCDIK joint out of range!");
return ccdik_data_chain[p_joint_idx].bone2d_node;
}
void SkeletonModification2DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCCDIK joint out of range!");
ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!");
if (is_setup) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
ccdik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx));
} else {
WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification...");
ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
} else {
ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
notify_property_list_changed();
}
int SkeletonModification2DCCDIK::get_ccdik_joint_bone_index(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), -1, "CCDIK joint out of range!");
return ccdik_data_chain[p_joint_idx].bone_idx;
}
void SkeletonModification2DCCDIK::set_ccdik_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!");
ccdik_data_chain.write[p_joint_idx].rotate_from_joint = p_rotate_from_joint;
}
bool SkeletonModification2DCCDIK::get_ccdik_joint_rotate_from_joint(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!");
return ccdik_data_chain[p_joint_idx].rotate_from_joint;
}
void SkeletonModification2DCCDIK::set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_constraint) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!");
ccdik_data_chain.write[p_joint_idx].enable_constraint = p_constraint;
notify_property_list_changed();
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2DCCDIK::get_ccdik_joint_enable_constraint(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!");
return ccdik_data_chain[p_joint_idx].enable_constraint;
}
void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_min(int p_joint_idx, float p_angle_min) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!");
ccdik_data_chain.write[p_joint_idx].constraint_angle_min = p_angle_min;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
float SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_min(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), 0.0, "CCDIK joint out of range!");
return ccdik_data_chain[p_joint_idx].constraint_angle_min;
}
void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_max(int p_joint_idx, float p_angle_max) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!");
ccdik_data_chain.write[p_joint_idx].constraint_angle_max = p_angle_max;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
float SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_max(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), 0.0, "CCDIK joint out of range!");
return ccdik_data_chain[p_joint_idx].constraint_angle_max;
}
void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_invert(int p_joint_idx, bool p_invert) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!");
ccdik_data_chain.write[p_joint_idx].constraint_angle_invert = p_invert;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_invert(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!");
return ccdik_data_chain[p_joint_idx].constraint_angle_invert;
}
void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!");
ccdik_data_chain.write[p_joint_idx].constraint_in_localspace = p_constraint_in_localspace;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2DCCDIK::get_ccdik_joint_constraint_in_localspace(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!");
return ccdik_data_chain[p_joint_idx].constraint_in_localspace;
}
void SkeletonModification2DCCDIK::set_ccdik_joint_editor_draw_gizmo(int p_joint_idx, bool p_draw_gizmo) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!");
ccdik_data_chain.write[p_joint_idx].editor_draw_gizmo = p_draw_gizmo;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2DCCDIK::get_ccdik_joint_editor_draw_gizmo(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!");
return ccdik_data_chain[p_joint_idx].editor_draw_gizmo;
}
void SkeletonModification2DCCDIK::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DCCDIK::set_target_node);
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DCCDIK::get_target_node);
ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification2DCCDIK::set_tip_node);
ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification2DCCDIK::get_tip_node);
ClassDB::bind_method(D_METHOD("set_ccdik_data_chain_length", "length"), &SkeletonModification2DCCDIK::set_ccdik_data_chain_length);
ClassDB::bind_method(D_METHOD("get_ccdik_data_chain_length"), &SkeletonModification2DCCDIK::get_ccdik_data_chain_length);
ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_node);
ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_node);
ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DCCDIK::set_ccdik_joint_bone_index);
ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_index", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_bone_index);
ClassDB::bind_method(D_METHOD("set_ccdik_joint_rotate_from_joint", "joint_idx", "rotate_from_joint"), &SkeletonModification2DCCDIK::set_ccdik_joint_rotate_from_joint);
ClassDB::bind_method(D_METHOD("get_ccdik_joint_rotate_from_joint", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_rotate_from_joint);
ClassDB::bind_method(D_METHOD("set_ccdik_joint_enable_constraint", "joint_idx", "enable_constraint"), &SkeletonModification2DCCDIK::set_ccdik_joint_enable_constraint);
ClassDB::bind_method(D_METHOD("get_ccdik_joint_enable_constraint", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_enable_constraint);
ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_min", "joint_idx", "angle_min"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_min);
ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_min", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_min);
ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_max", "joint_idx", "angle_max"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_max);
ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_max", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_max);
ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_invert", "joint_idx", "invert"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_invert);
ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_invert", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_invert);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_tip_node", "get_tip_node");
ADD_PROPERTY(PropertyInfo(Variant::INT, "ccdik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_ccdik_data_chain_length", "get_ccdik_data_chain_length");
}
SkeletonModification2DCCDIK::SkeletonModification2DCCDIK() {
stack = nullptr;
is_setup = false;
enabled = true;
editor_draw_gizmo = true;
}
SkeletonModification2DCCDIK::~SkeletonModification2DCCDIK() {
}

View file

@ -0,0 +1,116 @@
/**************************************************************************/
/* skeleton_modification_2d_ccdik.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_MODIFICATION_2D_CCDIK_H
#define SKELETON_MODIFICATION_2D_CCDIK_H
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/2d/skeleton/skeleton_modification_2d.h"
///////////////////////////////////////
// SkeletonModification2DCCDIK
///////////////////////////////////////
class SkeletonModification2DCCDIK : public SkeletonModification2D {
GDCLASS(SkeletonModification2DCCDIK, SkeletonModification2D);
private:
struct CCDIK_Joint_Data2D {
int bone_idx = -1;
NodePath bone2d_node;
ObjectID bone2d_node_cache;
bool rotate_from_joint = false;
bool enable_constraint = false;
float constraint_angle_min = 0;
float constraint_angle_max = (2.0 * Math_PI);
bool constraint_angle_invert = false;
bool constraint_in_localspace = true;
bool editor_draw_gizmo = true;
};
Vector<CCDIK_Joint_Data2D> ccdik_data_chain;
NodePath target_node;
ObjectID target_node_cache;
void update_target_cache();
NodePath tip_node;
ObjectID tip_node_cache;
void update_tip_cache();
void ccdik_joint_update_bone2d_cache(int p_joint_idx);
void _execute_ccdik_joint(int p_joint_idx, Node2D *p_target, Node2D *p_tip);
protected:
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:
void _execute(float p_delta) override;
void _setup_modification(SkeletonModificationStack2D *p_stack) override;
void _draw_editor_gizmo() override;
void set_target_node(const NodePath &p_target_node);
NodePath get_target_node() const;
void set_tip_node(const NodePath &p_tip_node);
NodePath get_tip_node() const;
int get_ccdik_data_chain_length();
void set_ccdik_data_chain_length(int p_new_length);
void set_ccdik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node);
NodePath get_ccdik_joint_bone2d_node(int p_joint_idx) const;
void set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx);
int get_ccdik_joint_bone_index(int p_joint_idx) const;
void set_ccdik_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint);
bool get_ccdik_joint_rotate_from_joint(int p_joint_idx) const;
void set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_constraint);
bool get_ccdik_joint_enable_constraint(int p_joint_idx) const;
void set_ccdik_joint_constraint_angle_min(int p_joint_idx, float p_angle_min);
float get_ccdik_joint_constraint_angle_min(int p_joint_idx) const;
void set_ccdik_joint_constraint_angle_max(int p_joint_idx, float p_angle_max);
float get_ccdik_joint_constraint_angle_max(int p_joint_idx) const;
void set_ccdik_joint_constraint_angle_invert(int p_joint_idx, bool p_invert);
bool get_ccdik_joint_constraint_angle_invert(int p_joint_idx) const;
void set_ccdik_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace);
bool get_ccdik_joint_constraint_in_localspace(int p_joint_idx) const;
void set_ccdik_joint_editor_draw_gizmo(int p_joint_idx, bool p_draw_gizmo);
bool get_ccdik_joint_editor_draw_gizmo(int p_joint_idx) const;
SkeletonModification2DCCDIK();
~SkeletonModification2DCCDIK();
};
#endif // SKELETON_MODIFICATION_2D_CCDIK_H

View file

@ -0,0 +1,461 @@
/**************************************************************************/
/* skeleton_modification_2d_fabrik.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_modification_2d_fabrik.h"
#include "scene/2d/skeleton_2d.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
#endif // TOOLS_ENABLED
bool SkeletonModification2DFABRIK::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("joint_data/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, fabrik_data_chain.size(), false);
if (what == "bone2d_node") {
set_fabrik_joint_bone2d_node(which, p_value);
} else if (what == "bone_index") {
set_fabrik_joint_bone_index(which, p_value);
} else if (what == "magnet_position") {
set_fabrik_joint_magnet_position(which, p_value);
} else if (what == "use_target_rotation") {
set_fabrik_joint_use_target_rotation(which, p_value);
} else {
return false;
}
} else {
return false;
}
return true;
}
bool SkeletonModification2DFABRIK::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("joint_data/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, fabrik_data_chain.size(), false);
if (what == "bone2d_node") {
r_ret = get_fabrik_joint_bone2d_node(which);
} else if (what == "bone_index") {
r_ret = get_fabrik_joint_bone_index(which);
} else if (what == "magnet_position") {
r_ret = get_fabrik_joint_magnet_position(which);
} else if (what == "use_target_rotation") {
r_ret = get_fabrik_joint_use_target_rotation(which);
} else {
return false;
}
} else {
return false;
}
return true;
}
void SkeletonModification2DFABRIK::_get_property_list(List<PropertyInfo> *p_list) const {
for (int i = 0; i < fabrik_data_chain.size(); i++) {
String base_string = "joint_data/" + itos(i) + "/";
p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT));
if (i > 0) {
p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "magnet_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
if (i == fabrik_data_chain.size() - 1) {
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_target_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
}
}
void SkeletonModification2DFABRIK::_execute(float p_delta) {
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
"Modification is not setup and therefore cannot execute!");
if (!enabled) {
return;
}
if (target_node_cache.is_null()) {
WARN_PRINT_ONCE("Target cache is out of date. Attempting to update...");
update_target_cache();
return;
}
if (fabrik_data_chain.size() <= 1) {
ERR_PRINT_ONCE("FABRIK requires at least two joints to operate! Cannot execute modification!");
return;
}
Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache));
if (!target || !target->is_inside_tree()) {
ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!");
return;
}
target_global_pose = target->get_global_transform();
if (fabrik_data_chain[0].bone2d_node_cache.is_null() && !fabrik_data_chain[0].bone2d_node.is_empty()) {
fabrik_joint_update_bone2d_cache(0);
WARN_PRINT("Bone2D cache for origin joint is out of date. Updating...");
}
Bone2D *origin_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[0].bone2d_node_cache));
if (!origin_bone2d_node || !origin_bone2d_node->is_inside_tree()) {
ERR_PRINT_ONCE("Origin joint's Bone2D node is not in the scene tree. Cannot execute modification!");
return;
}
origin_global_pose = origin_bone2d_node->get_global_transform();
if (fabrik_transform_chain.size() != fabrik_data_chain.size()) {
fabrik_transform_chain.resize(fabrik_data_chain.size());
}
for (int i = 0; i < fabrik_data_chain.size(); i++) {
// Update the transform chain
if (fabrik_data_chain[i].bone2d_node_cache.is_null() && !fabrik_data_chain[i].bone2d_node.is_empty()) {
WARN_PRINT_ONCE("Bone2D cache for joint " + itos(i) + " is out of date.. Attempting to update...");
fabrik_joint_update_bone2d_cache(i);
}
Bone2D *joint_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache));
if (!joint_bone2d_node) {
ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set! Cannot execute modification!");
return;
}
fabrik_transform_chain.write[i] = joint_bone2d_node->get_global_transform();
}
Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[fabrik_data_chain.size() - 1].bone2d_node_cache));
float final_bone2d_angle = final_bone2d_node->get_global_rotation();
if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) {
final_bone2d_angle = target_global_pose.get_rotation();
}
Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle));
float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y);
float target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_position());
chain_iterations = 0;
while (target_distance > chain_tolarance) {
chain_backwards();
chain_forwards();
final_bone2d_angle = final_bone2d_node->get_global_rotation();
if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) {
final_bone2d_angle = target_global_pose.get_rotation();
}
final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle));
target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_position());
chain_iterations += 1;
if (chain_iterations >= chain_max_iterations) {
break;
}
}
// Apply all of the saved transforms to the Bone2D nodes
for (int i = 0; i < fabrik_data_chain.size(); i++) {
Bone2D *joint_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache));
if (!joint_bone2d_node) {
ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set!");
continue;
}
Transform2D chain_trans = fabrik_transform_chain[i];
// Apply rotation
if (i + 1 < fabrik_data_chain.size()) {
chain_trans = chain_trans.looking_at(fabrik_transform_chain[i + 1].get_origin());
} else {
if (fabrik_data_chain[i].use_target_rotation) {
chain_trans.set_rotation(target_global_pose.get_rotation());
} else {
chain_trans = chain_trans.looking_at(target_global_pose.get_origin());
}
}
// Adjust for the bone angle
chain_trans.set_rotation(chain_trans.get_rotation() - joint_bone2d_node->get_bone_angle());
// Reset scale
chain_trans.set_scale(joint_bone2d_node->get_global_scale());
// Apply to the bone, and to the override
joint_bone2d_node->set_global_transform(chain_trans);
stack->skeleton->set_bone_local_pose_override(fabrik_data_chain[i].bone_idx, joint_bone2d_node->get_transform(), stack->strength, true);
}
}
void SkeletonModification2DFABRIK::chain_backwards() {
int final_joint_index = fabrik_data_chain.size() - 1;
Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone2d_node_cache));
Transform2D final_bone2d_trans = fabrik_transform_chain[final_joint_index];
// Apply magnet position
if (final_joint_index != 0) {
final_bone2d_trans.set_origin(final_bone2d_trans.get_origin() + fabrik_data_chain[final_joint_index].magnet_position);
}
// Set the rotation of the tip bone
final_bone2d_trans = final_bone2d_trans.looking_at(target_global_pose.get_origin());
// Set the position of the tip bone
float final_bone2d_angle = final_bone2d_trans.get_rotation();
if (fabrik_data_chain[final_joint_index].use_target_rotation) {
final_bone2d_angle = target_global_pose.get_rotation();
}
Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle));
float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y);
final_bone2d_trans.set_origin(target_global_pose.get_origin() - (final_bone2d_direction * final_bone2d_length));
// Save the transform
fabrik_transform_chain.write[final_joint_index] = final_bone2d_trans;
int i = final_joint_index;
while (i >= 1) {
Transform2D previous_pose = fabrik_transform_chain[i];
i -= 1;
Bone2D *current_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache));
Transform2D current_pose = fabrik_transform_chain[i];
// Apply magnet position
if (i != 0) {
current_pose.set_origin(current_pose.get_origin() + fabrik_data_chain[i].magnet_position);
}
float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y);
float length = current_bone2d_node_length / (current_pose.get_origin().distance_to(previous_pose.get_origin()));
Vector2 finish_position = previous_pose.get_origin().lerp(current_pose.get_origin(), length);
current_pose.set_origin(finish_position);
// Save the transform
fabrik_transform_chain.write[i] = current_pose;
}
}
void SkeletonModification2DFABRIK::chain_forwards() {
Transform2D origin_bone2d_trans = fabrik_transform_chain[0];
origin_bone2d_trans.set_origin(origin_global_pose.get_origin());
// Save the position
fabrik_transform_chain.write[0] = origin_bone2d_trans;
for (int i = 0; i < fabrik_data_chain.size() - 1; i++) {
Bone2D *current_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache));
Transform2D current_pose = fabrik_transform_chain[i];
Transform2D next_pose = fabrik_transform_chain[i + 1];
float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y);
float length = current_bone2d_node_length / (next_pose.get_origin().distance_to(current_pose.get_origin()));
Vector2 finish_position = current_pose.get_origin().lerp(next_pose.get_origin(), length);
current_pose.set_origin(finish_position);
// Apply to the bone
fabrik_transform_chain.write[i + 1] = current_pose;
}
}
void SkeletonModification2DFABRIK::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack != nullptr) {
is_setup = true;
if (stack->skeleton) {
for (int i = 0; i < fabrik_data_chain.size(); i++) {
fabrik_joint_update_bone2d_cache(i);
}
}
update_target_cache();
}
}
void SkeletonModification2DFABRIK::update_target_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
}
return;
}
target_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(target_node)) {
Node *node = stack->skeleton->get_node(target_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update target cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update target cache: node is not in scene tree!");
target_node_cache = node->get_instance_id();
}
}
}
}
void SkeletonModification2DFABRIK::fabrik_joint_update_bone2d_cache(int p_joint_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!");
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!");
}
return;
}
fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(fabrik_data_chain[p_joint_idx].bone2d_node)) {
Node *node = stack->skeleton->get_node(fabrik_data_chain[p_joint_idx].bone2d_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!");
fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id();
Bone2D *bone = Object::cast_to<Bone2D>(node);
if (bone) {
fabrik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton();
} else {
ERR_FAIL_MSG("FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!");
}
}
}
}
}
void SkeletonModification2DFABRIK::set_target_node(const NodePath &p_target_node) {
target_node = p_target_node;
update_target_cache();
}
NodePath SkeletonModification2DFABRIK::get_target_node() const {
return target_node;
}
void SkeletonModification2DFABRIK::set_fabrik_data_chain_length(int p_length) {
fabrik_data_chain.resize(p_length);
notify_property_list_changed();
}
int SkeletonModification2DFABRIK::get_fabrik_data_chain_length() {
return fabrik_data_chain.size();
}
void SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!");
fabrik_data_chain.write[p_joint_idx].bone2d_node = p_target_node;
fabrik_joint_update_bone2d_cache(p_joint_idx);
notify_property_list_changed();
}
NodePath SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), NodePath(), "FABRIK joint out of range!");
return fabrik_data_chain[p_joint_idx].bone2d_node;
}
void SkeletonModification2DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!");
ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!");
if (is_setup) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
fabrik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx));
} else {
WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification...");
fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
} else {
fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
notify_property_list_changed();
}
int SkeletonModification2DFABRIK::get_fabrik_joint_bone_index(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), -1, "FABRIK joint out of range!");
return fabrik_data_chain[p_joint_idx].bone_idx;
}
void SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!");
fabrik_data_chain.write[p_joint_idx].magnet_position = p_magnet_position;
}
Vector2 SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), Vector2(), "FABRIK joint out of range!");
return fabrik_data_chain[p_joint_idx].magnet_position;
}
void SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!");
fabrik_data_chain.write[p_joint_idx].use_target_rotation = p_use_target_rotation;
}
bool SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), false, "FABRIK joint out of range!");
return fabrik_data_chain[p_joint_idx].use_target_rotation;
}
void SkeletonModification2DFABRIK::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DFABRIK::set_target_node);
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DFABRIK::get_target_node);
ClassDB::bind_method(D_METHOD("set_fabrik_data_chain_length", "length"), &SkeletonModification2DFABRIK::set_fabrik_data_chain_length);
ClassDB::bind_method(D_METHOD("get_fabrik_data_chain_length"), &SkeletonModification2DFABRIK::get_fabrik_data_chain_length);
ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node);
ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node);
ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone_index);
ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_index", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone_index);
ClassDB::bind_method(D_METHOD("set_fabrik_joint_magnet_position", "joint_idx", "magnet_position"), &SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position);
ClassDB::bind_method(D_METHOD("get_fabrik_joint_magnet_position", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position);
ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_target_rotation", "joint_idx", "use_target_rotation"), &SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation);
ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_target_rotation", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node");
ADD_PROPERTY(PropertyInfo(Variant::INT, "fabrik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_fabrik_data_chain_length", "get_fabrik_data_chain_length");
}
SkeletonModification2DFABRIK::SkeletonModification2DFABRIK() {
stack = nullptr;
is_setup = false;
enabled = true;
editor_draw_gizmo = false;
}
SkeletonModification2DFABRIK::~SkeletonModification2DFABRIK() {
}

View file

@ -0,0 +1,108 @@
/**************************************************************************/
/* skeleton_modification_2d_fabrik.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_MODIFICATION_2D_FABRIK_H
#define SKELETON_MODIFICATION_2D_FABRIK_H
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/2d/skeleton/skeleton_modification_2d.h"
///////////////////////////////////////
// SkeletonModification2DFABRIK
///////////////////////////////////////
class SkeletonModification2DFABRIK : public SkeletonModification2D {
GDCLASS(SkeletonModification2DFABRIK, SkeletonModification2D);
private:
struct FABRIK_Joint_Data2D {
int bone_idx = -1;
NodePath bone2d_node;
ObjectID bone2d_node_cache;
Vector2 magnet_position = Vector2(0, 0);
bool use_target_rotation = false;
bool editor_draw_gizmo = true;
};
Vector<FABRIK_Joint_Data2D> fabrik_data_chain;
// Unlike in 3D, we need a vector of Transform2D objects to perform FABRIK.
// This is because FABRIK (unlike CCDIK) needs to operate on transforms that are NOT
// affected by each other, making the transforms stored in Bone2D unusable, as well as those in Skeleton2D.
// For this reason, this modification stores a vector of Transform2Ds used for the calculations, which are then applied at the end.
Vector<Transform2D> fabrik_transform_chain;
NodePath target_node;
ObjectID target_node_cache;
void update_target_cache();
float chain_tolarance = 0.01;
int chain_max_iterations = 10;
int chain_iterations = 0;
Transform2D target_global_pose;
Transform2D origin_global_pose;
void fabrik_joint_update_bone2d_cache(int p_joint_idx);
void chain_backwards();
void chain_forwards();
protected:
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:
void _execute(float p_delta) override;
void _setup_modification(SkeletonModificationStack2D *p_stack) override;
void set_target_node(const NodePath &p_target_node);
NodePath get_target_node() const;
int get_fabrik_data_chain_length();
void set_fabrik_data_chain_length(int p_new_length);
void set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node);
NodePath get_fabrik_joint_bone2d_node(int p_joint_idx) const;
void set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx);
int get_fabrik_joint_bone_index(int p_joint_idx) const;
void set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position);
Vector2 get_fabrik_joint_magnet_position(int p_joint_idx) const;
void set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation);
bool get_fabrik_joint_use_target_rotation(int p_joint_idx) const;
SkeletonModification2DFABRIK();
~SkeletonModification2DFABRIK();
};
#endif // SKELETON_MODIFICATION_2D_FABRIK_H

View file

@ -0,0 +1,577 @@
/**************************************************************************/
/* skeleton_modification_2d_jiggle.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_modification_2d_jiggle.h"
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/world_2d.h"
bool SkeletonModification2DJiggle::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("joint_data/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false);
if (what == "bone2d_node") {
set_jiggle_joint_bone2d_node(which, p_value);
} else if (what == "bone_index") {
set_jiggle_joint_bone_index(which, p_value);
} else if (what == "override_defaults") {
set_jiggle_joint_override(which, p_value);
} else if (what == "stiffness") {
set_jiggle_joint_stiffness(which, p_value);
} else if (what == "mass") {
set_jiggle_joint_mass(which, p_value);
} else if (what == "damping") {
set_jiggle_joint_damping(which, p_value);
} else if (what == "use_gravity") {
set_jiggle_joint_use_gravity(which, p_value);
} else if (what == "gravity") {
set_jiggle_joint_gravity(which, p_value);
} else {
return false;
}
} else if (path == "use_colliders") {
set_use_colliders(p_value);
} else if (path == "collision_mask") {
set_collision_mask(p_value);
} else {
return false;
}
return true;
}
bool SkeletonModification2DJiggle::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("joint_data/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false);
if (what == "bone2d_node") {
r_ret = get_jiggle_joint_bone2d_node(which);
} else if (what == "bone_index") {
r_ret = get_jiggle_joint_bone_index(which);
} else if (what == "override_defaults") {
r_ret = get_jiggle_joint_override(which);
} else if (what == "stiffness") {
r_ret = get_jiggle_joint_stiffness(which);
} else if (what == "mass") {
r_ret = get_jiggle_joint_mass(which);
} else if (what == "damping") {
r_ret = get_jiggle_joint_damping(which);
} else if (what == "use_gravity") {
r_ret = get_jiggle_joint_use_gravity(which);
} else if (what == "gravity") {
r_ret = get_jiggle_joint_gravity(which);
} else {
return false;
}
} else if (path == "use_colliders") {
r_ret = get_use_colliders();
} else if (path == "collision_mask") {
r_ret = get_collision_mask();
} else {
return false;
}
return true;
}
void SkeletonModification2DJiggle::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::BOOL, "use_colliders", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
if (use_colliders) {
p_list->push_back(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS, "", PROPERTY_USAGE_DEFAULT));
}
for (int i = 0; i < jiggle_data_chain.size(); i++) {
String base_string = "joint_data/" + itos(i) + "/";
p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "override_defaults", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
if (jiggle_data_chain[i].override_defaults) {
p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "stiffness", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "mass", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
if (jiggle_data_chain[i].use_gravity) {
p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
}
}
}
void SkeletonModification2DJiggle::_execute(float p_delta) {
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
"Modification is not setup and therefore cannot execute!");
if (!enabled) {
return;
}
if (target_node_cache.is_null()) {
WARN_PRINT_ONCE("Target cache is out of date. Attempting to update...");
update_target_cache();
return;
}
Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache));
if (!target || !target->is_inside_tree()) {
ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!");
return;
}
for (int i = 0; i < jiggle_data_chain.size(); i++) {
_execute_jiggle_joint(i, target, p_delta);
}
}
void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta) {
// Adopted from: https://wiki.unity3d.com/index.php/JiggleBone
// With modifications by TwistedTwigleg.
if (jiggle_data_chain[p_joint_idx].bone_idx <= -1 || jiggle_data_chain[p_joint_idx].bone_idx > stack->skeleton->get_bone_count()) {
ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " bone index is invalid. Cannot execute modification on joint...");
return;
}
if (jiggle_data_chain[p_joint_idx].bone2d_node_cache.is_null() && !jiggle_data_chain[p_joint_idx].bone2d_node.is_empty()) {
WARN_PRINT_ONCE("Bone2D cache for joint " + itos(p_joint_idx) + " is out of date. Updating...");
jiggle_joint_update_bone2d_cache(p_joint_idx);
}
Bone2D *operation_bone = stack->skeleton->get_bone(jiggle_data_chain[p_joint_idx].bone_idx);
if (!operation_bone) {
ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " does not have a Bone2D node or it cannot be found!");
return;
}
Transform2D operation_bone_trans = operation_bone->get_global_transform();
Vector2 target_position = p_target->get_global_position();
jiggle_data_chain.write[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta;
if (jiggle_data_chain[p_joint_idx].use_gravity) {
jiggle_data_chain.write[p_joint_idx].force += jiggle_data_chain[p_joint_idx].gravity * p_delta;
}
jiggle_data_chain.write[p_joint_idx].acceleration = jiggle_data_chain[p_joint_idx].force / jiggle_data_chain[p_joint_idx].mass;
jiggle_data_chain.write[p_joint_idx].velocity += jiggle_data_chain[p_joint_idx].acceleration * (1 - jiggle_data_chain[p_joint_idx].damping);
jiggle_data_chain.write[p_joint_idx].dynamic_position += jiggle_data_chain[p_joint_idx].velocity + jiggle_data_chain[p_joint_idx].force;
jiggle_data_chain.write[p_joint_idx].dynamic_position += operation_bone_trans.get_origin() - jiggle_data_chain[p_joint_idx].last_position;
jiggle_data_chain.write[p_joint_idx].last_position = operation_bone_trans.get_origin();
// Collision detection/response
if (use_colliders) {
if (execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process) {
Ref<World2D> world_2d = stack->skeleton->get_world_2d();
ERR_FAIL_COND(world_2d.is_null());
PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space());
PhysicsDirectSpaceState2D::RayResult ray_result;
PhysicsDirectSpaceState2D::RayParameters ray_params;
ray_params.from = operation_bone_trans.get_origin();
ray_params.to = jiggle_data_chain[p_joint_idx].dynamic_position;
ray_params.collision_mask = collision_mask;
// Add exception support?
bool ray_hit = space_state->intersect_ray(ray_params, ray_result);
if (ray_hit) {
jiggle_data_chain.write[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position;
jiggle_data_chain.write[p_joint_idx].acceleration = Vector2(0, 0);
jiggle_data_chain.write[p_joint_idx].velocity = Vector2(0, 0);
} else {
jiggle_data_chain.write[p_joint_idx].last_noncollision_position = jiggle_data_chain[p_joint_idx].dynamic_position;
}
} else {
WARN_PRINT_ONCE("Jiggle 2D modifier: You cannot detect colliders without the stack mode being set to _physics_process!");
}
}
// Rotate the bone using the dynamic position!
operation_bone_trans = operation_bone_trans.looking_at(jiggle_data_chain[p_joint_idx].dynamic_position);
operation_bone_trans.set_rotation(operation_bone_trans.get_rotation() - operation_bone->get_bone_angle());
// Reset scale
operation_bone_trans.set_scale(operation_bone->get_global_scale());
operation_bone->set_global_transform(operation_bone_trans);
stack->skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, operation_bone->get_transform(), stack->strength, true);
}
void SkeletonModification2DJiggle::_update_jiggle_joint_data() {
for (int i = 0; i < jiggle_data_chain.size(); i++) {
if (!jiggle_data_chain[i].override_defaults) {
set_jiggle_joint_stiffness(i, stiffness);
set_jiggle_joint_mass(i, mass);
set_jiggle_joint_damping(i, damping);
set_jiggle_joint_use_gravity(i, use_gravity);
set_jiggle_joint_gravity(i, gravity);
}
}
}
void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack) {
is_setup = true;
if (stack->skeleton) {
for (int i = 0; i < jiggle_data_chain.size(); i++) {
int bone_idx = jiggle_data_chain[i].bone_idx;
if (bone_idx > 0 && bone_idx < stack->skeleton->get_bone_count()) {
Bone2D *bone2d_node = stack->skeleton->get_bone(bone_idx);
jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_position();
}
jiggle_joint_update_bone2d_cache(i);
}
}
update_target_cache();
}
}
void SkeletonModification2DJiggle::update_target_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
}
return;
}
target_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(target_node)) {
Node *node = stack->skeleton->get_node(target_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update target cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update target cache: node is not in scene tree!");
target_node_cache = node->get_instance_id();
}
}
}
}
void SkeletonModification2DJiggle::jiggle_joint_update_bone2d_cache(int p_joint_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Cannot update bone2d cache: joint index out of range!");
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!");
}
return;
}
jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(jiggle_data_chain[p_joint_idx].bone2d_node)) {
Node *node = stack->skeleton->get_node(jiggle_data_chain[p_joint_idx].bone2d_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!");
jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id();
Bone2D *bone = Object::cast_to<Bone2D>(node);
if (bone) {
jiggle_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton();
} else {
ERR_FAIL_MSG("Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!");
}
}
}
}
}
void SkeletonModification2DJiggle::set_target_node(const NodePath &p_target_node) {
target_node = p_target_node;
update_target_cache();
}
NodePath SkeletonModification2DJiggle::get_target_node() const {
return target_node;
}
void SkeletonModification2DJiggle::set_stiffness(float p_stiffness) {
ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!");
stiffness = p_stiffness;
_update_jiggle_joint_data();
}
float SkeletonModification2DJiggle::get_stiffness() const {
return stiffness;
}
void SkeletonModification2DJiggle::set_mass(float p_mass) {
ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!");
mass = p_mass;
_update_jiggle_joint_data();
}
float SkeletonModification2DJiggle::get_mass() const {
return mass;
}
void SkeletonModification2DJiggle::set_damping(float p_damping) {
ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!");
ERR_FAIL_COND_MSG(p_damping > 1, "Damping cannot be more than one!");
damping = p_damping;
_update_jiggle_joint_data();
}
float SkeletonModification2DJiggle::get_damping() const {
return damping;
}
void SkeletonModification2DJiggle::set_use_gravity(bool p_use_gravity) {
use_gravity = p_use_gravity;
_update_jiggle_joint_data();
}
bool SkeletonModification2DJiggle::get_use_gravity() const {
return use_gravity;
}
void SkeletonModification2DJiggle::set_gravity(Vector2 p_gravity) {
gravity = p_gravity;
_update_jiggle_joint_data();
}
Vector2 SkeletonModification2DJiggle::get_gravity() const {
return gravity;
}
void SkeletonModification2DJiggle::set_use_colliders(bool p_use_colliders) {
use_colliders = p_use_colliders;
notify_property_list_changed();
}
bool SkeletonModification2DJiggle::get_use_colliders() const {
return use_colliders;
}
void SkeletonModification2DJiggle::set_collision_mask(int p_mask) {
collision_mask = p_mask;
}
int SkeletonModification2DJiggle::get_collision_mask() const {
return collision_mask;
}
// Jiggle joint data functions
int SkeletonModification2DJiggle::get_jiggle_data_chain_length() {
return jiggle_data_chain.size();
}
void SkeletonModification2DJiggle::set_jiggle_data_chain_length(int p_length) {
ERR_FAIL_COND(p_length < 0);
jiggle_data_chain.resize(p_length);
notify_property_list_changed();
}
void SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) {
ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!");
jiggle_data_chain.write[p_joint_idx].bone2d_node = p_target_node;
jiggle_joint_update_bone2d_cache(p_joint_idx);
notify_property_list_changed();
}
NodePath SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), NodePath(), "Jiggle joint out of range!");
return jiggle_data_chain[p_joint_idx].bone2d_node;
}
void SkeletonModification2DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!");
ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!");
if (is_setup) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
jiggle_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx));
} else {
WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification...");
jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
} else {
jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
notify_property_list_changed();
}
int SkeletonModification2DJiggle::get_jiggle_joint_bone_index(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), -1, "Jiggle joint out of range!");
return jiggle_data_chain[p_joint_idx].bone_idx;
}
void SkeletonModification2DJiggle::set_jiggle_joint_override(int p_joint_idx, bool p_override) {
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].override_defaults = p_override;
_update_jiggle_joint_data();
notify_property_list_changed();
}
bool SkeletonModification2DJiggle::get_jiggle_joint_override(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false);
return jiggle_data_chain[p_joint_idx].override_defaults;
}
void SkeletonModification2DJiggle::set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness) {
ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!");
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].stiffness = p_stiffness;
}
float SkeletonModification2DJiggle::get_jiggle_joint_stiffness(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1);
return jiggle_data_chain[p_joint_idx].stiffness;
}
void SkeletonModification2DJiggle::set_jiggle_joint_mass(int p_joint_idx, float p_mass) {
ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!");
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].mass = p_mass;
}
float SkeletonModification2DJiggle::get_jiggle_joint_mass(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1);
return jiggle_data_chain[p_joint_idx].mass;
}
void SkeletonModification2DJiggle::set_jiggle_joint_damping(int p_joint_idx, float p_damping) {
ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!");
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].damping = p_damping;
}
float SkeletonModification2DJiggle::get_jiggle_joint_damping(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1);
return jiggle_data_chain[p_joint_idx].damping;
}
void SkeletonModification2DJiggle::set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity) {
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].use_gravity = p_use_gravity;
notify_property_list_changed();
}
bool SkeletonModification2DJiggle::get_jiggle_joint_use_gravity(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false);
return jiggle_data_chain[p_joint_idx].use_gravity;
}
void SkeletonModification2DJiggle::set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity) {
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].gravity = p_gravity;
}
Vector2 SkeletonModification2DJiggle::get_jiggle_joint_gravity(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), Vector2(0, 0));
return jiggle_data_chain[p_joint_idx].gravity;
}
void SkeletonModification2DJiggle::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DJiggle::set_target_node);
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DJiggle::get_target_node);
ClassDB::bind_method(D_METHOD("set_jiggle_data_chain_length", "length"), &SkeletonModification2DJiggle::set_jiggle_data_chain_length);
ClassDB::bind_method(D_METHOD("get_jiggle_data_chain_length"), &SkeletonModification2DJiggle::get_jiggle_data_chain_length);
ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &SkeletonModification2DJiggle::set_stiffness);
ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification2DJiggle::get_stiffness);
ClassDB::bind_method(D_METHOD("set_mass", "mass"), &SkeletonModification2DJiggle::set_mass);
ClassDB::bind_method(D_METHOD("get_mass"), &SkeletonModification2DJiggle::get_mass);
ClassDB::bind_method(D_METHOD("set_damping", "damping"), &SkeletonModification2DJiggle::set_damping);
ClassDB::bind_method(D_METHOD("get_damping"), &SkeletonModification2DJiggle::get_damping);
ClassDB::bind_method(D_METHOD("set_use_gravity", "use_gravity"), &SkeletonModification2DJiggle::set_use_gravity);
ClassDB::bind_method(D_METHOD("get_use_gravity"), &SkeletonModification2DJiggle::get_use_gravity);
ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &SkeletonModification2DJiggle::set_gravity);
ClassDB::bind_method(D_METHOD("get_gravity"), &SkeletonModification2DJiggle::get_gravity);
ClassDB::bind_method(D_METHOD("set_use_colliders", "use_colliders"), &SkeletonModification2DJiggle::set_use_colliders);
ClassDB::bind_method(D_METHOD("get_use_colliders"), &SkeletonModification2DJiggle::get_use_colliders);
ClassDB::bind_method(D_METHOD("set_collision_mask", "collision_mask"), &SkeletonModification2DJiggle::set_collision_mask);
ClassDB::bind_method(D_METHOD("get_collision_mask"), &SkeletonModification2DJiggle::get_collision_mask);
// Jiggle joint data functions
ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone2d_node", "joint_idx", "bone2d_node"), &SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone2d_node", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DJiggle::set_jiggle_joint_bone_index);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_index", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone_index);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_override", "joint_idx", "override"), &SkeletonModification2DJiggle::set_jiggle_joint_override);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_override", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_override);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification2DJiggle::set_jiggle_joint_stiffness);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_stiffness", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_stiffness);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_mass", "joint_idx", "mass"), &SkeletonModification2DJiggle::set_jiggle_joint_mass);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_mass", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_mass);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_damping", "joint_idx", "damping"), &SkeletonModification2DJiggle::set_jiggle_joint_damping);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_damping", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_damping);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_use_gravity);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_use_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_use_gravity);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_gravity", "joint_idx", "gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_gravity);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_gravity);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node");
ADD_PROPERTY(PropertyInfo(Variant::INT, "jiggle_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_jiggle_data_chain_length", "get_jiggle_data_chain_length");
ADD_GROUP("Default Joint Settings", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness"), "set_stiffness", "get_stiffness");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01"), "set_damping", "get_damping");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_gravity"), "set_use_gravity", "get_use_gravity");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity");
ADD_GROUP("", "");
}
SkeletonModification2DJiggle::SkeletonModification2DJiggle() {
stack = nullptr;
is_setup = false;
jiggle_data_chain = Vector<Jiggle_Joint_Data2D>();
stiffness = 3;
mass = 0.75;
damping = 0.75;
use_gravity = false;
gravity = Vector2(0, 6.0);
enabled = true;
editor_draw_gizmo = false; // Nothing to really show in a gizmo right now.
}
SkeletonModification2DJiggle::~SkeletonModification2DJiggle() {
}

View file

@ -0,0 +1,139 @@
/**************************************************************************/
/* skeleton_modification_2d_jiggle.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_MODIFICATION_2D_JIGGLE_H
#define SKELETON_MODIFICATION_2D_JIGGLE_H
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/2d/skeleton/skeleton_modification_2d.h"
///////////////////////////////////////
// SkeletonModification2DJIGGLE
///////////////////////////////////////
class SkeletonModification2DJiggle : public SkeletonModification2D {
GDCLASS(SkeletonModification2DJiggle, SkeletonModification2D);
private:
struct Jiggle_Joint_Data2D {
int bone_idx = -1;
NodePath bone2d_node;
ObjectID bone2d_node_cache;
bool override_defaults = false;
float stiffness = 3;
float mass = 0.75;
float damping = 0.75;
bool use_gravity = false;
Vector2 gravity = Vector2(0, 6.0);
Vector2 force = Vector2(0, 0);
Vector2 acceleration = Vector2(0, 0);
Vector2 velocity = Vector2(0, 0);
Vector2 last_position = Vector2(0, 0);
Vector2 dynamic_position = Vector2(0, 0);
Vector2 last_noncollision_position = Vector2(0, 0);
};
Vector<Jiggle_Joint_Data2D> jiggle_data_chain;
NodePath target_node;
ObjectID target_node_cache;
void update_target_cache();
float stiffness = 3;
float mass = 0.75;
float damping = 0.75;
bool use_gravity = false;
Vector2 gravity = Vector2(0, 6);
bool use_colliders = false;
uint32_t collision_mask = 1;
void jiggle_joint_update_bone2d_cache(int p_joint_idx);
void _execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta);
void _update_jiggle_joint_data();
protected:
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:
void _execute(float p_delta) override;
void _setup_modification(SkeletonModificationStack2D *p_stack) override;
void set_target_node(const NodePath &p_target_node);
NodePath get_target_node() const;
void set_stiffness(float p_stiffness);
float get_stiffness() const;
void set_mass(float p_mass);
float get_mass() const;
void set_damping(float p_damping);
float get_damping() const;
void set_use_gravity(bool p_use_gravity);
bool get_use_gravity() const;
void set_gravity(Vector2 p_gravity);
Vector2 get_gravity() const;
void set_use_colliders(bool p_use_colliders);
bool get_use_colliders() const;
void set_collision_mask(int p_mask);
int get_collision_mask() const;
int get_jiggle_data_chain_length();
void set_jiggle_data_chain_length(int p_new_length);
void set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node);
NodePath get_jiggle_joint_bone2d_node(int p_joint_idx) const;
void set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx);
int get_jiggle_joint_bone_index(int p_joint_idx) const;
void set_jiggle_joint_override(int p_joint_idx, bool p_override);
bool get_jiggle_joint_override(int p_joint_idx) const;
void set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness);
float get_jiggle_joint_stiffness(int p_joint_idx) const;
void set_jiggle_joint_mass(int p_joint_idx, float p_mass);
float get_jiggle_joint_mass(int p_joint_idx) const;
void set_jiggle_joint_damping(int p_joint_idx, float p_damping);
float get_jiggle_joint_damping(int p_joint_idx) const;
void set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity);
bool get_jiggle_joint_use_gravity(int p_joint_idx) const;
void set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity);
Vector2 get_jiggle_joint_gravity(int p_joint_idx) const;
SkeletonModification2DJiggle();
~SkeletonModification2DJiggle();
};
#endif // SKELETON_MODIFICATION_2D_JIGGLE_H

View file

@ -0,0 +1,414 @@
/**************************************************************************/
/* skeleton_modification_2d_lookat.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_modification_2d_lookat.h"
#include "scene/2d/skeleton_2d.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
#endif // TOOLS_ENABLED
bool SkeletonModification2DLookAt::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("enable_constraint")) {
set_enable_constraint(p_value);
} else if (path.begins_with("constraint_angle_min")) {
set_constraint_angle_min(Math::deg_to_rad(float(p_value)));
} else if (path.begins_with("constraint_angle_max")) {
set_constraint_angle_max(Math::deg_to_rad(float(p_value)));
} else if (path.begins_with("constraint_angle_invert")) {
set_constraint_angle_invert(p_value);
} else if (path.begins_with("constraint_in_localspace")) {
set_constraint_in_localspace(p_value);
} else if (path.begins_with("additional_rotation")) {
set_additional_rotation(Math::deg_to_rad(float(p_value)));
}
#ifdef TOOLS_ENABLED
else if (path.begins_with("editor/draw_gizmo")) {
set_editor_draw_gizmo(p_value);
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
bool SkeletonModification2DLookAt::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("enable_constraint")) {
r_ret = get_enable_constraint();
} else if (path.begins_with("constraint_angle_min")) {
r_ret = Math::rad_to_deg(get_constraint_angle_min());
} else if (path.begins_with("constraint_angle_max")) {
r_ret = Math::rad_to_deg(get_constraint_angle_max());
} else if (path.begins_with("constraint_angle_invert")) {
r_ret = get_constraint_angle_invert();
} else if (path.begins_with("constraint_in_localspace")) {
r_ret = get_constraint_in_localspace();
} else if (path.begins_with("additional_rotation")) {
r_ret = Math::rad_to_deg(get_additional_rotation());
}
#ifdef TOOLS_ENABLED
else if (path.begins_with("editor/draw_gizmo")) {
r_ret = get_editor_draw_gizmo();
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
void SkeletonModification2DLookAt::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::BOOL, "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
if (enable_constraint) {
p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
p_list->push_back(PropertyInfo(Variant::FLOAT, "additional_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
#endif // TOOLS_ENABLED
}
void SkeletonModification2DLookAt::_execute(float p_delta) {
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
"Modification is not setup and therefore cannot execute!");
if (!enabled) {
return;
}
if (target_node_cache.is_null()) {
WARN_PRINT_ONCE("Target cache is out of date. Attempting to update...");
update_target_cache();
return;
}
if (bone2d_node_cache.is_null() && !bone2d_node.is_empty()) {
update_bone2d_cache();
WARN_PRINT_ONCE("Bone2D node cache is out of date. Attempting to update...");
return;
}
if (target_node_reference == nullptr) {
target_node_reference = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache));
}
if (!target_node_reference || !target_node_reference->is_inside_tree()) {
ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!");
return;
}
if (bone_idx <= -1) {
ERR_PRINT_ONCE("Bone index is invalid. Cannot execute modification!");
return;
}
Bone2D *operation_bone = stack->skeleton->get_bone(bone_idx);
if (operation_bone == nullptr) {
ERR_PRINT_ONCE("bone_idx for modification does not point to a valid bone! Cannot execute modification");
return;
}
Transform2D operation_transform = operation_bone->get_global_transform();
Transform2D target_trans = target_node_reference->get_global_transform();
// Look at the target!
operation_transform = operation_transform.looking_at(target_trans.get_origin());
// Apply whatever scale it had prior to looking_at
operation_transform.set_scale(operation_bone->get_global_scale());
// Account for the direction the bone faces in:
operation_transform.set_rotation(operation_transform.get_rotation() - operation_bone->get_bone_angle());
// Apply additional rotation
operation_transform.set_rotation(operation_transform.get_rotation() + additional_rotation);
// Apply constraints in globalspace:
if (enable_constraint && !constraint_in_localspace) {
operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert));
}
// Convert from a global transform to a local transform via the Bone2D node
operation_bone->set_global_transform(operation_transform);
operation_transform = operation_bone->get_transform();
// Apply constraints in localspace:
if (enable_constraint && constraint_in_localspace) {
operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert));
}
// Set the local pose override, and to make sure child bones are also updated, set the transform of the bone.
stack->skeleton->set_bone_local_pose_override(bone_idx, operation_transform, stack->strength, true);
operation_bone->set_transform(operation_transform);
}
void SkeletonModification2DLookAt::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack != nullptr) {
is_setup = true;
update_target_cache();
update_bone2d_cache();
}
}
void SkeletonModification2DLookAt::_draw_editor_gizmo() {
if (!enabled || !is_setup) {
return;
}
Bone2D *operation_bone = stack->skeleton->get_bone(bone_idx);
editor_draw_angle_constraints(operation_bone, constraint_angle_min, constraint_angle_max,
enable_constraint, constraint_in_localspace, constraint_angle_invert);
}
void SkeletonModification2DLookAt::update_bone2d_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!");
}
return;
}
bone2d_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(bone2d_node)) {
Node *node = stack->skeleton->get_node(bone2d_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update Bone2D cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update Bone2D cache: node is not in the scene tree!");
bone2d_node_cache = node->get_instance_id();
Bone2D *bone = Object::cast_to<Bone2D>(node);
if (bone) {
bone_idx = bone->get_index_in_skeleton();
} else {
ERR_FAIL_MSG("Error Bone2D cache: Nodepath to Bone2D is not a Bone2D node!");
}
// Set this to null so we update it
target_node_reference = nullptr;
}
}
}
}
void SkeletonModification2DLookAt::set_bone2d_node(const NodePath &p_target_node) {
bone2d_node = p_target_node;
update_bone2d_cache();
}
NodePath SkeletonModification2DLookAt::get_bone2d_node() const {
return bone2d_node;
}
int SkeletonModification2DLookAt::get_bone_index() const {
return bone_idx;
}
void SkeletonModification2DLookAt::set_bone_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_setup && stack) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
bone_idx = p_bone_idx;
bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx));
} else {
WARN_PRINT("Cannot verify the bone index for this modification...");
bone_idx = p_bone_idx;
}
} else {
bone_idx = p_bone_idx;
}
notify_property_list_changed();
}
void SkeletonModification2DLookAt::update_target_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
}
return;
}
target_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(target_node)) {
Node *node = stack->skeleton->get_node(target_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update target cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update target cache: node is not in the scene tree!");
target_node_cache = node->get_instance_id();
}
}
}
}
void SkeletonModification2DLookAt::set_target_node(const NodePath &p_target_node) {
target_node = p_target_node;
update_target_cache();
}
NodePath SkeletonModification2DLookAt::get_target_node() const {
return target_node;
}
float SkeletonModification2DLookAt::get_additional_rotation() const {
return additional_rotation;
}
void SkeletonModification2DLookAt::set_additional_rotation(float p_rotation) {
additional_rotation = p_rotation;
}
void SkeletonModification2DLookAt::set_enable_constraint(bool p_constraint) {
enable_constraint = p_constraint;
notify_property_list_changed();
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2DLookAt::get_enable_constraint() const {
return enable_constraint;
}
void SkeletonModification2DLookAt::set_constraint_angle_min(float p_angle_min) {
constraint_angle_min = p_angle_min;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
float SkeletonModification2DLookAt::get_constraint_angle_min() const {
return constraint_angle_min;
}
void SkeletonModification2DLookAt::set_constraint_angle_max(float p_angle_max) {
constraint_angle_max = p_angle_max;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
float SkeletonModification2DLookAt::get_constraint_angle_max() const {
return constraint_angle_max;
}
void SkeletonModification2DLookAt::set_constraint_angle_invert(bool p_invert) {
constraint_angle_invert = p_invert;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2DLookAt::get_constraint_angle_invert() const {
return constraint_angle_invert;
}
void SkeletonModification2DLookAt::set_constraint_in_localspace(bool p_constraint_in_localspace) {
constraint_in_localspace = p_constraint_in_localspace;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2DLookAt::get_constraint_in_localspace() const {
return constraint_in_localspace;
}
void SkeletonModification2DLookAt::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bone2d_node", "bone2d_nodepath"), &SkeletonModification2DLookAt::set_bone2d_node);
ClassDB::bind_method(D_METHOD("get_bone2d_node"), &SkeletonModification2DLookAt::get_bone2d_node);
ClassDB::bind_method(D_METHOD("set_bone_index", "bone_idx"), &SkeletonModification2DLookAt::set_bone_index);
ClassDB::bind_method(D_METHOD("get_bone_index"), &SkeletonModification2DLookAt::get_bone_index);
ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DLookAt::set_target_node);
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DLookAt::get_target_node);
ClassDB::bind_method(D_METHOD("set_additional_rotation", "rotation"), &SkeletonModification2DLookAt::set_additional_rotation);
ClassDB::bind_method(D_METHOD("get_additional_rotation"), &SkeletonModification2DLookAt::get_additional_rotation);
ClassDB::bind_method(D_METHOD("set_enable_constraint", "enable_constraint"), &SkeletonModification2DLookAt::set_enable_constraint);
ClassDB::bind_method(D_METHOD("get_enable_constraint"), &SkeletonModification2DLookAt::get_enable_constraint);
ClassDB::bind_method(D_METHOD("set_constraint_angle_min", "angle_min"), &SkeletonModification2DLookAt::set_constraint_angle_min);
ClassDB::bind_method(D_METHOD("get_constraint_angle_min"), &SkeletonModification2DLookAt::get_constraint_angle_min);
ClassDB::bind_method(D_METHOD("set_constraint_angle_max", "angle_max"), &SkeletonModification2DLookAt::set_constraint_angle_max);
ClassDB::bind_method(D_METHOD("get_constraint_angle_max"), &SkeletonModification2DLookAt::get_constraint_angle_max);
ClassDB::bind_method(D_METHOD("set_constraint_angle_invert", "invert"), &SkeletonModification2DLookAt::set_constraint_angle_invert);
ClassDB::bind_method(D_METHOD("get_constraint_angle_invert"), &SkeletonModification2DLookAt::get_constraint_angle_invert);
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_index"), "set_bone_index", "get_bone_index");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D"), "set_bone2d_node", "get_bone2d_node");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node");
}
SkeletonModification2DLookAt::SkeletonModification2DLookAt() {
stack = nullptr;
is_setup = false;
bone_idx = -1;
additional_rotation = 0;
enable_constraint = false;
constraint_angle_min = 0;
constraint_angle_max = Math_PI * 2;
constraint_angle_invert = false;
enabled = true;
editor_draw_gizmo = true;
}
SkeletonModification2DLookAt::~SkeletonModification2DLookAt() {
}

View file

@ -0,0 +1,100 @@
/**************************************************************************/
/* skeleton_modification_2d_lookat.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_MODIFICATION_2D_LOOKAT_H
#define SKELETON_MODIFICATION_2D_LOOKAT_H
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/2d/skeleton/skeleton_modification_2d.h"
///////////////////////////////////////
// SkeletonModification2DLookAt
///////////////////////////////////////
class SkeletonModification2DLookAt : public SkeletonModification2D {
GDCLASS(SkeletonModification2DLookAt, SkeletonModification2D);
private:
int bone_idx = -1;
NodePath bone2d_node;
ObjectID bone2d_node_cache;
NodePath target_node;
ObjectID target_node_cache;
Node2D *target_node_reference = nullptr;
float additional_rotation = 0;
bool enable_constraint = false;
float constraint_angle_min = 0;
float constraint_angle_max = (2.0 * Math_PI);
bool constraint_angle_invert = false;
bool constraint_in_localspace = true;
void update_bone2d_cache();
void update_target_cache();
protected:
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:
void _execute(float p_delta) override;
void _setup_modification(SkeletonModificationStack2D *p_stack) override;
void _draw_editor_gizmo() override;
void set_bone2d_node(const NodePath &p_target_node);
NodePath get_bone2d_node() const;
void set_bone_index(int p_idx);
int get_bone_index() const;
void set_target_node(const NodePath &p_target_node);
NodePath get_target_node() const;
void set_additional_rotation(float p_rotation);
float get_additional_rotation() const;
void set_enable_constraint(bool p_constraint);
bool get_enable_constraint() const;
void set_constraint_angle_min(float p_angle_min);
float get_constraint_angle_min() const;
void set_constraint_angle_max(float p_angle_max);
float get_constraint_angle_max() const;
void set_constraint_angle_invert(bool p_invert);
bool get_constraint_angle_invert() const;
void set_constraint_in_localspace(bool p_constraint_in_localspace);
bool get_constraint_in_localspace() const;
SkeletonModification2DLookAt();
~SkeletonModification2DLookAt();
};
#endif // SKELETON_MODIFICATION_2D_LOOKAT_H

View file

@ -0,0 +1,297 @@
/**************************************************************************/
/* skeleton_modification_2d_physicalbones.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_modification_2d_physicalbones.h"
#include "scene/2d/physics/physical_bone_2d.h"
#include "scene/2d/skeleton_2d.h"
bool SkeletonModification2DPhysicalBones::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
#ifdef TOOLS_ENABLED
// Exposes a way to fetch the PhysicalBone2D nodes from the Godot editor.
if (is_setup) {
if (Engine::get_singleton()->is_editor_hint()) {
if (path.begins_with("fetch_bones")) {
fetch_physical_bones();
notify_property_list_changed();
return true;
}
}
}
#endif //TOOLS_ENABLED
if (path.begins_with("joint_")) {
int which = path.get_slicec('_', 1).to_int();
String what = path.get_slicec('_', 2);
ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false);
if (what == "nodepath") {
set_physical_bone_node(which, p_value);
return true;
}
}
return false;
}
bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
if (path.begins_with("fetch_bones")) {
return true; // Do nothing!
}
}
#endif //TOOLS_ENABLED
if (path.begins_with("joint_")) {
int which = path.get_slicec('_', 1).to_int();
String what = path.get_slicec('_', 2);
ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false);
if (what == "nodepath") {
r_ret = get_physical_bone_node(which);
return true;
}
}
return false;
}
void SkeletonModification2DPhysicalBones::_get_property_list(List<PropertyInfo> *p_list) const {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
p_list->push_back(PropertyInfo(Variant::BOOL, "fetch_bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
#endif //TOOLS_ENABLED
for (int i = 0; i < physical_bone_chain.size(); i++) {
String base_string = "joint_" + itos(i) + "_";
p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicalBone2D", PROPERTY_USAGE_DEFAULT));
}
}
void SkeletonModification2DPhysicalBones::_execute(float p_delta) {
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
"Modification is not setup and therefore cannot execute!");
if (!enabled) {
return;
}
if (_simulation_state_dirty) {
_update_simulation_state();
}
for (int i = 0; i < physical_bone_chain.size(); i++) {
PhysicalBone_Data2D bone_data = physical_bone_chain[i];
if (bone_data.physical_bone_node_cache.is_null()) {
WARN_PRINT_ONCE("PhysicalBone2D cache " + itos(i) + " is out of date. Attempting to update...");
_physical_bone_update_cache(i);
continue;
}
PhysicalBone2D *physical_bone = Object::cast_to<PhysicalBone2D>(ObjectDB::get_instance(bone_data.physical_bone_node_cache));
if (!physical_bone) {
ERR_PRINT_ONCE("PhysicalBone2D not found at index " + itos(i) + "!");
return;
}
if (physical_bone->get_bone2d_index() < 0 || physical_bone->get_bone2d_index() > stack->skeleton->get_bone_count()) {
ERR_PRINT_ONCE("PhysicalBone2D at index " + itos(i) + " has invalid Bone2D!");
return;
}
Bone2D *bone_2d = stack->skeleton->get_bone(physical_bone->get_bone2d_index());
if (physical_bone->get_simulate_physics() && !physical_bone->get_follow_bone_when_simulating()) {
bone_2d->set_global_transform(physical_bone->get_global_transform());
stack->skeleton->set_bone_local_pose_override(physical_bone->get_bone2d_index(), bone_2d->get_transform(), stack->strength, true);
}
}
}
void SkeletonModification2DPhysicalBones::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack) {
is_setup = true;
if (stack->skeleton) {
for (int i = 0; i < physical_bone_chain.size(); i++) {
_physical_bone_update_cache(i);
}
}
}
}
void SkeletonModification2DPhysicalBones::_physical_bone_update_cache(int p_joint_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Cannot update PhysicalBone2D cache: joint index out of range!");
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update PhysicalBone2D cache: modification is not properly setup!");
}
return;
}
physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(physical_bone_chain[p_joint_idx].physical_bone_node)) {
Node *node = stack->skeleton->get_node(physical_bone_chain[p_joint_idx].physical_bone_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update Physical Bone2D " + itos(p_joint_idx) + " cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update Physical Bone2D " + itos(p_joint_idx) + " cache: node is not in scene tree!");
physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = node->get_instance_id();
}
}
}
}
int SkeletonModification2DPhysicalBones::get_physical_bone_chain_length() {
return physical_bone_chain.size();
}
void SkeletonModification2DPhysicalBones::set_physical_bone_chain_length(int p_length) {
ERR_FAIL_COND(p_length < 0);
physical_bone_chain.resize(p_length);
notify_property_list_changed();
}
void SkeletonModification2DPhysicalBones::fetch_physical_bones() {
ERR_FAIL_NULL_MSG(stack, "No modification stack found! Cannot fetch physical bones!");
ERR_FAIL_NULL_MSG(stack->skeleton, "No skeleton found! Cannot fetch physical bones!");
physical_bone_chain.clear();
List<Node *> node_queue = List<Node *>();
node_queue.push_back(stack->skeleton);
while (node_queue.size() > 0) {
Node *node_to_process = node_queue.front()->get();
node_queue.pop_front();
if (node_to_process != nullptr) {
PhysicalBone2D *potential_bone = Object::cast_to<PhysicalBone2D>(node_to_process);
if (potential_bone) {
PhysicalBone_Data2D new_data = PhysicalBone_Data2D();
new_data.physical_bone_node = stack->skeleton->get_path_to(potential_bone);
new_data.physical_bone_node_cache = potential_bone->get_instance_id();
physical_bone_chain.push_back(new_data);
}
for (int i = 0; i < node_to_process->get_child_count(); i++) {
node_queue.push_back(node_to_process->get_child(i));
}
}
}
}
void SkeletonModification2DPhysicalBones::start_simulation(const TypedArray<StringName> &p_bones) {
_simulation_state_dirty = true;
_simulation_state_dirty_names = p_bones;
_simulation_state_dirty_process = true;
if (is_setup) {
_update_simulation_state();
}
}
void SkeletonModification2DPhysicalBones::stop_simulation(const TypedArray<StringName> &p_bones) {
_simulation_state_dirty = true;
_simulation_state_dirty_names = p_bones;
_simulation_state_dirty_process = false;
if (is_setup) {
_update_simulation_state();
}
}
void SkeletonModification2DPhysicalBones::_update_simulation_state() {
if (!_simulation_state_dirty) {
return;
}
_simulation_state_dirty = false;
if (_simulation_state_dirty_names.size() <= 0) {
for (int i = 0; i < physical_bone_chain.size(); i++) {
PhysicalBone2D *physical_bone = Object::cast_to<PhysicalBone2D>(stack->skeleton->get_node(physical_bone_chain[i].physical_bone_node));
if (!physical_bone) {
continue;
}
physical_bone->set_simulate_physics(_simulation_state_dirty_process);
}
} else {
for (int i = 0; i < physical_bone_chain.size(); i++) {
PhysicalBone2D *physical_bone = Object::cast_to<PhysicalBone2D>(ObjectDB::get_instance(physical_bone_chain[i].physical_bone_node_cache));
if (!physical_bone) {
continue;
}
if (_simulation_state_dirty_names.has(physical_bone->get_name())) {
physical_bone->set_simulate_physics(_simulation_state_dirty_process);
}
}
}
}
void SkeletonModification2DPhysicalBones::set_physical_bone_node(int p_joint_idx, const NodePath &p_nodepath) {
ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Joint index out of range!");
physical_bone_chain.write[p_joint_idx].physical_bone_node = p_nodepath;
_physical_bone_update_cache(p_joint_idx);
}
NodePath SkeletonModification2DPhysicalBones::get_physical_bone_node(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, physical_bone_chain.size(), NodePath(), "Joint index out of range!");
return physical_bone_chain[p_joint_idx].physical_bone_node;
}
void SkeletonModification2DPhysicalBones::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_physical_bone_chain_length", "length"), &SkeletonModification2DPhysicalBones::set_physical_bone_chain_length);
ClassDB::bind_method(D_METHOD("get_physical_bone_chain_length"), &SkeletonModification2DPhysicalBones::get_physical_bone_chain_length);
ClassDB::bind_method(D_METHOD("set_physical_bone_node", "joint_idx", "physicalbone2d_node"), &SkeletonModification2DPhysicalBones::set_physical_bone_node);
ClassDB::bind_method(D_METHOD("get_physical_bone_node", "joint_idx"), &SkeletonModification2DPhysicalBones::get_physical_bone_node);
ClassDB::bind_method(D_METHOD("fetch_physical_bones"), &SkeletonModification2DPhysicalBones::fetch_physical_bones);
ClassDB::bind_method(D_METHOD("start_simulation", "bones"), &SkeletonModification2DPhysicalBones::start_simulation, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("stop_simulation", "bones"), &SkeletonModification2DPhysicalBones::stop_simulation, DEFVAL(Array()));
ADD_PROPERTY(PropertyInfo(Variant::INT, "physical_bone_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_physical_bone_chain_length", "get_physical_bone_chain_length");
}
SkeletonModification2DPhysicalBones::SkeletonModification2DPhysicalBones() {
stack = nullptr;
is_setup = false;
physical_bone_chain = Vector<PhysicalBone_Data2D>();
enabled = true;
editor_draw_gizmo = false; // Nothing to really show in a gizmo right now.
}
SkeletonModification2DPhysicalBones::~SkeletonModification2DPhysicalBones() {
}

View file

@ -0,0 +1,82 @@
/**************************************************************************/
/* skeleton_modification_2d_physicalbones.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_MODIFICATION_2D_PHYSICALBONES_H
#define SKELETON_MODIFICATION_2D_PHYSICALBONES_H
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/2d/skeleton/skeleton_modification_2d.h"
///////////////////////////////////////
// SkeletonModification2DJIGGLE
///////////////////////////////////////
class SkeletonModification2DPhysicalBones : public SkeletonModification2D {
GDCLASS(SkeletonModification2DPhysicalBones, SkeletonModification2D);
private:
struct PhysicalBone_Data2D {
NodePath physical_bone_node;
ObjectID physical_bone_node_cache;
};
Vector<PhysicalBone_Data2D> physical_bone_chain;
void _physical_bone_update_cache(int p_joint_idx);
bool _simulation_state_dirty = false;
TypedArray<StringName> _simulation_state_dirty_names;
bool _simulation_state_dirty_process = false;
void _update_simulation_state();
protected:
static void _bind_methods();
bool _get(const StringName &p_path, Variant &r_ret) const;
bool _set(const StringName &p_path, const Variant &p_value);
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
void _execute(float p_delta) override;
void _setup_modification(SkeletonModificationStack2D *p_stack) override;
int get_physical_bone_chain_length();
void set_physical_bone_chain_length(int p_new_length);
void set_physical_bone_node(int p_joint_idx, const NodePath &p_path);
NodePath get_physical_bone_node(int p_joint_idx) const;
void fetch_physical_bones();
void start_simulation(const TypedArray<StringName> &p_bones);
void stop_simulation(const TypedArray<StringName> &p_bones);
SkeletonModification2DPhysicalBones();
~SkeletonModification2DPhysicalBones();
};
#endif // SKELETON_MODIFICATION_2D_PHYSICALBONES_H

View file

@ -0,0 +1,135 @@
/**************************************************************************/
/* skeleton_modification_2d_stackholder.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_modification_2d_stackholder.h"
#include "scene/2d/skeleton_2d.h"
bool SkeletonModification2DStackHolder::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path == "held_modification_stack") {
set_held_modification_stack(p_value);
}
#ifdef TOOLS_ENABLED
else if (path == "editor/draw_gizmo") {
set_editor_draw_gizmo(p_value);
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
bool SkeletonModification2DStackHolder::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path == "held_modification_stack") {
r_ret = get_held_modification_stack();
}
#ifdef TOOLS_ENABLED
else if (path == "editor/draw_gizmo") {
r_ret = get_editor_draw_gizmo();
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
void SkeletonModification2DStackHolder::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::OBJECT, "held_modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE));
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
#endif // TOOLS_ENABLED
}
void SkeletonModification2DStackHolder::_execute(float p_delta) {
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
"Modification is not setup and therefore cannot execute!");
if (held_modification_stack.is_valid()) {
held_modification_stack->execute(p_delta, execution_mode);
}
}
void SkeletonModification2DStackHolder::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack != nullptr) {
is_setup = true;
if (held_modification_stack.is_valid()) {
held_modification_stack->set_skeleton(stack->get_skeleton());
held_modification_stack->setup();
}
}
}
void SkeletonModification2DStackHolder::_draw_editor_gizmo() {
if (stack) {
if (held_modification_stack.is_valid()) {
held_modification_stack->draw_editor_gizmos();
}
}
}
void SkeletonModification2DStackHolder::set_held_modification_stack(Ref<SkeletonModificationStack2D> p_held_stack) {
held_modification_stack = p_held_stack;
if (is_setup && held_modification_stack.is_valid()) {
held_modification_stack->set_skeleton(stack->get_skeleton());
held_modification_stack->setup();
}
}
Ref<SkeletonModificationStack2D> SkeletonModification2DStackHolder::get_held_modification_stack() const {
return held_modification_stack;
}
void SkeletonModification2DStackHolder::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_held_modification_stack", "held_modification_stack"), &SkeletonModification2DStackHolder::set_held_modification_stack);
ClassDB::bind_method(D_METHOD("get_held_modification_stack"), &SkeletonModification2DStackHolder::get_held_modification_stack);
}
SkeletonModification2DStackHolder::SkeletonModification2DStackHolder() {
stack = nullptr;
is_setup = false;
enabled = true;
}
SkeletonModification2DStackHolder::~SkeletonModification2DStackHolder() {
}

View file

@ -0,0 +1,64 @@
/**************************************************************************/
/* skeleton_modification_2d_stackholder.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_MODIFICATION_2D_STACKHOLDER_H
#define SKELETON_MODIFICATION_2D_STACKHOLDER_H
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/2d/skeleton/skeleton_modification_2d.h"
///////////////////////////////////////
// SkeletonModification2DJIGGLE
///////////////////////////////////////
class SkeletonModification2DStackHolder : public SkeletonModification2D {
GDCLASS(SkeletonModification2DStackHolder, SkeletonModification2D);
protected:
static void _bind_methods();
bool _get(const StringName &p_path, Variant &r_ret) const;
bool _set(const StringName &p_path, const Variant &p_value);
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
Ref<SkeletonModificationStack2D> held_modification_stack;
void _execute(float p_delta) override;
void _setup_modification(SkeletonModificationStack2D *p_stack) override;
void _draw_editor_gizmo() override;
void set_held_modification_stack(Ref<SkeletonModificationStack2D> p_held_stack);
Ref<SkeletonModificationStack2D> get_held_modification_stack() const;
SkeletonModification2DStackHolder();
~SkeletonModification2DStackHolder();
};
#endif // SKELETON_MODIFICATION_2D_STACKHOLDER_H

View file

@ -0,0 +1,489 @@
/**************************************************************************/
/* skeleton_modification_2d_twoboneik.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_modification_2d_twoboneik.h"
#include "scene/2d/skeleton_2d.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
#endif // TOOLS_ENABLED
bool SkeletonModification2DTwoBoneIK::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path == "joint_one_bone_idx") {
set_joint_one_bone_idx(p_value);
} else if (path == "joint_one_bone2d_node") {
set_joint_one_bone2d_node(p_value);
} else if (path == "joint_two_bone_idx") {
set_joint_two_bone_idx(p_value);
} else if (path == "joint_two_bone2d_node") {
set_joint_two_bone2d_node(p_value);
}
#ifdef TOOLS_ENABLED
else if (path.begins_with("editor/draw_gizmo")) {
set_editor_draw_gizmo(p_value);
} else if (path.begins_with("editor/draw_min_max")) {
set_editor_draw_min_max(p_value);
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
bool SkeletonModification2DTwoBoneIK::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path == "joint_one_bone_idx") {
r_ret = get_joint_one_bone_idx();
} else if (path == "joint_one_bone2d_node") {
r_ret = get_joint_one_bone2d_node();
} else if (path == "joint_two_bone_idx") {
r_ret = get_joint_two_bone_idx();
} else if (path == "joint_two_bone2d_node") {
r_ret = get_joint_two_bone2d_node();
}
#ifdef TOOLS_ENABLED
else if (path.begins_with("editor/draw_gizmo")) {
r_ret = get_editor_draw_gizmo();
} else if (path.begins_with("editor/draw_min_max")) {
r_ret = get_editor_draw_min_max();
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
void SkeletonModification2DTwoBoneIK::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::INT, "joint_one_bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::NODE_PATH, "joint_one_bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::INT, "joint_two_bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::NODE_PATH, "joint_two_bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT));
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_min_max", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
#endif // TOOLS_ENABLED
}
void SkeletonModification2DTwoBoneIK::_execute(float p_delta) {
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
"Modification is not setup and therefore cannot execute!");
if (!enabled) {
return;
}
if (target_node_cache.is_null()) {
WARN_PRINT_ONCE("Target cache is out of date. Attempting to update...");
update_target_cache();
return;
}
if (joint_one_bone2d_node_cache.is_null() && !joint_one_bone2d_node.is_empty()) {
WARN_PRINT_ONCE("Joint one Bone2D node cache is out of date. Attempting to update...");
update_joint_one_bone2d_cache();
}
if (joint_two_bone2d_node_cache.is_null() && !joint_two_bone2d_node.is_empty()) {
WARN_PRINT_ONCE("Joint two Bone2D node cache is out of date. Attempting to update...");
update_joint_two_bone2d_cache();
}
Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache));
if (!target || !target->is_inside_tree()) {
ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!");
return;
}
Bone2D *joint_one_bone = stack->skeleton->get_bone(joint_one_bone_idx);
if (joint_one_bone == nullptr) {
ERR_PRINT_ONCE("Joint one bone_idx does not point to a valid bone! Cannot execute modification!");
return;
}
Bone2D *joint_two_bone = stack->skeleton->get_bone(joint_two_bone_idx);
if (joint_two_bone == nullptr) {
ERR_PRINT_ONCE("Joint two bone_idx does not point to a valid bone! Cannot execute modification!");
return;
}
// Adopted from the links below:
// http://theorangeduck.com/page/simple-two-joint
// https://www.alanzucconi.com/2018/05/02/ik-2d-2/
// With modifications by TwistedTwigleg
Vector2 target_difference = target->get_global_position() - joint_one_bone->get_global_position();
float joint_one_to_target = target_difference.length();
float angle_atan = target_difference.angle();
float bone_one_length = joint_one_bone->get_length() * MIN(joint_one_bone->get_global_scale().x, joint_one_bone->get_global_scale().y);
float bone_two_length = joint_two_bone->get_length() * MIN(joint_two_bone->get_global_scale().x, joint_two_bone->get_global_scale().y);
bool override_angles_due_to_out_of_range = false;
if (joint_one_to_target < target_minimum_distance) {
joint_one_to_target = target_minimum_distance;
}
if (joint_one_to_target > target_maximum_distance && target_maximum_distance > 0.0) {
joint_one_to_target = target_maximum_distance;
}
if (bone_one_length + bone_two_length < joint_one_to_target) {
override_angles_due_to_out_of_range = true;
}
if (!override_angles_due_to_out_of_range) {
float angle_0 = Math::acos(((joint_one_to_target * joint_one_to_target) + (bone_one_length * bone_one_length) - (bone_two_length * bone_two_length)) / (2.0 * joint_one_to_target * bone_one_length));
float angle_1 = Math::acos(((bone_two_length * bone_two_length) + (bone_one_length * bone_one_length) - (joint_one_to_target * joint_one_to_target)) / (2.0 * bone_two_length * bone_one_length));
if (flip_bend_direction) {
angle_0 = -angle_0;
angle_1 = -angle_1;
}
if (isnan(angle_0) || isnan(angle_1)) {
// We cannot solve for this angle! Do nothing to avoid setting the rotation (and scale) to NaN.
} else {
joint_one_bone->set_global_rotation(angle_atan - angle_0 - joint_one_bone->get_bone_angle());
joint_two_bone->set_rotation(-Math_PI - angle_1 - joint_two_bone->get_bone_angle() + joint_one_bone->get_bone_angle());
}
} else {
joint_one_bone->set_global_rotation(angle_atan - joint_one_bone->get_bone_angle());
joint_two_bone->set_global_rotation(angle_atan - joint_two_bone->get_bone_angle());
}
stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, joint_one_bone->get_transform(), stack->strength, true);
stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, joint_two_bone->get_transform(), stack->strength, true);
}
void SkeletonModification2DTwoBoneIK::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack) {
is_setup = true;
update_target_cache();
update_joint_one_bone2d_cache();
update_joint_two_bone2d_cache();
}
}
void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() {
if (!enabled || !is_setup) {
return;
}
Bone2D *operation_bone_one = stack->skeleton->get_bone(joint_one_bone_idx);
if (!operation_bone_one) {
return;
}
stack->skeleton->draw_set_transform(
stack->skeleton->to_local(operation_bone_one->get_global_position()),
operation_bone_one->get_global_rotation() - stack->skeleton->get_global_rotation());
Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4);
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color");
}
#endif // TOOLS_ENABLED
if (flip_bend_direction) {
float angle = -(Math_PI * 0.5) + operation_bone_one->get_bone_angle();
stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(angle), sin(angle)) * (operation_bone_one->get_length() * 0.5), bone_ik_color, 2.0);
} else {
float angle = (Math_PI * 0.5) + operation_bone_one->get_bone_angle();
stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(angle), sin(angle)) * (operation_bone_one->get_length() * 0.5), bone_ik_color, 2.0);
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
if (editor_draw_min_max) {
if (target_maximum_distance != 0.0 || target_minimum_distance != 0.0) {
Vector2 target_direction = Vector2(0, 1);
if (target_node_cache.is_valid()) {
stack->skeleton->draw_set_transform(Vector2(0, 0), 0.0);
Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache));
target_direction = operation_bone_one->get_global_position().direction_to(target->get_global_position());
}
stack->skeleton->draw_circle(target_direction * target_minimum_distance, 8, bone_ik_color);
stack->skeleton->draw_circle(target_direction * target_maximum_distance, 8, bone_ik_color);
stack->skeleton->draw_line(target_direction * target_minimum_distance, target_direction * target_maximum_distance, bone_ik_color, 2.0);
}
}
}
#endif // TOOLS_ENABLED
}
void SkeletonModification2DTwoBoneIK::update_target_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
}
return;
}
target_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(target_node)) {
Node *node = stack->skeleton->get_node(target_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update target cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update target cache: node is not in the scene tree!");
target_node_cache = node->get_instance_id();
}
}
}
}
void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!");
}
return;
}
joint_one_bone2d_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(joint_one_bone2d_node)) {
Node *node = stack->skeleton->get_node(joint_one_bone2d_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update joint one Bone2D cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update joint one Bone2D cache: node is not in the scene tree!");
joint_one_bone2d_node_cache = node->get_instance_id();
Bone2D *bone = Object::cast_to<Bone2D>(node);
if (bone) {
joint_one_bone_idx = bone->get_index_in_skeleton();
} else {
ERR_FAIL_MSG("Update joint one Bone2D cache: Nodepath to Bone2D is not a Bone2D node!");
}
}
}
}
}
void SkeletonModification2DTwoBoneIK::update_joint_two_bone2d_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!");
}
return;
}
joint_two_bone2d_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(joint_two_bone2d_node)) {
Node *node = stack->skeleton->get_node(joint_two_bone2d_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update joint two Bone2D cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update joint two Bone2D cache: node is not in scene tree!");
joint_two_bone2d_node_cache = node->get_instance_id();
Bone2D *bone = Object::cast_to<Bone2D>(node);
if (bone) {
joint_two_bone_idx = bone->get_index_in_skeleton();
} else {
ERR_FAIL_MSG("Update joint two Bone2D cache: Nodepath to Bone2D is not a Bone2D node!");
}
}
}
}
}
void SkeletonModification2DTwoBoneIK::set_target_node(const NodePath &p_target_node) {
target_node = p_target_node;
update_target_cache();
}
NodePath SkeletonModification2DTwoBoneIK::get_target_node() const {
return target_node;
}
void SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node(const NodePath &p_target_node) {
joint_one_bone2d_node = p_target_node;
update_joint_one_bone2d_cache();
notify_property_list_changed();
}
void SkeletonModification2DTwoBoneIK::set_target_minimum_distance(float p_distance) {
ERR_FAIL_COND_MSG(p_distance < 0, "Target minimum distance cannot be less than zero!");
target_minimum_distance = p_distance;
}
float SkeletonModification2DTwoBoneIK::get_target_minimum_distance() const {
return target_minimum_distance;
}
void SkeletonModification2DTwoBoneIK::set_target_maximum_distance(float p_distance) {
ERR_FAIL_COND_MSG(p_distance < 0, "Target maximum distance cannot be less than zero!");
target_maximum_distance = p_distance;
}
float SkeletonModification2DTwoBoneIK::get_target_maximum_distance() const {
return target_maximum_distance;
}
void SkeletonModification2DTwoBoneIK::set_flip_bend_direction(bool p_flip_direction) {
flip_bend_direction = p_flip_direction;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2DTwoBoneIK::get_flip_bend_direction() const {
return flip_bend_direction;
}
NodePath SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node() const {
return joint_one_bone2d_node;
}
void SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node(const NodePath &p_target_node) {
joint_two_bone2d_node = p_target_node;
update_joint_two_bone2d_cache();
notify_property_list_changed();
}
NodePath SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node() const {
return joint_two_bone2d_node;
}
void SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx(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_setup) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
joint_one_bone_idx = p_bone_idx;
joint_one_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
joint_one_bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx));
} else {
WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one...");
joint_one_bone_idx = p_bone_idx;
}
} else {
joint_one_bone_idx = p_bone_idx;
}
notify_property_list_changed();
}
int SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx() const {
return joint_one_bone_idx;
}
void SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx(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_setup) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
joint_two_bone_idx = p_bone_idx;
joint_two_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
joint_two_bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx));
} else {
WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two...");
joint_two_bone_idx = p_bone_idx;
}
} else {
joint_two_bone_idx = p_bone_idx;
}
notify_property_list_changed();
}
int SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx() const {
return joint_two_bone_idx;
}
#ifdef TOOLS_ENABLED
void SkeletonModification2DTwoBoneIK::set_editor_draw_min_max(bool p_draw) {
editor_draw_min_max = p_draw;
}
bool SkeletonModification2DTwoBoneIK::get_editor_draw_min_max() const {
return editor_draw_min_max;
}
#endif // TOOLS_ENABLED
void SkeletonModification2DTwoBoneIK::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DTwoBoneIK::set_target_node);
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DTwoBoneIK::get_target_node);
ClassDB::bind_method(D_METHOD("set_target_minimum_distance", "minimum_distance"), &SkeletonModification2DTwoBoneIK::set_target_minimum_distance);
ClassDB::bind_method(D_METHOD("get_target_minimum_distance"), &SkeletonModification2DTwoBoneIK::get_target_minimum_distance);
ClassDB::bind_method(D_METHOD("set_target_maximum_distance", "maximum_distance"), &SkeletonModification2DTwoBoneIK::set_target_maximum_distance);
ClassDB::bind_method(D_METHOD("get_target_maximum_distance"), &SkeletonModification2DTwoBoneIK::get_target_maximum_distance);
ClassDB::bind_method(D_METHOD("set_flip_bend_direction", "flip_direction"), &SkeletonModification2DTwoBoneIK::set_flip_bend_direction);
ClassDB::bind_method(D_METHOD("get_flip_bend_direction"), &SkeletonModification2DTwoBoneIK::get_flip_bend_direction);
ClassDB::bind_method(D_METHOD("set_joint_one_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node);
ClassDB::bind_method(D_METHOD("get_joint_one_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node);
ClassDB::bind_method(D_METHOD("set_joint_one_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx);
ClassDB::bind_method(D_METHOD("get_joint_one_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx);
ClassDB::bind_method(D_METHOD("set_joint_two_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node);
ClassDB::bind_method(D_METHOD("get_joint_two_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node);
ClassDB::bind_method(D_METHOD("set_joint_two_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx);
ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_minimum_distance", PROPERTY_HINT_RANGE, "0,100000000,0.01,suffix:px"), "set_target_minimum_distance", "get_target_minimum_distance");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_maximum_distance", PROPERTY_HINT_NONE, "0,100000000,0.01,suffix:px"), "set_target_maximum_distance", "get_target_maximum_distance");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_bend_direction", PROPERTY_HINT_NONE, ""), "set_flip_bend_direction", "get_flip_bend_direction");
ADD_GROUP("", "");
}
SkeletonModification2DTwoBoneIK::SkeletonModification2DTwoBoneIK() {
stack = nullptr;
is_setup = false;
enabled = true;
editor_draw_gizmo = true;
}
SkeletonModification2DTwoBoneIK::~SkeletonModification2DTwoBoneIK() {
}

View file

@ -0,0 +1,107 @@
/**************************************************************************/
/* skeleton_modification_2d_twoboneik.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_MODIFICATION_2D_TWOBONEIK_H
#define SKELETON_MODIFICATION_2D_TWOBONEIK_H
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/2d/skeleton/skeleton_modification_2d.h"
///////////////////////////////////////
// SkeletonModification2DJIGGLE
///////////////////////////////////////
class SkeletonModification2DTwoBoneIK : public SkeletonModification2D {
GDCLASS(SkeletonModification2DTwoBoneIK, SkeletonModification2D);
private:
NodePath target_node;
ObjectID target_node_cache;
float target_minimum_distance = 0;
float target_maximum_distance = 0;
bool flip_bend_direction = false;
NodePath joint_one_bone2d_node;
ObjectID joint_one_bone2d_node_cache;
int joint_one_bone_idx = -1;
NodePath joint_two_bone2d_node;
ObjectID joint_two_bone2d_node_cache;
int joint_two_bone_idx = -1;
#ifdef TOOLS_ENABLED
bool editor_draw_min_max = false;
#endif // TOOLS_ENABLED
void update_target_cache();
void update_joint_one_bone2d_cache();
void update_joint_two_bone2d_cache();
protected:
static void _bind_methods();
bool _get(const StringName &p_path, Variant &r_ret) const;
bool _set(const StringName &p_path, const Variant &p_value);
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
void _execute(float p_delta) override;
void _setup_modification(SkeletonModificationStack2D *p_stack) override;
void _draw_editor_gizmo() override;
void set_target_node(const NodePath &p_target_node);
NodePath get_target_node() const;
void set_target_minimum_distance(float p_minimum_distance);
float get_target_minimum_distance() const;
void set_target_maximum_distance(float p_maximum_distance);
float get_target_maximum_distance() const;
void set_flip_bend_direction(bool p_flip_direction);
bool get_flip_bend_direction() const;
void set_joint_one_bone2d_node(const NodePath &p_node);
NodePath get_joint_one_bone2d_node() const;
void set_joint_one_bone_idx(int p_bone_idx);
int get_joint_one_bone_idx() const;
void set_joint_two_bone2d_node(const NodePath &p_node);
NodePath get_joint_two_bone2d_node() const;
void set_joint_two_bone_idx(int p_bone_idx);
int get_joint_two_bone_idx() const;
#ifdef TOOLS_ENABLED
void set_editor_draw_min_max(bool p_draw);
bool get_editor_draw_min_max() const;
#endif // TOOLS_ENABLED
SkeletonModification2DTwoBoneIK();
~SkeletonModification2DTwoBoneIK();
};
#endif // SKELETON_MODIFICATION_2D_TWOBONEIK_H

View file

@ -0,0 +1,270 @@
/**************************************************************************/
/* skeleton_modification_stack_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_modification_stack_2d.h"
#include "scene/2d/skeleton_2d.h"
void SkeletonModificationStack2D::_get_property_list(List<PropertyInfo> *p_list) const {
for (int i = 0; i < modifications.size(); i++) {
p_list->push_back(
PropertyInfo(Variant::OBJECT, "modifications/" + itos(i),
PROPERTY_HINT_RESOURCE_TYPE,
"SkeletonModification2D",
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE));
}
}
bool SkeletonModificationStack2D::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("modifications/")) {
int mod_idx = path.get_slicec('/', 1).to_int();
set_modification(mod_idx, p_value);
return true;
}
return false;
}
bool SkeletonModificationStack2D::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("modifications/")) {
int mod_idx = path.get_slicec('/', 1).to_int();
r_ret = get_modification(mod_idx);
return true;
}
return false;
}
void SkeletonModificationStack2D::setup() {
if (is_setup) {
return;
}
if (skeleton != nullptr) {
is_setup = true;
for (int i = 0; i < modifications.size(); i++) {
if (!modifications[i].is_valid()) {
continue;
}
modifications.get(i)->_setup_modification(this);
}
#ifdef TOOLS_ENABLED
set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
} else {
WARN_PRINT("Cannot setup SkeletonModificationStack2D: no Skeleton2D set!");
}
}
void SkeletonModificationStack2D::execute(float p_delta, int p_execution_mode) {
ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr || is_queued_for_deletion(),
"Modification stack is not properly setup and therefore cannot execute!");
if (!skeleton->is_inside_tree()) {
ERR_PRINT_ONCE("Skeleton is not inside SceneTree! Cannot execute modification!");
return;
}
if (!enabled) {
return;
}
for (int i = 0; i < modifications.size(); i++) {
if (!modifications[i].is_valid()) {
continue;
}
if (modifications[i]->get_execution_mode() == p_execution_mode) {
modifications.get(i)->_execute(p_delta);
}
}
}
void SkeletonModificationStack2D::draw_editor_gizmos() {
if (!is_setup) {
return;
}
if (editor_gizmo_dirty) {
for (int i = 0; i < modifications.size(); i++) {
if (!modifications[i].is_valid()) {
continue;
}
if (modifications[i]->editor_draw_gizmo) {
modifications.get(i)->_draw_editor_gizmo();
}
}
skeleton->draw_set_transform(Vector2(0, 0));
editor_gizmo_dirty = false;
}
}
void SkeletonModificationStack2D::set_editor_gizmos_dirty(bool p_dirty) {
if (!is_setup) {
return;
}
if (!editor_gizmo_dirty && p_dirty) {
editor_gizmo_dirty = p_dirty;
if (skeleton) {
skeleton->queue_redraw();
}
} else {
editor_gizmo_dirty = p_dirty;
}
}
void SkeletonModificationStack2D::enable_all_modifications(bool p_enabled) {
for (int i = 0; i < modifications.size(); i++) {
if (!modifications[i].is_valid()) {
continue;
}
modifications.get(i)->set_enabled(p_enabled);
}
}
Ref<SkeletonModification2D> SkeletonModificationStack2D::get_modification(int p_mod_idx) const {
ERR_FAIL_INDEX_V(p_mod_idx, modifications.size(), nullptr);
return modifications[p_mod_idx];
}
void SkeletonModificationStack2D::add_modification(Ref<SkeletonModification2D> p_mod) {
ERR_FAIL_COND(!p_mod.is_valid());
p_mod->_setup_modification(this);
modifications.push_back(p_mod);
#ifdef TOOLS_ENABLED
set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
}
void SkeletonModificationStack2D::delete_modification(int p_mod_idx) {
ERR_FAIL_INDEX(p_mod_idx, modifications.size());
modifications.remove_at(p_mod_idx);
#ifdef TOOLS_ENABLED
set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
}
void SkeletonModificationStack2D::set_modification(int p_mod_idx, Ref<SkeletonModification2D> p_mod) {
ERR_FAIL_INDEX(p_mod_idx, modifications.size());
if (p_mod.is_null()) {
modifications.write[p_mod_idx] = Ref<SkeletonModification2D>();
} else {
modifications.write[p_mod_idx] = p_mod;
p_mod->_setup_modification(this);
}
#ifdef TOOLS_ENABLED
set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
}
void SkeletonModificationStack2D::set_modification_count(int p_count) {
ERR_FAIL_COND_MSG(p_count < 0, "Modification count cannot be less than zero.");
modifications.resize(p_count);
notify_property_list_changed();
#ifdef TOOLS_ENABLED
set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
}
int SkeletonModificationStack2D::get_modification_count() const {
return modifications.size();
}
void SkeletonModificationStack2D::set_skeleton(Skeleton2D *p_skeleton) {
skeleton = p_skeleton;
}
Skeleton2D *SkeletonModificationStack2D::get_skeleton() const {
return skeleton;
}
bool SkeletonModificationStack2D::get_is_setup() const {
return is_setup;
}
void SkeletonModificationStack2D::set_enabled(bool p_enabled) {
enabled = p_enabled;
}
bool SkeletonModificationStack2D::get_enabled() const {
return enabled;
}
void SkeletonModificationStack2D::set_strength(float p_strength) {
ERR_FAIL_COND_MSG(p_strength < 0, "Strength cannot be less than zero!");
ERR_FAIL_COND_MSG(p_strength > 1, "Strength cannot be more than one!");
strength = p_strength;
}
float SkeletonModificationStack2D::get_strength() const {
return strength;
}
void SkeletonModificationStack2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("setup"), &SkeletonModificationStack2D::setup);
ClassDB::bind_method(D_METHOD("execute", "delta", "execution_mode"), &SkeletonModificationStack2D::execute);
ClassDB::bind_method(D_METHOD("enable_all_modifications", "enabled"), &SkeletonModificationStack2D::enable_all_modifications);
ClassDB::bind_method(D_METHOD("get_modification", "mod_idx"), &SkeletonModificationStack2D::get_modification);
ClassDB::bind_method(D_METHOD("add_modification", "modification"), &SkeletonModificationStack2D::add_modification);
ClassDB::bind_method(D_METHOD("delete_modification", "mod_idx"), &SkeletonModificationStack2D::delete_modification);
ClassDB::bind_method(D_METHOD("set_modification", "mod_idx", "modification"), &SkeletonModificationStack2D::set_modification);
ClassDB::bind_method(D_METHOD("set_modification_count", "count"), &SkeletonModificationStack2D::set_modification_count);
ClassDB::bind_method(D_METHOD("get_modification_count"), &SkeletonModificationStack2D::get_modification_count);
ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModificationStack2D::get_is_setup);
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModificationStack2D::set_enabled);
ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModificationStack2D::get_enabled);
ClassDB::bind_method(D_METHOD("set_strength", "strength"), &SkeletonModificationStack2D::set_strength);
ClassDB::bind_method(D_METHOD("get_strength"), &SkeletonModificationStack2D::get_strength);
ClassDB::bind_method(D_METHOD("get_skeleton"), &SkeletonModificationStack2D::get_skeleton);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0, 1, 0.001"), "set_strength", "get_strength");
ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0, 100, 1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Modifications,modifications/"), "set_modification_count", "get_modification_count");
}
SkeletonModificationStack2D::SkeletonModificationStack2D() {
}

View file

@ -0,0 +1,99 @@
/**************************************************************************/
/* skeleton_modification_stack_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_MODIFICATION_STACK_2D_H
#define SKELETON_MODIFICATION_STACK_2D_H
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/2d/skeleton/skeleton_modification_2d.h"
///////////////////////////////////////
// SkeletonModificationStack2D
///////////////////////////////////////
class Skeleton2D;
class SkeletonModification2D;
class Bone2D;
class SkeletonModificationStack2D : public Resource {
GDCLASS(SkeletonModificationStack2D, Resource);
friend class Skeleton2D;
friend class SkeletonModification2D;
protected:
static void _bind_methods();
void _get_property_list(List<PropertyInfo> *p_list) const;
bool _set(const StringName &p_path, const Variant &p_value);
bool _get(const StringName &p_path, Variant &r_ret) const;
public:
Skeleton2D *skeleton = nullptr;
bool is_setup = false;
bool enabled = false;
float strength = 1.0;
enum EXECUTION_MODE {
execution_mode_process,
execution_mode_physics_process
};
Vector<Ref<SkeletonModification2D>> modifications = Vector<Ref<SkeletonModification2D>>();
void setup();
void execute(float p_delta, int p_execution_mode);
bool editor_gizmo_dirty = false;
void draw_editor_gizmos();
void set_editor_gizmos_dirty(bool p_dirty);
void enable_all_modifications(bool p_enable);
Ref<SkeletonModification2D> get_modification(int p_mod_idx) const;
void add_modification(Ref<SkeletonModification2D> p_mod);
void delete_modification(int p_mod_idx);
void set_modification(int p_mod_idx, Ref<SkeletonModification2D> p_mod);
void set_modification_count(int p_count);
int get_modification_count() const;
void set_skeleton(Skeleton2D *p_skeleton);
Skeleton2D *get_skeleton() const;
bool get_is_setup() const;
void set_enabled(bool p_enabled);
bool get_enabled() const;
void set_strength(float p_strength);
float get_strength() const;
SkeletonModificationStack2D();
};
#endif // SKELETON_MODIFICATION_STACK_2D_H

View file

@ -0,0 +1,48 @@
/**************************************************************************/
/* tile_set.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
#include "tile_set.h"
Ref<NavigationPolygon> TileData::_get_navigation_polygon_bind_compat_84660(int p_layer_id) const {
return get_navigation_polygon(p_layer_id, false, false, false);
}
Ref<OccluderPolygon2D> TileData::_get_occluder_bind_compat_84660(int p_layer_id) const {
return get_occluder(p_layer_id, false, false, false);
}
void TileData::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("get_navigation_polygon"), &TileData::_get_navigation_polygon_bind_compat_84660);
ClassDB::bind_compatibility_method(D_METHOD("get_occluder"), &TileData::_get_occluder_bind_compat_84660);
}
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,157 @@
/**************************************************************************/
/* world_boundary_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 "world_boundary_shape_2d.h"
#include "core/math/geometry_2d.h"
#include "servers/physics_server_2d.h"
#include "servers/rendering_server.h"
bool WorldBoundaryShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
Vector2 point = distance * normal;
Vector2 l[2][2] = { { point - normal.orthogonal() * 100, point + normal.orthogonal() * 100 }, { point, point + normal * 30 } };
for (int i = 0; i < 2; i++) {
Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, l[i]);
if (p_point.distance_to(closest) < p_tolerance) {
return true;
}
}
return false;
}
void WorldBoundaryShape2D::_update_shape() {
Array arr;
arr.push_back(normal);
arr.push_back(distance);
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), arr);
emit_changed();
}
void WorldBoundaryShape2D::set_normal(const Vector2 &p_normal) {
// Can be non-unit but prevent zero.
ERR_FAIL_COND(p_normal.is_zero_approx());
if (normal == p_normal) {
return;
}
normal = p_normal;
_update_shape();
}
void WorldBoundaryShape2D::set_distance(real_t p_distance) {
if (distance == p_distance) {
return;
}
distance = p_distance;
_update_shape();
}
Vector2 WorldBoundaryShape2D::get_normal() const {
return normal;
}
real_t WorldBoundaryShape2D::get_distance() const {
return distance;
}
void WorldBoundaryShape2D::draw(const RID &p_to_rid, const Color &p_color) {
Vector2 point = distance * normal;
real_t line_width = 3.0;
// Draw collision shape line.
PackedVector2Array line_points = {
point - normal.orthogonal() * 100,
point - normal.orthogonal() * 60,
point + normal.orthogonal() * 60,
point + normal.orthogonal() * 100
};
Color transparent_color = Color(p_color, 0);
PackedColorArray line_colors = {
transparent_color,
p_color,
p_color,
transparent_color
};
RS::get_singleton()->canvas_item_add_polyline(p_to_rid, line_points, line_colors, line_width);
// Draw arrow.
Color arrow_color = p_color.inverted();
Transform2D xf;
xf.rotate(normal.angle());
Vector<Vector2> arrow_points = {
xf.xform(Vector2(distance + line_width / 2, -2.5)),
xf.xform(Vector2(distance + 20, -2.5)),
xf.xform(Vector2(distance + 20, -10)),
xf.xform(Vector2(distance + 40, 0)),
xf.xform(Vector2(distance + 20, 10)),
xf.xform(Vector2(distance + 20, 2.5)),
xf.xform(Vector2(distance + line_width / 2, 2.5)),
};
RS::get_singleton()->canvas_item_add_polyline(p_to_rid, arrow_points, { arrow_color }, line_width / 2);
}
Rect2 WorldBoundaryShape2D::get_rect() const {
Vector2 point = distance * normal;
Vector2 l1[2] = { point - normal.orthogonal() * 100, point + normal.orthogonal() * 100 };
Vector2 l2[2] = { point, point + normal * 30 };
Rect2 rect;
rect.position = l1[0];
rect.expand_to(l1[1]);
rect.expand_to(l2[0]);
rect.expand_to(l2[1]);
return rect;
}
real_t WorldBoundaryShape2D::get_enclosing_radius() const {
return distance;
}
void WorldBoundaryShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_normal", "normal"), &WorldBoundaryShape2D::set_normal);
ClassDB::bind_method(D_METHOD("get_normal"), &WorldBoundaryShape2D::get_normal);
ClassDB::bind_method(D_METHOD("set_distance", "distance"), &WorldBoundaryShape2D::set_distance);
ClassDB::bind_method(D_METHOD("get_distance"), &WorldBoundaryShape2D::get_distance);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "normal"), "set_normal", "get_normal");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less,suffix:px"), "set_distance", "get_distance");
}
WorldBoundaryShape2D::WorldBoundaryShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->world_boundary_shape_create()) {
_update_shape();
}

View file

@ -0,0 +1,64 @@
/**************************************************************************/
/* world_boundary_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 WORLD_BOUNDARY_SHAPE_2D_H
#define WORLD_BOUNDARY_SHAPE_2D_H
#include "scene/resources/2d/shape_2d.h"
class WorldBoundaryShape2D : public Shape2D {
GDCLASS(WorldBoundaryShape2D, Shape2D);
// WorldBoundaryShape2D is often used for one-way platforms, where the normal pointing up makes sense.
Vector2 normal = Vector2(0, -1);
real_t distance = 0.0;
void _update_shape();
protected:
static void _bind_methods();
public:
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
void set_normal(const Vector2 &p_normal);
void set_distance(real_t p_distance);
Vector2 get_normal() const;
real_t get_distance() const;
virtual void draw(const RID &p_to_rid, const Color &p_color) override;
virtual Rect2 get_rect() const override;
virtual real_t get_enclosing_radius() const override;
WorldBoundaryShape2D();
};
#endif // WORLD_BOUNDARY_SHAPE_2D_H