feat: godot-engine-source-4.3-stable
This commit is contained in:
parent
c59a7dcade
commit
7125d019b5
11149 changed files with 5070401 additions and 0 deletions
6
engine/scene/resources/2d/SCsub
Normal file
6
engine/scene/resources/2d/SCsub
Normal 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")
|
||||
124
engine/scene/resources/2d/capsule_shape_2d.cpp
Normal file
124
engine/scene/resources/2d/capsule_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
64
engine/scene/resources/2d/capsule_shape_2d.h
Normal file
64
engine/scene/resources/2d/capsule_shape_2d.h
Normal 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
|
||||
95
engine/scene/resources/2d/circle_shape_2d.cpp
Normal file
95
engine/scene/resources/2d/circle_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
58
engine/scene/resources/2d/circle_shape_2d.h
Normal file
58
engine/scene/resources/2d/circle_shape_2d.h
Normal 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
|
||||
119
engine/scene/resources/2d/concave_polygon_shape_2d.cpp
Normal file
119
engine/scene/resources/2d/concave_polygon_shape_2d.cpp
Normal 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);
|
||||
}
|
||||
55
engine/scene/resources/2d/concave_polygon_shape_2d.h
Normal file
55
engine/scene/resources/2d/concave_polygon_shape_2d.h
Normal 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
|
||||
143
engine/scene/resources/2d/convex_polygon_shape_2d.cpp
Normal file
143
engine/scene/resources/2d/convex_polygon_shape_2d.cpp
Normal 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()) {
|
||||
}
|
||||
59
engine/scene/resources/2d/convex_polygon_shape_2d.h
Normal file
59
engine/scene/resources/2d/convex_polygon_shape_2d.h
Normal 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
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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
|
||||
587
engine/scene/resources/2d/navigation_polygon.cpp
Normal file
587
engine/scene/resources/2d/navigation_polygon.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
167
engine/scene/resources/2d/navigation_polygon.h
Normal file
167
engine/scene/resources/2d/navigation_polygon.h
Normal 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
|
||||
562
engine/scene/resources/2d/polygon_path_finder.cpp
Normal file
562
engine/scene/resources/2d/polygon_path_finder.cpp
Normal 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() {
|
||||
}
|
||||
98
engine/scene/resources/2d/polygon_path_finder.h
Normal file
98
engine/scene/resources/2d/polygon_path_finder.h
Normal 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
|
||||
107
engine/scene/resources/2d/rectangle_shape_2d.cpp
Normal file
107
engine/scene/resources/2d/rectangle_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
60
engine/scene/resources/2d/rectangle_shape_2d.h
Normal file
60
engine/scene/resources/2d/rectangle_shape_2d.h
Normal 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
|
||||
100
engine/scene/resources/2d/segment_shape_2d.cpp
Normal file
100
engine/scene/resources/2d/segment_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
63
engine/scene/resources/2d/segment_shape_2d.h
Normal file
63
engine/scene/resources/2d/segment_shape_2d.h
Normal 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
|
||||
117
engine/scene/resources/2d/separation_ray_shape_2d.cpp
Normal file
117
engine/scene/resources/2d/separation_ray_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
61
engine/scene/resources/2d/separation_ray_shape_2d.h
Normal file
61
engine/scene/resources/2d/separation_ray_shape_2d.h
Normal 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
|
||||
124
engine/scene/resources/2d/shape_2d.cpp
Normal file
124
engine/scene/resources/2d/shape_2d.cpp
Normal 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);
|
||||
}
|
||||
70
engine/scene/resources/2d/shape_2d.h
Normal file
70
engine/scene/resources/2d/shape_2d.h
Normal 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
|
||||
244
engine/scene/resources/2d/skeleton/skeleton_modification_2d.cpp
Normal file
244
engine/scene/resources/2d/skeleton/skeleton_modification_2d.cpp
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
|
@ -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
|
||||
48
engine/scene/resources/2d/tile_set.compat.inc
Normal file
48
engine/scene/resources/2d/tile_set.compat.inc
Normal 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
|
||||
6970
engine/scene/resources/2d/tile_set.cpp
Normal file
6970
engine/scene/resources/2d/tile_set.cpp
Normal file
File diff suppressed because it is too large
Load diff
1001
engine/scene/resources/2d/tile_set.h
Normal file
1001
engine/scene/resources/2d/tile_set.h
Normal file
File diff suppressed because it is too large
Load diff
157
engine/scene/resources/2d/world_boundary_shape_2d.cpp
Normal file
157
engine/scene/resources/2d/world_boundary_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
64
engine/scene/resources/2d/world_boundary_shape_2d.h
Normal file
64
engine/scene/resources/2d/world_boundary_shape_2d.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue